Repository navigation
fix(logging): shutdown hook cleanup reaches close0 when hook removal throws (#7686) - #7699
Merged
Aias00 merged 1 commit intoOct 19, 2026
Merged
Conversation
Aias00
approved these changes
Oct 17, 2026
Aias00
left a comment
Contributor
There was a problem hiding this comment.
Summary
Fixes #7686: when close() is invoked by the registered shutdown-hook thread itself during JVM shutdown, Runtime.removeShutdownHook() throws IllegalStateException("Shutdown in progress"). In the previous code this exception propagated out of close() before close0() was reached, so the real resource cleanup was silently skipped exactly at JVM-shutdown time — the worst possible moment.
What the change does
AbstractLogConsumeClient.java:
- New
tryRemoveShutdownHook(): readscloseThread.get(), and wraps the new protectedremoveHook(hook)in atry/catch (IllegalStateException)that logs at DEBUG. Cleanup now always proceeds toclose0(). - New protected
removeHook(hook): the single seam that callsRuntime.getRuntime().removeShutdownHook(hook), so tests can simulate the JVM-shutdown throw. close()now callstryRemoveShutdownHook()first, then usesisStarted.compareAndSet(true, false)(atomic, idempotent) before invokingclose0()— soclose0()runs exactly once even under concurrent/re-entrant close.
Verification
- Master confirmation: master
close()is exactly the buggy form — directremoveShutdownHook(closeThread.get())with notry/catch, followed byif (isStarted.get()) { isStarted.set(false); close0(); }. The PR correctly isolates and fixes it. closeThreadscope:closeThreadis referenced only inside the parent class (field declaration,initClientset +addShutdownHook, andtryRemoveShutdownHookget). No subclass overrides or touches it → refactor is safe.- Idempotency / race safety: the old
isStarted.set(false)after aget()check was effectively idempotent single-threaded; the newcompareAndSetadditionally makes concurrentclose()calls safe (two threads can no longer both runclose0()). Strict improvement, no regression.tryRemoveShutdownHook()remains unconditionally invoked, so the non-shutdown path is unchanged. - Tests are valid, not trivial:
testClose0StillRunsWhenHookRemovalFailsusesThrowingHookLogConsumeClientwhoseremoveHookthrows. Confirmed from source that the parentinitClientsetscloseThreadto a registered hook thread (line 79-80), sotryRemoveShutdownHook()reads it and the override is genuinely invoked — the test really exercises theIllegalStateExceptionpath and assertsclose0()ran once.testCloseIsIdempotent: threeclose()calls →close0()runs once.- Local:
mvn -pl shenyu-plugin/shenyu-plugin-logging/shenyu-plugin-logging-common test -Dtest=AbstractLogConsumeClientTest→ 5 tests, 0 failures, BUILD SUCCESS.
- CI: all relevant checks pass —
pr_build,Analyze (java),check-license-header,rat_check,scope,sql-matrix,registry_dependencies,build (shenyu-integrated-test-http),build (shenyu-integrated-test-k8s-gateway-api-http),agent_gateway_migrations. The only reds aree2e(4s aggregator) ande2e-case (e2e-logging-rocketmq-compose). These are attributed to the known RocketMQ logging-compose e2e flake — orthogonal to this PR (which only changesclose()+ a unit test and never touches the rocketmq e2e test or the consume path); this exact job has flaked on unrelated PRs (#7666 / #7661 / #7669).
Notes (non-blocking)
- In
testClose0StillRunsWhenHookRemovalFails, thefinallyblock removesclient.getRegisteredHook()— a thread created ininitClient0that is not the one actually registered viaaddShutdownHook(closeThread.get())(parent line 80). The real shutdown hook therefore leaks for the rest of the JVM. Becauseclose()is now idempotent (compareAndSet), the leak is benign (no doubleclose0()on actual shutdown), but for test hygiene it would be cleaner to removecloseThread.get()in thefinally. Not a correctness issue.
Conclusion
Correct, well-scoped fix for a real shutdown-time cleanup gap, with valid regression tests and green relevant CI. APPROVE.
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 #7686.
Modifications
AbstractLogConsumeClient.close()removed its own shutdown hook before any exception handling. Whenclose()runs from the hook thread during JVM shutdown,Runtime.removeShutdownHookthrowsIllegalStateException: Shutdown in progress, soclose0()was never reached on the shutdown path and the backend was not flushed or cleaned up.Hook removal now tolerates
IllegalStateException: during shutdown the removal is both impossible and unnecessary, and cleanup proceeds toclose0(). The single call intoRuntimeis isolated in a protectedremoveHookmethod so the shutdown-time failure can be reproduced deterministically instead of depending on real JVM shutdown.isStartedis flipped withcompareAndSet(true, false), soclose0()runs exactly once across explicit close, re-initialization and the shutdown hook; previously a concurrent close could run cleanup twice.Verifying this change
New
testClose0StillRunsWhenHookRemovalFails: throwsIllegalStateException("Shutdown in progress")from the hook-removal seam and assertsclose0()still executes. Errors on the pre-fix code, green after.New
testCloseIsIdempotent: repeated close runsclose0()exactly once.Targeted tests pass:
./mvnw -B -ntp -pl shenyu-plugin/shenyu-plugin-logging/shenyu-plugin-logging-common -am test -Dmaven.javadoc.skip=true62/62 green.
Notes
close(), so no downstream change is needed.