Skip to content

Fix memory leak in Webflux related to an ever growing stack - #2580

Merged
adinauer merged 2 commits into
mainfrom
feat/new-hub-for-webflux-request
Mar 2, 2023
Merged

adinauer merged 2 commits into
mainfrom
feat/new-hub-for-webflux-request

Conversation

@adinauer

@adinauer adinauer commented Mar 2, 2023 •

Copy link
Copy Markdown
Member

📜 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.

  @GetMapping("{id}")
  Mono<Person> person(@PathVariable Long id) {
//    LOGGER.info("Loading person with id={}", id);
//    throw new IllegalArgumentException("Something went wrong [id=" + id + "]");
    return personService.create(new Person("f", "l")).flatMap(it -> Mono.error(new RuntimeException("oopsie from webflux")));
  }

wrk -t2 -c100 -d30s http://user:password@localhost:8080/person/91

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

@github-actions

github-actions Bot commented Mar 2, 2023 •

Copy link
Copy Markdown
Contributor
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 6e9681d

SpringApplication.run(SentryDemoApplication.class, args);
}

@Bean

Copy link
Copy Markdown
Member Author

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 {

Copy link
Copy Markdown
Member Author

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copied from other samples

public Mono<Void> filter(
final @NotNull ServerWebExchange serverWebExchange,
final @NotNull WebFilterChain webFilterChain) {
@NotNull IHub requestHub = Sentry.cloneMainHub();

Copy link
Copy Markdown
Member Author

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.

__ -> {
hub.popScope();
doFinally(requestHub);
Sentry.setCurrentHub(NoOpHub.getInstance());

Copy link
Copy Markdown
Member Author

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);

Copy link
Copy Markdown
Member Author

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.

@github-actions

github-actions Bot commented Mar 2, 2023 •

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 347.54 ms 394.89 ms 47.35 ms
Size 1.73 MiB 2.34 MiB 626.22 KiB

Baseline results on branch: main

Startup times

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

Previous results on branch: feat/new-hub-for-webflux-request

Startup times

Revision Plain With Sentry Diff
303fe40 286.78 ms 320.36 ms 33.58 ms

App size

Revision Plain With Sentry Diff
303fe40 1.73 MiB 2.34 MiB 626.22 KiB

@codecov

codecov Bot commented Mar 2, 2023 •

Copy link
Copy Markdown

Codecov Report

Patch coverage: 76.19% and project coverage change: -0.02 ⚠️

Comparison is base (e5bbb00) 80.44% compared to head (6e9681d) 80.42%.

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              
Impacted Files Coverage Δ
...ebflux/SentryWebFilterWithThreadLocalAccessor.java 0.00% <0.00%> (ø)
...pring/jakarta/webflux/AbstractSentryWebFilter.java 94.44% <94.44%> (ø)
...sentry/spring/jakarta/webflux/SentryWebFilter.java 100.00% <100.00%> (+4.54%) ⬆️
...java/io/sentry/spring/webflux/SentryWebFilter.java 96.00% <100.00%> (+0.76%) ⬆️
sentry/src/main/java/io/sentry/HubAdapter.java 9.23% <0.00%> (+3.07%) ⬆️

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.
📢 Do you have feedback about the report comment? Let us know in this issue.

@adinauer
adinauer merged commit 1345e5d into main Mar 2, 2023
@adinauer
adinauer deleted the feat/new-hub-for-webflux-request branch March 2, 2023 10:10
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.

2 participants