Skip to content

fix(bootstrap): own and dispose the gateway's custom Netty event loops (#7715) - #7726

Merged
Aias00 merged 1 commit into
apache:masterfrom
konlue:fix/netty-loopresources-lifecycle
Oct 19, 2026
Merged

Aias00 merged 1 commit into
apache:masterfrom
konlue:fix/netty-loopresources-lifecycle

Conversation

@konlue

@konlue konlue commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #7715.

Summary

EventLoopNettyCustomizer#apply creates a new LoopResources on every call and hands it to HttpServer#runOn, but nothing owns it: Spring Boot's server stop closes the bound server without disposing loops supplied by a customizer, so a stopped/restarted embedded server leaves its event-loop threads and resources behind.

Modifications

  • Add a shenyuServerLoopResources bean with @Bean(destroyMethod = "dispose"), created from NettyHttpProperties (same select/worker counts and conditions as before).
  • Pass that bean into EventLoopNettyCustomizer and use .runOn(loopResources) instead of allocating a new LoopResources in apply().
  • Add a lifecycle test: the bean is not disposed inside the context and is disposed after the context closes.

Verifying this change

  • New ShenyuNettyWebServerConfigurationTest#testEventLoopResourcesDisposedWithContext: inside ApplicationContextRunner, isDisposed() is false; after run() returns (context closed), isDisposed() is true. Removing destroyMethod = "dispose" makes this assertion fail (Expected: is <true>), restoring it passes.

  • Targeted tests pass:

    ./mvnw -B -ntp -pl shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway test -Dtest=ShenyuNettyWebServerConfigurationTest

    4/4 green.

  • Checkstyle passes:

    ./mvnw -B -ntp -pl shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway checkstyle:check

    clean.

Notes

One LoopResources instance per application context: server stop/start cycles inside the same context reuse it, and it is disposed exactly once when the context closes, so loops shared by a still-running server are never disposed early. Select/worker counts and the daemon flag are unchanged.

Review follow-up

A LoopResources supplied through runOn is not disposed by the embedded server — only Reactor Netty's shared HttpResources are — which is why the loop resources need an owner; the context-managed bean introduced here provides it.

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Ownership now has an owner, which is the whole point.

Evidence

  • LoopResources extends Disposable, so @Bean(destroyMethod = "dispose") is the correct lifecycle hook, and Disposable.dispose() in reactor-netty is idempotent (DefaultLoopResources guards it with a flag), so a double dispose cannot throw.
  • Destroy ordering is safe: nettyReactiveWebServerFactory takes the bean as a constructor argument, so the bean is destroyed after the factory, and Spring Boot's WebServerStartStopLifecycle stops the server during context close before singleton destruction. The loops therefore always outlive the server that uses them.
  • Server restart inside a live context (the case you call out) is also fine: the bean is only disposed at context close, so runOn still receives usable loops.
  • The test is the right shape — ApplicationContextRunner asserts not-disposed inside run() and disposed after it returns, and you verified the red-green by removing destroyMethod.
  • Both beans carry the same @ConditionalOnProperty(..., matchIfMissing = true), so the factory can never ask for a LoopResources bean that was not created.

Non-blocking

  1. Worth one sentence in the PR body confirming the interaction with reactor-netty: a LoopResources supplied through runOn is not disposed by the server (only resources from HttpResources are), which is exactly why this bean is needed. That is the assumption the whole fix rests on.
  2. shenyuServerLoopResources(ObjectProvider<NettyHttpProperties>) falls back to new NettyHttpProperties() when no properties bean exists — same pattern as the factory, so consistent. Just note that the fallback means two independent instances could in theory produce different select/worker counts; they do not today because both read the same provider.

CI: pr_build green.

@konlue

konlue commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Done. Note added to the PR body — a LoopResources supplied via runOn isn't disposed by the embedded server (only the shared HttpResources are), which is the ownership assumption behind the fix.

@Aias00
Aias00 merged commit 00c2e56 into apache:master Oct 19, 2026
28 checks passed
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.

[BUG] Gateway web-server shutdown leaves its custom Netty event loops running

2 participants