Hubs / Scopes Merge 18 - Implement pushScope ,popScope and withScope for Scopes#3321
Hubs / Scopes Merge 18 - Implement pushScope ,popScope and withScope for Scopes#3321
pushScope ,popScope and withScope for Scopes#3321Conversation
…pes-merge-2-add-scopes
pushScope ,popScope and withScope for ScopespushScope ,popScope and withScope for Scopes
|
| final @Nullable Scopes parent = getParent(); | ||
| if (parent != null) { | ||
| // TODO this is never closed | ||
| parent.makeCurrent(); |
There was a problem hiding this comment.
We're handed back a lifecycle token here which we never call close on. Popping scopes this way is kinda hacky. I haven't come up with a better approach yet.
There was a problem hiding this comment.
If I read it right makeCurrent() returns the token for the previous scope. Since this is legacy push/pop API, wouldn't it make sense to automatically close the previous scope?
| parent.makeCurrent(); | |
| final @NotNull ISentryLifecycleToken previousScopes = parent.makeCurrent(); | |
| previousScopes.close(); |
There was a problem hiding this comment.
The previous Scopes should be restored, when the current one is closed, so we can't close it when creating a new (child) scope.
Take a piece of nested code:
// outermost scopes (scopes1)
Sentry.withScope(scope -> {
// inner scopes (scopes2)
try { ... = scope.forkedCurrentScopes("...").makeCurrent()) {
// innermost scopes (scopes3)
}
}
When leaving the try block, we're closing scopes3 and restoring scopes2. When leaving the withScope lambda, we're closing scopes2 and restoring scopes1.
There was a problem hiding this comment.
I copied this pattern from OpenTelemetry, as we'll have to call their io.opentelemetry.context.Scope.close() inside our OTEL flavor lifecycle token. See https://github.com/getsentry/sentry-java/pull/3285/files#diff-ce5e1c852e10adfae80110c82615eaa9571a7eeb5ed8bb79512c08dc388c4bc0R43
There was a problem hiding this comment.
Think of this chain of lifecycle tokens as a replacement for the stack. We're keeping scopes from being garbage collected by holding a reference to each parent scopes instance.
| serverWebExchange.getAttributes().put(SENTRY_SCOPES_KEY, requestHub); | ||
| Sentry.setCurrentScopes(requestHub); | ||
| requestHub.pushScope(); | ||
| requestHub.pushScope(); // TODO don't |
There was a problem hiding this comment.
pushScope by itself here wouldn't be problematic, however combining this with e.g. setting request on the requestHub reference, leads to unexpected results where e.g. request isn't set on captured messages.
There was a problem hiding this comment.
Instead of pushScope, maybe we could simply call requestHub.forkedCurrentScopes() and then call .makeCurrent() on that. Then from there on only use the reference, returned by the fork call.
There was a problem hiding this comment.
I'm thinking tho we don't even need to call pushScope here since we're forking Scopes anyways.
There was a problem hiding this comment.
We could look into Mono.using() to do the cleanup for us in a follow up PR
There was a problem hiding this comment.
Let's test first and then follow up with a separate PR if it actually works / helps
Performance metrics 🚀
|
Is this really an issue? IMHO push/pop should only be used in combination with other old APIs like |
We'll see as bug reports come in, I guess. We can fix all our integrations but in theory it's possible users will experience some weird behaviour because of this. |
| Scopes forkedScopes = forkedScopes("withScope"); | ||
| Scopes forkedScopes = forkedCurrentScope("withScope"); | ||
| // TODO should forkedScopes be made current inside callback? | ||
| // TODO forkedScopes.makeCurrent()? |
There was a problem hiding this comment.
Depending on the behaviour we want to achieve, it might make sense to make them the currentScopes. IIRC Hub does this by pushing/popping scope around the callback.
We could just add the makeCurrent call to the the try/catch block here.
#skip-changelog
📜 Description
Without having a stack as we used to in
Hubthe only option I see is to make pushScope etc. manipulate scopes storage.There is one major problem here, where we use a
Scopes(used to beHub) reference that isn'tScopesAdapter(used to beHubAdapter). I think the most relevant here are reactive frameworks (reactor / webflux). Here's problematic code:Because
pushScopenow creates a fork ofScopesand sets it as current, doing anything with thescopesreference, will lead to unexpected results. In this caserequestisn't set on the message being captured. The problem is, this used to work just fine withHubbecause of its internal stack.The main reason, we have so much code, using scopes (previously hub) references is testability. Maybe we should consider a different approach for testing, e.g. using a special scopes storage. However I don't want to fiddle with tests and production code too much at the same time, so I suggest leaving tests alone as much as possible and once production changes are (mostly) done, we can revisit our testing approach.
This PR also introduces
makeCurrentonScopeswhich I copied from OpenTelemetry. It stores aScopesin the scopes storage, making it the current one, that is used by static API.I'm not yet entirely certain how to handle
pushScopeandpopScope. Maybe we should deprecate them in favor of a single new method, that hands back a lifecycle token for closing (popping) and restoring previous state.💡 Motivation and Context
💚 How did you test it?
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps