Skip to content

fix(batcher): Do not let a failed flush kill the flusher thread (#7138) - #7186

Open
zkasuran wants to merge 1 commit into
getsentry:masterfrom
zkasuran:fix/tolerate-exceptions-in-flush-loop
Open

fix(batcher): Do not let a failed flush kill the flusher thread (#7138)#7186
zkasuran wants to merge 1 commit into
getsentry:masterfrom
zkasuran:fix/tolerate-exceptions-in-flush-loop

Conversation

@zkasuran

Copy link
Copy Markdown

Closes #7138.

What

The batcher's _flush_loop calls _flush() unguarded. Any exception there (the RuntimeError in the issue, but really anything raised while serializing or sending a batch) propagates out of the loop and ends the daemon flusher thread. After that _ensure_thread() still sees _flusher_pid == pid and returns True, so add() keeps appending to a buffer that nothing drains. Once it reaches MAX_BEFORE_DROP every later log, metric or span is dropped silently for the rest of the process lifetime.

Fix

Following the issue title, wrap the flush in each loop in capture_internal_exceptions(), the SDK's own helper for internal errors that should be logged rather than propagated:

  • Batcher._flush_loop (logs and metrics)
  • SpanBatcher._flush_loop (both the pending-buckets flush and the periodic full flush)

A single bad batch is now logged and swallowed. The flusher thread stays alive and the next flush delivers everything that queued up in the meantime.

Tests

One regression test per loop (test_flush_loop_swallows_flush_exception in tests/test_logs.py and tests/tracing/test_span_batcher.py) drives one loop iteration where _flush raises, then asserts _flush_loop returns instead of propagating. They fail on unpatched source (the RuntimeError escapes the loop) and pass with the guard. Both carry the tests_internal_exceptions marker since they intentionally exercise the internal-exception path.

Verification

  • pytest tests/tracing/test_span_batcher.py tests/test_logs.py tests/test_metrics.py: 68 passed.
  • The two new tests fail on unpatched source and pass with the fix.
  • ruff check and ruff format --check: clean on the changed files.
  • mypy sentry_sdk: no new errors (the pre-existing ones are all in an unrelated integration).

AI assistance (Claude, Anthropic) was used in developing this change. The design, review and verification were done by the author. Verified locally before submitting: the tests, ruff and mypy above.

…entry#7138)

An unhandled exception inside `_flush_loop` terminated the batcher's daemon
flusher thread. After that the buffer kept filling with nothing draining it.
Every later log, metric or span was then dropped for the rest of the process
lifetime once the queue hit its cap.

Wrap the flush call in each loop (`Batcher._flush_loop` and
`SpanBatcher._flush_loop`) in `capture_internal_exceptions()`, the SDK's own
helper for errors that should be logged rather than propagated. A single bad
batch is now swallowed and logged so the loop keeps running.

Adds a regression test for each loop that drives one iteration where the flush
raises then asserts the loop returns instead of propagating.
@zkasuran
zkasuran requested a review from a team as a code owner August 13, 2026 09:13
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.

Tolerate exceptions in _flush_loop()

1 participant