Edit

Share via


Orleans silo lifecycle overview

Orleans silos use an observable lifecycle for the ordered startup and shutdown of Orleans systems and application layer components. For more information on the implementation details, see Orleans lifecycle.

Stages

Orleans silo and cluster clients use a common set of service lifecycle stages:

public static class ServiceLifecycleStage
{
    public const int First = int.MinValue;
    public const int RuntimeInitialize = 2_000;
    public const int RuntimeServices = 4_000;
    public const int RuntimeStorageServices = 6_000;
    public const int RuntimeGrainServices = 8_000;
    public const int ApplicationServices = 10_000;
    public const int BecomeActive = Active - 1;
    public const int Active = 20_000;
    public const int Last = int.MaxValue;
}

Logging

Due to the inversion of control, where participants join the lifecycle rather than the lifecycle having a centralized set of initialization steps, it's not always clear from the code what the startup/shutdown order is. To help address this, Orleans adds logging before silo startup to report which components participate at each stage. These logs are recorded at the Information log level on the Orleans.Runtime.SiloLifecycleSubject logger. For instance:

Information, Orleans.Runtime.SiloLifecycleSubject, "Stage 2000: Orleans.Statistics.PerfCounterEnvironmentStatistics, Orleans.Runtime.InsideRuntimeClient, Orleans.Runtime.Silo"

Information, Orleans.Runtime.SiloLifecycleSubject, "Stage 4000: Orleans.Runtime.Silo"

Information, Orleans.Runtime.SiloLifecycleSubject, "Stage 10000: Orleans.Runtime.Versions.GrainVersionStore, Orleans.Storage.AzureTableGrainStorage-Default, Orleans.Storage.AzureTableGrainStorage-PubSubStore"

Additionally, Orleans similarly logs timing and error information for each component by stage. For instance:

Information, Orleans.Runtime.SiloLifecycleSubject, "Lifecycle observer Orleans.Runtime.InsideRuntimeClient started in stage 2000 which took 33 Milliseconds."

Information, Orleans.Runtime.SiloLifecycleSubject, "Lifecycle observer Orleans.Statistics.PerfCounterEnvironmentStatistics started in stage 2000 which took 17 Milliseconds."

Silo lifecycle participation

Your application logic can participate in the silo's lifecycle by registering a participating service in the silo's service container. Register the service as an ILifecycleParticipant<TLifecycleObservable>, where T is ISiloLifecycle.

public interface ISiloLifecycle : ILifecycleObservable
{
}

public interface ILifecycleParticipant<TLifecycleObservable>
    where TLifecycleObservable : ILifecycleObservable
{
    void Participate(TLifecycleObservable lifecycle);
}

When the silo starts, all participants (ILifecycleParticipant<ISiloLifecycle>) in the container can participate by having their ILifecycleParticipant<TLifecycleObservable>.Participate behavior called. Once all have had the opportunity to participate, the silo's observable lifecycle starts all stages in order.

Example

With the introduction of the silo lifecycle, bootstrap providers, which previously allowed you to inject logic at the provider initialization phase, are no longer necessary. You can now inject application logic at any stage of silo startup. Nonetheless, we added a 'startup task' facade to aid the transition for developers who used bootstrap providers. As an example of how you can develop components that participate in the silo's lifecycle, let's look at the startup task facade.

The startup task only needs to inherit from ILifecycleParticipant<ISiloLifecycle> and subscribe the application logic to the silo lifecycle at the specified stage.

class StartupTask : ILifecycleParticipant<ISiloLifecycle>
{
    private readonly IServiceProvider _serviceProvider;
    private readonly Func<IServiceProvider, CancellationToken, Task> _startupTask;
    private readonly int _stage;

    public StartupTask(
        IServiceProvider serviceProvider,
        Func<IServiceProvider, CancellationToken, Task> startupTask,
        int stage)
    {
        _serviceProvider = serviceProvider;
        _startupTask = startupTask;
        _stage = stage;
    }

    public void Participate(ISiloLifecycle lifecycle)
    {
        lifecycle.Subscribe<StartupTask>(
            _stage,
            cancellation => _startupTask(_serviceProvider, cancellation));
    }
}

From the preceding implementation, you can see that in the Participate(...) call, it subscribes to the silo lifecycle at the configured stage, passing the application callback rather than its initialization logic. Components needing initialization at a given stage would provide their callback, but the pattern remains the same. Now that you have a StartupTask ensuring the application's hook is called at the configured stage, you need to ensure the StartupTask participates in the silo lifecycle.

For this, you only need to register it in the container. Do this using an extension function on ISiloHostBuilder:

public static ISiloHostBuilder AddStartupTask(
    this ISiloHostBuilder builder,
    Func<IServiceProvider, CancellationToken, Task> startupTask,
    int stage = ServiceLifecycleStage.Active)
{
    builder.ConfigureServices(services =>
        services.AddTransient<ILifecycleParticipant<ISiloLifecycle>>(
            serviceProvider =>
                new StartupTask(
                    serviceProvider, startupTask, stage)));

    return builder;
}

By registering the StartupTask in the silo's service container as the marker interface ILifecycleParticipant<ISiloLifecycle>, you signal to the silo that this component needs to participate in the silo lifecycle.