Skip to content

Wait for workers to terminate when the Spring context closes - #3115

Open
AdzerKI wants to merge 2 commits into
temporalio:mainfrom
AdzerKI:spring-boot-graceful-worker-shutdown
Open

AdzerKI wants to merge 2 commits into
temporalio:mainfrom
AdzerKI:spring-boot-graceful-worker-shutdown

Conversation

@AdzerKI

@AdzerKI AdzerKI commented Oct 2, 2026 •

Copy link
Copy Markdown

What changed?

  • Added WorkerFactoryLifecycle, a SmartLifecycle that shuts down a started WorkerFactory when the context stops and waits for its workers to terminate. The wait is bounded by the standard spring.lifecycle.timeout-per-shutdown-phase.
  • It is not pauseable (isPauseable() returns false, Spring Framework 7): a shut down factory cannot be started again, so a paused context, such as a cached Spring TestContext one, keeps its workers running.
  • The root namespace registers it as the temporalWorkerFactoryLifecycle bean, and each non-root namespace registers its own one next to its WorkerFactory.
  • Removed the ContextClosedEvent branch from NonRootNamespaceEventListener. It shut down the non-root factories only when some other context closed, and did not wait.
  • Added GracefulShutdownTest: an activity is still running when the context is closed, and the test checks that it has completed and the factory has terminated once close() returns.

Why?

The root WorkerFactory is shut down by its destroyMethod without waiting, and temporalWorkflowServiceStubs is destroyed right after it. Pollers and in-flight activity completions then fail with UNAVAILABLE: Channel shutdown invoked, and the running activity is cut off. Stopping the factory in the lifecycle phase lets in-flight tasks finish before any bean is destroyed.

Closes #2026. It uses the existing Spring Boot shutdown timeout property instead of adding a new one.

Breaking changes

None. Context close now waits for in-flight tasks, up to spring.lifecycle.timeout-per-shutdown-phase (30s by default).

Server PR

N/A.

Testing

./gradlew :temporal-spring-boot-autoconfigure:spotlessCheck :temporal-spring-boot-autoconfigure:test
./gradlew :temporal-spring-boot-autoconfigure:test -P edgeDepsTest -P springBoot3Test
./gradlew :temporal-spring-boot-autoconfigure:test -P edgeDepsTest -P springBoot4Test

GracefulShutdownTest fails on current main (the activity has not completed when close() returns) and passes with the change. The full module passes on Spring Boot 2, 3 and 4.

testWorkersKeepRunningWhenContextIsPausedAndRestarted pauses and restarts the context and then runs a workflow. On Spring Boot 4 it times out without isPauseable() (the restarted context has no pollers) and passes with it; on Spring Boot 2 and 3 it is skipped, as context pausing comes with Spring Framework 7. GracefulShutdownTest was run on Spring Boot 2 and 4 for this commit.

The root namespace worker factory was shut down by its destroy method
without waiting, and the service stubs were closed right after it, so
pollers and in-flight activity completions failed with
UNAVAILABLE: Channel shutdown invoked. Non-root namespace factories were
shut down only when another context closed.

Stop every worker factory from a SmartLifecycle that waits for
termination within spring.lifecycle.timeout-per-shutdown-phase, before
the beans are destroyed.
@AdzerKI
AdzerKI requested a review from a team as a code owner October 2, 2026 05:10
A shut down WorkerFactory cannot be started again. Spring Framework 7 pauses
a cached test context when another one is started and restarts it when a test
reuses it, so the stopped factory left the reused context without pollers and
its workflows never progressed.

This branch has not been deployed

No deployments
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.

[SpringBoot] Add graceful shutdown wait time as property

1 participant