Skip to content

fix: eliminate three thread-safety bugs in telemetry delta reporting#90

Merged
raflyhangga merged 3 commits into
mainfrom
fix/telemetry-thread-safety
Jun 30, 2026
Merged

fix: eliminate three thread-safety bugs in telemetry delta reporting#90
raflyhangga merged 3 commits into
mainfrom
fix/telemetry-thread-safety

Conversation

@raflyhangga

Copy link
Copy Markdown
Contributor

Summary

  • Race on active_duration_s: _activity_detector was incrementing active_duration_s outside any lock, while _post_telemetry_delta read it twice inside _telemetry_lock (which didn't guard the write). If the detector fired between the two reads, _last_posted_active_s would advance past what was actually reported, permanently losing that second from every future delta. Fix: move the increment inside the existing _counter_lock block and snapshot the field once under that same lock in _post_telemetry_delta.

  • RuntimeError from dict(rows_written): Multiple stream-flush threads can add new keys to rows_written concurrently (each under its own per-stream lock, but with no shared guard on the dict). Iterating it via dict() while another thread inserts a key raises RuntimeError: dictionary changed size during iteration. Fix: add _rows_written_lock to WriteManager, hold it at the single mutation site in _write_buffer_to_file, and hold it at every dict(rows_written) call site (_post_telemetry_delta and _write_manifest).

  • Redundant network calls: post_telemetry was called even when both duration_delta and events_delta were zero. Fix: early-return before advancing _last_posted_active_s / _last_posted_rows so no interval is silently dropped from the accumulator on a skipped call.

Test plan

  • Run pytest tests/ and confirm no regressions
  • Start a trace session with --upload enabled and verify telemetry still fires after each file upload
  • Confirm manifest.json rows_written counts match expected stream output

🤖 Generated with Claude Code

- active_duration_s was incremented outside any lock in _activity_detector,
  letting _post_telemetry_delta read it twice with a torn value and permanently
  lose increments; move the increment inside _counter_lock and snapshot the
  field once under that lock before computing the delta
- dict(rows_written) could raise RuntimeError when concurrent stream-flush
  threads added new keys mid-iteration; add _rows_written_lock to WriteManager
  and hold it at both the mutation site and every call site that copies the dict
  (telemetry delta + manifest snapshot)
- skip post_telemetry network call when both duration_delta and events_delta
  are zero; defer advancing _last_posted_* until we actually post so no
  interval is silently dropped from the accumulator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces thread-safety improvements to IOTracer and WriterManager by adding synchronization locks around shared state variables, such as active_duration_s and rows_written. It also fixes an initialization bug where self.automatic_upload was not correctly passed to WriteManager. The review feedback highlights two key areas for improvement: first, protecting the write_dropped dictionary under the same _rows_written_lock to prevent potential concurrent modification errors during manifest generation; second, improving encapsulation by exposing a thread-safe snapshot method on WriteManager rather than directly accessing its internal lock and dictionary from IOTracer.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/tracer/IOTracer.py Outdated
Comment thread src/tracer/IOTracer.py Outdated
raflyhangga and others added 2 commits June 30, 2026 08:56
write_dropped was mutated on error paths without any lock, meaning two
streams failing simultaneously could concurrently add new keys while
_write_manifest iterated the dict via dict(), raising RuntimeError.

Reuse the existing _rows_written_lock at the mutation site in
_write_buffer_to_file and expand the snapshot block in _write_manifest
to capture write_dropped alongside rows_written under the same lock.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nager

IOTracer was reaching directly into _rows_written_lock and rows_written
to build thread-safe snapshots, duplicating the lock pattern in two
places. Add get_rows_written_snapshot() and get_counters_snapshot() to
WriteManager so callers never touch the internal lock directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@raflyhangga
raflyhangga merged commit da36e1d into main Jun 30, 2026
4 checks passed
@raflyhangga
raflyhangga deleted the fix/telemetry-thread-safety branch June 30, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant