fix: isolate inherited hubs under hub_isolation_level = :fiber#3036
Open
SeanLF wants to merge 2 commits into
Open
fix: isolate inherited hubs under hub_isolation_level = :fiber#3036SeanLF wants to merge 2 commits into
SeanLF wants to merge 2 commits into
Conversation
Fiber storage is inherited by child fibers and by threads started from them, handing over the same Hub object rather than a copy. Under hub_isolation_level = :fiber that left sibling request fibers and worker threads sharing one hub, and one unsynchronized scope stack, so concurrent requests and jobs read each other's tags. The `|| clone` fallback never fired because the inherited value was not nil. Tag each stored entry with its owning fiber so a context that inherited one takes its own copy. The copy keeps the live span: Scope#dup deep-copies it, and spans recorded on a detached transaction copy are never sent, which would break the Async fan-out this mode exists for. Reported by @trevorturk in getsentry#3018. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fiber-storage gem polyfills Fiber[] on Ruby < 3.2 and on JRuby, so :fiber is active there too, but the polyfill does not inherit into Thread.new. A thread started from a fiber therefore begins from the main hub and cannot stay on the parent's trace, which the thread half of this expectation assumed it could. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Jul 26, 2026
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.
Fixes the
:fiberisolation level, which did not isolate anything. Reported by @trevorturk in #3018.The bug
Fiber storage is inherited by child fibers and by threads started from a fiber, and the inherited value is the same object. Once anything seeds a hub in an ancestor fiber, every sibling request fiber and every worker thread shares one
Hub, along with its unsynchronized@stack.Reproduced on master (Ruby 4.0):
with_scope: every one read another job's tagsThe
|| clonefallback inget_current_hubnever fires in those contexts, because the inherited value is not nil.The fix
Tag each stored entry with its owning fiber. A context that reads an entry it did not store gets its own copy instead of sharing.
The copy is a fork, not
Hub#clone: private scope stack, but the live span. Without that,Scope#dupdeep-dups the span,Transaction#deep_dupbuilds a freshSpanRecorder, and every child-fiber span lands on a detached transaction copy that is never sent. Async fan-out inside a request is the workload this mode exists for.Fiber contexts now behave like thread contexts (own hub, own scope stack) and stay on the same trace.
Behaviour change
The spec asserting a child fiber gets the same hub object is inverted: it now asserts the child gets the parent's context in its own hub. Sharing the object is what caused the leak.
Sentry.last_event_idand the per-hub profiler registry do not cross the boundary, matching existing:threadbehaviour.Testing
Hub#clone, so it guards the fix rather than the original bugfiber-storagegem polyfillsFiber[], so:fiberis active there too; the polyfill inherits into child fibers (same bug, same fix) but not intoThread.new, so a thread there already starts from the main hubget_current_hub(50ns to 77ns), no extra allocations. The fork runs once per new context and is O(spans)