Fix infinite loop when an error event fails in @ERROR - #5463
Open
tarun9715m wants to merge 1 commit into
Open
Conversation
If a record is larger than chunk_limit_size, the buffer raises BufferChunkOverflowError at emit time and the event is routed to @error. When the output in @error raises the same error, the event was routed to @error again, and this repeated without bound. Nothing marked the event as having already been through the error path, so there was no termination condition. The routing is recursive, so the worker eventually died with SystemStackError. @error is the last resort route. An error event which fails while it is being routed there is now dumped with a distinct error log and dropped, instead of being routed back to @error. The behavior without an @error label is unchanged, including re-raising the error and the emit_error_log_interval suppression. Fixes fluent#5462 Signed-off-by: tarun9715m <tarun9715m@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Watson1978
requested changes
Aug 6, 2026
Watson1978
left a comment
Contributor
There was a problem hiding this comment.
Thank you for working on this.
I cannot take this change as it is. However well-intentioned, discarding events without the user asking for it is not acceptable behaviour for Fluentd. @ERROR is a recovery path, so dropping the events routed to it defeats its own purpose. Data should be discarded only where the user has explicitly configured it to be (e.g. overflow_action drop_oldest_chunk), never as a built-in fallback.
Note also that in the event stream path the discarded records are not preserved anywhere: the new log line carries only record_count, so their contents are lost entirely.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
Fixes #5462.
A record larger than
chunk_limit_sizeraisesBufferChunkOverflowErrorat emit time and the event is routed to@ERROR. If the output inside@ERRORraises the same error, the event was routed to@ERRORagain, and this repeated without bound. Nothing marked the event as having already been through the error path, so there was no termination condition.The routing is recursive rather than a flat loop:
EventRouter#emit_streamrescues →RootAgent#handle_emits_error→@error_collector.emit_stream→ the plugin in@ERRORraises → that router's rescue →handle_emits_error→ …So each iteration adds stack frames and the worker eventually dies with
SystemStackError(~7000 levels in my reproduction). None of the buffer's failure-handling parameters apply, because they govern flush-time failures:retry_max_times/retry_timeoutneed a flush that never happened,overflow_actiongovernsBufferOverflowError(buffer full) which is a different error class, and<secondary>receives a chunk after flush retries are exhausted when no chunk was ever created.How
@ERRORis the last-resort route, so it should be bounded by construction. An error event that fails while being routed to@ERRORis now dumped with a distincterror-level log and dropped, instead of being routed back. This uses a thread-local re-entrancy guard inRootAgent, applied to bothhandle_emits_error(event streams) andemit_error_event(single records — reached when a plugin inside@ERROR, such as a filter, callsrouter.emit_error_eventitself).The distinct message also addresses the second half of the issue: a genuine loop is now distinguishable from repeated single failures.
Behaviour when no
@ERRORlabel is configured is unchanged, includingraise errorand theemit_error_log_intervalsuppression. The guard resets in anensure, so the next error event still gets its normal single attempt at@ERROR.Reproduction
Config from the issue (
chunk_limit_size 7in both the primary output and@ERROR), 3 × 2 MB-line input file, 25 second window:send an error event stream to @ERRORSystemStackError/ worker deathKnown limitation
The guard catches synchronous re-entry, which covers every emit-time error path including the reported one. An error re-emitted from a different thread inside
@ERRORwould not be caught — bounding that would require a marker on the event itself. Happy to explore that here if a reviewer would prefer it.Tests
4 regression tests added to
test/test_root_agent.rb, covering both entry points, the dropped-and-not-rerouted behaviour, and that the guard resets so a subsequent error event is still routed normally.bundle exec rake teston macOS / Ruby 4.0.6:4345 tests, 15851 assertions, 0 failures, 0 errors, 3 pendings, 36 omissions.🤖 Generated with Claude Code