Repository navigation
Use JFR events to trace the shutdown sequence - #54440
Conversation
mkouba
commented
May 25, 2026
- emit JFR events so that shutdown actions can be observed with standard profiling tools
- monitored actions include: ShutdownContext.addShutdownTask(), ShutdownContext.addLastShutdownTask(), ShutdownListener, CDI Singleton and ApplicationScoped beans, CDI Singleton and Application contexts
|
Note that from JPMS perspective this adds the dependency on the |
This comment has been minimized.
This comment has been minimized.
Actually, I'm not even sure we want to depend on a |
I would say it is fine. |
|
Agree that depending on I wanna step back: what are you trying to achieve here? There's no linked issue, so I have no idea. And tracing specifically shutdown doesn't look like an ideal place to begin at. |
This comment has been minimized.
This comment has been minimized.
|
🤖 Automatically associated with the WG - Graceful Shutdown Working Group based on AI classification. |
|
Hi @mkouba, I took a stab as simplifying this. Find my code over here: https://github.com/Ladicek/quarkus/commits/shutdown-seq/ You'll note that 90% of what I did was peeling off the abstraction layers you introduced. The only thing they do is add overhead. The inline code isn't that longer than what you had. I also adopted the pattern used by @dmlloyd in smallrye/smallrye-modules#7: FooEvent foo = new FooEvent();
if (foo.isEnabled()) {
foo.bar = "foobar";
foo.begin();
} else {
foo = null;
}
try {
...
} finally {
if (foo != null) {
foo.commit();
}
}Seems overall a little shorter and more obvious. Do what you want with it :-) |
Well, I don't see how the snippet above is shorter/less error-prone than: ShutdownSequence.run(new Runnable() {
@Override
public void run() {
i.shutdown(new LatchShutdownNotification(shutdown));
}
}, ActionType.SHUTDOWN_LISTENER_SHUTDOWN, i.getClass().getName());It could even be (but we have some weird rules to avoid using lambdas at runtime at all cost 🤷): ShutdownSequence.run(() -> i.shutdown(new LatchShutdownNotification(shutdown)),
ActionType.SHUTDOWN_LISTENER_SHUTDOWN,
i.getClass().getName());The reason why I introduced the "abstraction layer" was to minimize the risk of copy&paste error, i.e. define the JFR "ceremony" in one place. But I agree that it's not nice. I will take a second look later this week. |
|
So I renamed |
|
Whatever floats your boat. BTW I still hate the way you changed |
It does float but I'm definitely not proud of it.
Hm, I don't think there's such a big difference... |
This comment has been minimized.
This comment has been minimized.
|
I verified with a couple of apps that the JFR events are ok. As for the pattern of how this is set up, I've seen in a couple of places in the JDK that the Event class itself has a singleton event that is used to check if it is enabled or not: Then, instead of having a It would be something like: This is, of course, nitpicking. We have all different types of uses of shouldCommit, etc., in the code base. Maybe this discussion would also serve as a rationalisation of all that. |
- emit JFR events so that shutdown actions can be observed with standard profiling tools - monitored actions include: ShutdownContext.addShutdownTask(), ShutdownContext.addLastShutdownTask(), ShutdownListener, CDI Singleton and ApplicationScoped beans, CDI Singleton and Application contexts
|
@Ladicek I've removed some "ugliness" from the @ozangunalp the JDK pattern makes sense for hot paths but for shutdown tracing it's unnecessary complexity. I'm not going to spend more time with this PR. If someone approves it, I will merge it. Honestly, I'm not very proud of this code but it could be helpful. |
|
I still don't like introducing abstraction around code that is supposed to have near-zero overhead, but I might be overly cautious here 🤷 |
This comment has been minimized.
This comment has been minimized.
manovotn
left a comment
There was a problem hiding this comment.
FWIW I think the PR is fine and ready to be merged.
It is certainly better than what we have right now (or rather, what we lack right now!).
I still don't like introducing abstraction around code that is supposed to have near-zero overhead, but I might be overly cautious here 🤷
I am not too worried there - this code path is executed once during shutdown which IMO makes it way less critical than it may initially appear.
Status for workflow
|
| Status | Name | Step | Failures | Logs | Raw logs | Build scan |
|---|---|---|---|---|---|---|
| ✔️ | JVM Tests - JDK 21 | Logs | Raw logs | 🚧 | ||
| ✔️ | JVM Tests - JDK 25 | Logs | Raw logs | 🚧 | ||
| ✔️ | JVM Tests - JDK 25 Semeru | Logs | Raw logs | 🚧 | ||
| ❌ | JVM Tests - JDK 21 Windows | Build |
Failures | Logs | Raw logs | 🚧 |
Failures
⚙️ JVM Tests - JDK 21 Windows #
- Failing: extensions/grpc/deployment
! Skipped: extensions/liquibase/liquibase-mongodb/deployment extensions/micrometer-opentelemetry/deployment extensions/micrometer-registry-prometheus/deployment and 11 more📦 extensions/grpc/deployment
❌ Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.6:test (default-test) on project quarkus-grpc-deployment: There was a timeout in the fork
Flaky tests - Develocity
⚙️ JVM Tests - JDK 25
📦 extensions/redis-client/runtime
❌ io.quarkus.redis.datasource.PubSubTest.testWithMultipleChannelsAndASinglePattern - History
io.smallrye.mutiny.TimeoutException
Details
io.smallrye.mutiny.TimeoutException
at io.smallrye.mutiny.operators.uni.UniBlockingAwait.await(UniBlockingAwait.java:65)
at io.smallrye.mutiny.groups.UniAwait.atMost(UniAwait.java:76)
at io.quarkus.redis.datasource.PubSubTest.testWithMultipleChannelsAndASinglePattern(PubSubTest.java:101)
⚙️ JVM Tests - JDK 25 Semeru
📦 extensions/aesh/websocket/deployment
❌ io.quarkus.aesh.websocket.deployment.AeshWebSocketConnectionTest.testWebSocketConnection - History
[Expected to receive 'Hello World!' in WebSocket output within 30s. Received: ] Expecting value to be true but was false-org.opentest4j.AssertionFailedError
Details
org.opentest4j.AssertionFailedError:
[Expected to receive 'Hello World!' in WebSocket output within 30s. Received: ]
Expecting value to be true but was false
at io.quarkus.aesh.websocket.deployment.AeshWebSocketConnectionTest.testWebSocketConnection(AeshWebSocketConnectionTest.java:67)
❌ io.quarkus.aesh.websocket.deployment.AeshWebSocketRolesAllowedTest.testCorrectRoleSucceeds - History
[Expected to receive 'Hello World!' in WebSocket output within 30s. Received: ] Expecting value to be true but was false-org.opentest4j.AssertionFailedError
Details
org.opentest4j.AssertionFailedError:
[Expected to receive 'Hello World!' in WebSocket output within 30s. Received: ]
Expecting value to be true but was false
at io.quarkus.aesh.websocket.deployment.AeshWebSocketRolesAllowedTest.testCorrectRoleSucceeds(AeshWebSocketRolesAllowedTest.java:109)
📦 extensions/redis-client/runtime
❌ io.quarkus.redis.datasource.PubSubCommandsTest.subscribeToSingleWithMulti - History
Assertion condition defined as a Lambda expression in io.quarkus.redis.datasource.PubSubCommandsTest Expecting empty but was: [channel-test] within 10 seconds.-org.awaitility.core.ConditionTimeoutException
Details
org.awaitility.core.ConditionTimeoutException:
Assertion condition defined as a Lambda expression in io.quarkus.redis.datasource.PubSubCommandsTest
Expecting empty but was: [channel-test] within 10 seconds.
at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:119)
at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:31)
at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
at org.awaitility.core.ConditionFactory.untilAsserted(ConditionFactory.java:790)
❌ io.quarkus.redis.datasource.PubSubCommandsTest.subscribeToSingleWithMultiAsMessages - History
Assertion condition defined as a Lambda expression in io.quarkus.redis.datasource.PubSubCommandsTest Expecting empty but was: [channel-test] within 10 seconds.-org.awaitility.core.ConditionTimeoutException
Details
org.awaitility.core.ConditionTimeoutException:
Assertion condition defined as a Lambda expression in io.quarkus.redis.datasource.PubSubCommandsTest
Expecting empty but was: [channel-test] within 10 seconds.
at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:119)
at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:31)
at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
at org.awaitility.core.ConditionFactory.untilAsserted(ConditionFactory.java:790)
Condition with Lambda expression in io.quarkus.redis.datasource.PubSubCommandsTest was not fulfilled within 10 seconds.-org.awaitility.core.ConditionTimeoutException
Details
org.awaitility.core.ConditionTimeoutException: Condition with Lambda expression in io.quarkus.redis.datasource.PubSubCommandsTest was not fulfilled within 10 seconds.
at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
at org.awaitility.core.CallableCondition.await(CallableCondition.java:78)
at org.awaitility.core.CallableCondition.await(CallableCondition.java:26)
at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1129)
at io.quarkus.redis.datasource.PubSubCommandsTest.subscribeToSingleWithMultiAsMessages(PubSubCommandsTest.java:354)
❌ io.quarkus.redis.datasource.PubSubCommandsTest.testSubscribeAsMessages - History
Condition with Lambda expression in io.quarkus.redis.datasource.PubSubCommandsTest was not fulfilled within 10 seconds.-org.awaitility.core.ConditionTimeoutException
Details
org.awaitility.core.ConditionTimeoutException: Condition with Lambda expression in io.quarkus.redis.datasource.PubSubCommandsTest was not fulfilled within 10 seconds.
at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
at org.awaitility.core.CallableCondition.await(CallableCondition.java:78)
at org.awaitility.core.CallableCondition.await(CallableCondition.java:26)
at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1129)
at io.quarkus.redis.datasource.PubSubCommandsTest.testSubscribeAsMessages(PubSubCommandsTest.java:383)
Suppressed: org.awaitility.core.ConditionTimeoutException: Assertion condition defined as a Lambda expression in io.quarkus.redis.datasource.PubSubCommandsTest
