Skip to content

Lifecycle implementation

Orleans composes many independently registered services into one silo or client. Some services must start only after a dependency is ready and stop before that dependency disappears. The lifecycle abstraction gives those components an ordered protocol without placing every dependency in a central start method.

ILifecycleObservable accepts subscriptions at integer stages. During startup it visits stages in ascending order; during shutdown it visits them in descending order. Every observer at a stage completes before the lifecycle advances.

ILifecycleObserver provides asynchronous OnStart and OnStop callbacks. ILifecycleParticipant<T> is the enrollment contract: a participant receives the lifecycle and subscribes itself. Silo and client hosting discover their registered participants through dependency injection; grain-scoped services have an explicit enrollment owner.

Rendering diagram.

The reverse shutdown order is the key invariant. A service can continue using dependencies which started at earlier stages until its own stop callback completes.

SiloLifecycleSubject drives silo startup and shutdown. Membership, messaging, grain directory, activation collection, statistics, providers, and other runtime components participate at named ServiceLifecycleStage values.

The client uses the same pattern for gateway discovery, connections, stream providers, and the outside runtime client. The generic ILifecycleObservable also lets providers compose a private lifecycle when the silo-specific interface is unnecessary.

The host-facing stage list and configuration examples are documented in silo lifecycle. This page focuses on the protocol rather than where application startup code should be registered.

IConfigureGrainTypeComponents configures a shared plan for a grain type. A configurator can use GrainClassMap to select implementation classes, then add synchronous actions with AddActivationSetup. Selection and composition occur when creating the shared context; activations execute the cached actions.

GrainTypeSharedContext seals setup registration after the configurators finish. When constructing an activation, the runtime assigns the fully constructed grain object and records creation before invoking setup actions in addition order. It then enrolls the grain object and starts the lifecycle. This boundary also applies to custom grain activators and to individual stateless workers.

Setup delegates are shared and can run concurrently across activations. Resolve per-activation state through ActivationServices inside the action. Existing DI registrations retain their explicit enrollment paths. Setup failures follow activation-construction failure handling: subsequent setup and lifecycle startup are skipped, and the assigned grain and scope are disposed.

See shared activation setup for a compiled interface-selection example.

A lifecycle participant should:

  • subscribe during composition, before lifecycle start;
  • use a stable observer name for diagnostics;
  • select the latest stage whose prerequisites are guaranteed;
  • make OnStop safe after a partial OnStart;
  • honor the supplied cancellation token; and
  • release dependencies before their lower stage stops.

Avoid creating hidden ordering by resolving and starting another participant manually. Stage dependencies should remain visible in lifecycle subscriptions.

Startup does not skip a failed observer and continue to a success-shaped state. The exception aborts lifecycle progress and the host reports startup failure. Shutdown attempts to unwind initialized stages under its cancellation deadline.

Cancellation bounds lifecycle observation; it cannot guarantee instantaneous cleanup of external resources. Provider code should keep shutdown idempotent and should not swallow failures which leave durable ownership ambiguous.