Transport#@discarded_events is a plain Hash with no synchronization. fetch_pending_client_report iterates it with map (transport.rb:198) while record_lost_event inserts into it (transport.rb:174, :179).
Ruby's iteration guard raises in the inserting thread, so the error surfaces in record_lost_event, whose callers include Client#capture_event on request threads and the background worker.
Reproduced as RuntimeError: can't add a new key into hash during iteration, but only after widening @discarded_events to 20k keys to open the window. In production the hash holds a handful of reason/category pairs, so the odds are low. Filing for completeness, not urgency.
Failing spec
Drop in sentry-ruby/spec/ and run bundle exec rspec spec/transport_discarded_events_spec.rb. Fails on master with RuntimeError: can't add a new key into hash during iteration.
# Stress repro, not a suite-quality spec. The window is widened deliberately:
# @discarded_events normally holds a handful of pairs, which makes the real
# iteration too short to interleave reliably.
# Run from sentry-ruby/: bundle exec rspec path/to/transport_discarded_events_spec.rb
require "spec_helper"
RSpec.describe "Transport#record_lost_event concurrency" do
before { perform_basic_setup }
let(:transport) { Sentry.get_current_client.transport }
it "does not raise while a client report is being built" do
errors = Queue.new
done = false
writer = Thread.new do
i = 0
until done
begin
transport.record_lost_event(:network_error, "category#{i += 1}")
rescue => e
errors << e
end
end
end
500.times do
wide = Hash.new(0)
20_000.times { |i| wide[[:network_error, "seed#{i}"]] = 1 }
transport.instance_variable_set(:@discarded_events, wide)
transport.send(:fetch_pending_client_report, force: true)
end
done = true
writer.join
expect(errors).to be_empty, "record_lost_event raised: #{errors.size > 0 ? errors.pop.inspect : ''}"
end
end
Transport#@discarded_eventsis a plain Hash with no synchronization.fetch_pending_client_reportiterates it withmap(transport.rb:198) whilerecord_lost_eventinserts into it (transport.rb:174, :179).Ruby's iteration guard raises in the inserting thread, so the error surfaces in
record_lost_event, whose callers includeClient#capture_eventon request threads and the background worker.Reproduced as
RuntimeError: can't add a new key into hash during iteration, but only after widening@discarded_eventsto 20k keys to open the window. In production the hash holds a handful of reason/category pairs, so the odds are low. Filing for completeness, not urgency.Failing spec
Drop in
sentry-ruby/spec/and runbundle exec rspec spec/transport_discarded_events_spec.rb. Fails on master withRuntimeError: can't add a new key into hash during iteration.