Runtime Orchestration Migration
This guide covers the breaking migration from independent dispatcher launchers to the one-shot WowRuntime. It owns only the upgrade procedure. For the stable runtime model and shutdown semantics, see Runtime Lifecycle.
Event, snapshot, and message formats are unchanged. No data migration is required, but custom lifecycle integrations must be recompiled and migrated.
Migration at a Glance
| Previous integration | Required replacement | Source |
|---|---|---|
Independent MessageDispatcherLauncher instances | One WowRuntime owns all RuntimeComponent instances | WowRuntime.kt:47-76 |
| Generic Wow lifecycle implementation | RuntimeComponent for runtime-owned work, or GracefullyStoppable for independently owned shutdown only | RuntimeComponent.kt:18-62, GracefullyStoppable.kt:19-37 |
Constructor, @PostConstruct, or Spring destruction owns resources | Inert construction; acquire in prepare or start; release only through the runtime | RuntimeComponent.kt:18-33 |
| Subscription or demand implies readiness | Explicit MessageReceiver.readiness and processing admission callbacks | MessageReceiver.kt:20-89 |
| Per-dispatcher shutdown timeout | One runtime-wide deadline plus a stable quiet period | WowRuntime.kt:103-125 |
1. Replace Lifecycle Ownership
Remove:
MessageDispatcherLauncherbeans, factories, injections, and direct dispatcher lifecycle calls;- application-defined
WowRuntimeLifecyclebeans in Starter applications; - Spring
Lifecycle,SmartLifecycle,DisposableBean,@PreDestroy, and explicit destroy methods from runtime-owned component beans.
Dispatcher lifecycle methods are final templates. Recompile every subclass. Additional lifecycle ownership belongs in a separate RuntimeComponent, not in a dispatcher override. MainDispatcher retains only narrow cleanup hooks for framework implementations that own schedulers. MainDispatcher.kt:193-357
Non-Spring applications
Construct exactly one runtime. start() returns a cold Mono<Void> and must be subscribed.
val runtime = WowRuntime(components, shutdownTimeout, shutdownQuietPeriod)
runtime.start().block()
// application work
runtime.stop()Spring Boot applications
The Starter owns the canonical wowRuntimeLifecycle bridge. Runtime-owned components must be singleton beans whose declared return type exposes RuntimeComponent or a subtype. The runtime invokes the Spring-exposed proxy; there is no target unwrapping.
A custom runtime must be a direct singleton bean named wowRuntime, must be the only local WowRuntime, and must disable Spring's inferred AutoCloseable.close() owner:
@Bean(WOW_RUNTIME_BEAN_NAME, destroyMethod = "")
fun customWowRuntime(): WowRuntime =
WowRuntime(components, shutdownTimeout, shutdownQuietPeriod)A FactoryBean product is rejected because the Starter cannot prove exclusive destruction ownership. When the application provides the canonical runtime, that runtime owns its component topology; automatic discovery applies only to the Starter-created default runtime. A child ApplicationContext owns only its local components. WowAutoConfiguration.kt:118-213
If a custom ingress implements SmartLifecycle, use a phase greater than WOW_RUNTIME_PHASE: ingress then starts after runtime readiness and stops before the runtime. If the application replaces lifecycleProcessor with a DefaultLifecycleProcessor, Wow derives the runtime phase timeout from the selected runtime's shutdownTimeout and adds the configured completion margin. WowAutoConfiguration.kt:65-89
2. Migrate Custom Runtime Participants
Implement RuntimeComponent directly:
class CustomRuntimeComponent : RuntimeComponent {
override fun prepare(runtimeContext: RuntimeContext): Mono<Void> =
prepareResourcesWithoutOpeningProcessing(runtimeContext)
override fun start() = openIntake()
override fun quiesce() = closeIntake()
override fun stopGracefully(): Mono<Void> = drainAndClose()
override fun forceStop() {
closeIntake()
disposeOwnedResources()
}
}The lifecycle contract is strict:
preparecompletes when the component can retain admitted work without loss, while processing remains closed;startopens processing;quiescecloses intake promptly, synchronously, and idempotently;stopGracefullydrains accepted work;forceStopis prompt, non-blocking, repeat-safe, and safe before preparation.
Acquire a RuntimeActivity through RuntimeContext.tryAcquire() before accepting each complete asynchronous operation. Close it only when that complete chain terminates. Report terminal pipeline failures with reportFailure. RuntimeContext.kt:16-45
Cancelling the start() subscription aborts and force-stops this one-shot runtime. Preparation publishers must therefore release or terminate promptly when forceStop runs. A terminated runtime or Spring context cannot restart; create a new instance instead.
3. Migrate Message Sources
A custom MessageBus whose subscription is not immediately able to retain new work must override receiver and return:
- a single-use message stream;
- a hot, replayable readiness signal;
- an idempotent
openProcessingcallback; - an idempotent
closeProcessingcallback.
Preserve all four when mapping a receiver. The runtime subscribes before awaiting readiness, opens processing only during the global start pass, and closes logical processing before detached physical cancellation. MessageReceiver.kt:20-89
Transport-specific checks:
- Redis readiness creates all consumer groups without starting stream reads before processing admission.
AbstractRedisMessageBus.kt:76-146 - Kafka readiness persists a conservative assignment boundary before publishing readiness. Provision topics before runtime startup.
AbstractKafkaBus.kt:128-185AbstractKafkaBus.kt:260-273
For a custom LocalMessageBus, the default sendIfSubscribed deliberately returns false. Override it only when true proves that every targeted local receiver acquired processing admission; subscriberCount() plus send() is not an atomic receipt. Ordinary receiver() consumers need no receipt protocol. Runtime-owned custom consumers of the built-in in-memory bus opt in with runtimeReceiver() and then call confirmLocalDelivery() after admission and handoff, or rejectLocalDelivery() when they filter or cannot admit the exchange. MessageBus.kt:54-107LocalDeliveryReceipt.kt:252-273
4. Update Adjacent Extensions
- A custom
AggregateSchedulerSuppliermust implement bothstopGracefully()andforceStop(). Force stop synchronously disposes every scheduler that graceful shutdown could own.AggregateSchedulerSupplier.kt:40-65 AutoRegistraris initialization work and now implementsSmartInitializingSingleton. Remove lifecycle calls and references to the deletedAUTO_REGISTRAR_PHASE.AutoRegistrar.kt:20-44
5. Review Shutdown Configuration
wow.shutdown-timeoutis one deadline for quiescing and stopping the complete runtime, not an allowance per dispatcher.wow.shutdown-quiet-perioddefaults to1s, must be non-negative, strictly shorter than the timeout, and both durations must fit signed 64-bit nanoseconds.- Runtime termination can carry the original pipeline error. Unexpected fatal termination in a Starter application closes the application context.
The runtime closes global admission first, quiesces component intake, waits for a stable quiet period, and then stops components in reverse order. Deadline expiry or cleanup failure enters force stop. WowRuntime.kt:325-468WowProperties.kt:24-47
Deployment and Rollback
Before deployment:
- recompile every custom dispatcher and lifecycle extension;
- verify that each runtime-owned resource has exactly one owner;
- run application-context startup and graceful-shutdown tests with production timeout values;
- test fatal failure, startup cancellation, and deadline expiry;
- provision Kafka topics before starting the runtime.
Do not use a mixed lifecycle topology. To roll back, completely stop the new application context and deploy the previous binaries together with the previous launcher configuration. Do not restart a context whose runtime has terminated.
References
| Source | Responsibility |
|---|---|
WowRuntime.kt | One-shot orchestration, shared deadline, fatal shutdown |
RuntimeComponent.kt | Runtime-owned lifecycle contract |
RuntimeContext.kt | Activity admission and fatal failure reporting |
MessageReceiver.kt | Readiness and processing admission |
WowRuntimeLifecycle.kt | Spring lifecycle bridge |
WowAutoConfiguration.kt | Starter composition root and ownership validation |
Related Pages
| Page | Relationship |
|---|---|
| Migration Guide | Upgrade overview and other migration topics |
| Runtime Lifecycle | Stable runtime architecture and shutdown semantics |
| Configuration | Complete Spring Boot property reference |
| Spring Boot Starter | Starter auto-configuration and application integration |