-
-
Notifications
You must be signed in to change notification settings - Fork 448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix memory leak in Webflux related to an ever growing stack #2580
Conversation
|
|
||
@SpringBootApplication | ||
public class SentryDemoApplication { | ||
public static void main(String[] args) { | ||
SpringApplication.run(SentryDemoApplication.class, args); | ||
} | ||
|
||
@Bean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copied from other samples
@@ -0,0 +1,25 @@ | |||
package io.sentry.samples.spring.boot; | |||
|
|||
public class Todo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copied from other samples
import org.springframework.web.reactive.function.client.WebClient; | ||
import reactor.core.publisher.Mono; | ||
|
||
@RestController |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copied from other samples
} | ||
|
||
@Override | ||
public Mono<Void> filter( | ||
final @NotNull ServerWebExchange serverWebExchange, | ||
final @NotNull WebFilterChain webFilterChain) { | ||
@NotNull IHub requestHub = Sentry.cloneMainHub(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now clone the hub from the main hub instead of using whatever hub is present on the thread.
return webFilterChain | ||
.filter(serverWebExchange) | ||
.doFinally( | ||
__ -> { | ||
hub.popScope(); | ||
doFinally(requestHub); | ||
Sentry.setCurrentHub(NoOpHub.getInstance()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only resets one of the ThreadLocals. In testing the thread were .doFirst
is run still keeps the hub alive. Don't know of a way to clean the ThreadLocal (yet) but I have another idea i wanna explore so we only leak an id and reference the hub in a store instead. I wanna try that in a different PR.
}) | ||
.doFirst( | ||
() -> { | ||
hub.pushScope(); | ||
Sentry.setCurrentHub(requestHub); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to set it as current hub for the thread as SentryScheduleHook
clones the current hub and without this it'd clone a random hub breaking scope.
Performance metrics 🚀
|
Revision | Plain | With Sentry | Diff |
---|---|---|---|
db5bd4e | 352.90 ms | 359.52 ms | 6.62 ms |
d691d8f | 352.56 ms | 389.59 ms | 37.03 ms |
db5bd4e | 314.82 ms | 349.17 ms | 34.35 ms |
db5bd4e | 347.00 ms | 366.74 ms | 19.74 ms |
db5bd4e | 286.94 ms | 315.26 ms | 28.32 ms |
e5bbb00 | 317.46 ms | 353.26 ms | 35.80 ms |
App size
Revision | Plain | With Sentry | Diff |
---|---|---|---|
db5bd4e | 1.73 MiB | 2.34 MiB | 626.30 KiB |
d691d8f | 1.73 MiB | 2.34 MiB | 626.23 KiB |
db5bd4e | 1.73 MiB | 2.34 MiB | 626.30 KiB |
db5bd4e | 1.73 MiB | 2.34 MiB | 626.30 KiB |
db5bd4e | 1.73 MiB | 2.34 MiB | 626.30 KiB |
e5bbb00 | 1.73 MiB | 2.34 MiB | 626.22 KiB |
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #2580 +/- ##
============================================
- Coverage 80.44% 80.42% -0.02%
- Complexity 4020 4024 +4
============================================
Files 332 333 +1
Lines 15160 15181 +21
Branches 1979 1979
============================================
+ Hits 12195 12210 +15
- Misses 2186 2192 +6
Partials 779 779
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
📜 Description
Use a new hub cloned from main hub for WebFlux requests instead of cloning the current one on the thread. This allows the hub, used for the request, to be garbage collected eventually. Previously we were using the current hub on the thread to call
.pushScope()
, then cloned that hub including the stack and the call to.popScope()
would go to the clone instead of the hub we called.pushScope()
on. This means we were popping the copied stack of the cloned hub leaving the stack of the original hub untouched. This lead to an ever growing stack on the original hub, causing a memory leak and also scope bleed because the original hub survives across requests and is never cleaned up.💡 Motivation and Context
Improves #2557 but still holds on to one hub per thread + stack and scope etc.
I have some ideas for further improving this which I'll try later. I'd like to first ship this as it already makes the situation a lot better.
💚 How did you test it?
Manually running our sample and looking at
visualvm
as well as debugging thread locals.wrk -t2 -c100 -d30s http://user:password@localhost:8080/person/91
📝 Checklist
sendDefaultPII
is enabled.🔮 Next steps