From aeb1919df077180cafcdc5338b07fc3da2ffd466 Mon Sep 17 00:00:00 2001 From: ReubenBond Date: Tue, 13 Feb 2018 21:22:09 +1100 Subject: [PATCH 1/3] Support a modern analogue to bootstrap providers in the form of IStartupTask --- src/Orleans.Core/Runtime/Constants.cs | 1 + src/Orleans.Runtime/Lifecycle/IStartupTask.cs | 18 ++ .../Lifecycle/SiloBuilderStartupExtensions.cs | 168 ++++++++++++++++++ src/Orleans.Runtime/Silo/Silo.cs | 1 - test/Tester/StartupTaskTests.cs | 82 +++++++++ 5 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 src/Orleans.Runtime/Lifecycle/IStartupTask.cs create mode 100644 src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs create mode 100644 test/Tester/StartupTaskTests.cs diff --git a/src/Orleans.Core/Runtime/Constants.cs b/src/Orleans.Core/Runtime/Constants.cs index df8ae2ac671..3c3e717ae3a 100644 --- a/src/Orleans.Core/Runtime/Constants.cs +++ b/src/Orleans.Core/Runtime/Constants.cs @@ -37,6 +37,7 @@ internal class Constants public static readonly GrainId MembershipOracleId = GrainId.GetSystemTargetGrainId(15); public static readonly GrainId TypeManagerId = GrainId.GetSystemTargetGrainId(17); public static readonly GrainId FallbackSystemTargetId = GrainId.GetSystemTargetGrainId(19); + public static readonly GrainId StartupTaskSystemTargetId = GrainId.GetSystemTargetGrainId(20); public static readonly GrainId DeploymentLoadPublisherSystemTargetId = GrainId.GetSystemTargetGrainId(22); public static readonly GrainId MultiClusterOracleId = GrainId.GetSystemTargetGrainId(23); public static readonly GrainId ClusterDirectoryServiceId = GrainId.GetSystemTargetGrainId(24); diff --git a/src/Orleans.Runtime/Lifecycle/IStartupTask.cs b/src/Orleans.Runtime/Lifecycle/IStartupTask.cs new file mode 100644 index 00000000000..c25d48e2d80 --- /dev/null +++ b/src/Orleans.Runtime/Lifecycle/IStartupTask.cs @@ -0,0 +1,18 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Orleans.Runtime +{ + /// + /// Defines an action to be taken during silo startup. + /// + public interface IStartupTask + { + /// + /// Called after the silo has started. + /// + /// The cancellation token which is canceled when the method must abort. + /// A representing the work performed. + Task OnStarted(CancellationToken cancellationToken); + } +} diff --git a/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs b/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs new file mode 100644 index 00000000000..9b338c271bd --- /dev/null +++ b/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; + +using Orleans.Hosting; +using Orleans.Runtime; +using Orleans.Runtime.Providers; +using Orleans.Runtime.Scheduler; +using Orleans.Runtime.Startup; + +namespace Orleans.Hosting +{ + /// + /// The silo builder startup extensions. + /// + public static class SiloBuilderStartupExtensions + { + /// + /// Adds a startup task to be executed when the silo has started. + /// + /// + /// The builder. + /// + /// + /// The startup task type. + /// + /// + /// The provided . + /// + public static ISiloHostBuilder AddStartupTask(this ISiloHostBuilder builder) + where TStartup : class, IStartupTask + { + EnsureStartupAdded(builder); + builder.ConfigureServices(services => services.AddSingleton()); + return builder; + } + + /// + /// Adds a startup task to be executed when the silo has started. + /// + /// + /// The builder. + /// + /// + /// The startup task. + /// + /// + /// The provided . + /// + public static ISiloHostBuilder AddStartupTask(this ISiloHostBuilder builder, IStartupTask startupTask) + { + EnsureStartupAdded(builder); + builder.ConfigureServices(services => services.AddSingleton(startupTask)); + return builder; + } + + /// + /// Adds a startup task to be executed when the silo has started. + /// + /// + /// The builder. + /// + /// + /// The startup task. + /// + /// + /// The provided . + /// + public static ISiloHostBuilder AddStartupTask(this ISiloHostBuilder builder, Func startupTask) + { + EnsureStartupAdded(builder); + builder.ConfigureServices(services => services.AddSingleton(sp => new DelegateStartupTask(sp, startupTask))); + return builder; + } + + private static void EnsureStartupAdded(ISiloHostBuilder builder) + { + if (!builder.Properties.TryGetValue(typeof(StartupTask), out var _)) + { + builder.ConfigureServices( + services => + { + services.AddSingleton, StartupTask>(); + services.TryAddSingleton(); + }); + builder.Properties[typeof(StartupTask)] = true; + } + } + + private class StartupTask : ILifecycleParticipant + { + private readonly IEnumerable startupTasks; + + private readonly OrleansTaskScheduler scheduler; + + private readonly SiloProviderRuntime siloProviderRuntime; + + private readonly StartupTaskSystemTarget schedulingTarget; + + public StartupTask( + IEnumerable startupTasks, + OrleansTaskScheduler scheduler, + StartupTaskSystemTarget schedulingTarget, + SiloProviderRuntime siloProviderRuntime) + { + this.startupTasks = startupTasks; + this.scheduler = scheduler; + this.siloProviderRuntime = siloProviderRuntime; + this.schedulingTarget = schedulingTarget; + } + + public void Participate(ISiloLifecycle lifecycle) + { + lifecycle.Subscribe(SiloLifecycleStage.RuntimeServices, this.RegisterSystemTarget); + lifecycle.Subscribe(SiloLifecycleStage.SiloActive, this.OnStarted); + } + + private Task RegisterSystemTarget(CancellationToken cancellationToken) + { + // Register the target used for scheduling startup tasks. + this.siloProviderRuntime.RegisterSystemTarget(this.schedulingTarget); + return Task.CompletedTask; + } + + private Task OnStarted(CancellationToken cancellationToken) + { + return this.scheduler.QueueTask( + async () => + { + foreach (var task in this.startupTasks) + { + await task.OnStarted(cancellationToken); + } + }, + this.schedulingTarget.SchedulingContext); + } + } + + // A dummy system target for scheduling startup tasks. + internal class StartupTaskSystemTarget : SystemTarget + { + public StartupTaskSystemTarget(ILocalSiloDetails localSiloDetails, ILoggerFactory loggerFactory) + : base(Constants.StartupTaskSystemTargetId, localSiloDetails.SiloAddress, loggerFactory) + { + } + } + + private class DelegateStartupTask : IStartupTask + { + private readonly IServiceProvider serviceProvider; + + private readonly Func startupTask; + + public DelegateStartupTask(IServiceProvider serviceProvider, Func startupTask) + { + this.serviceProvider = serviceProvider; + this.startupTask = startupTask; + } + + public Task OnStarted(CancellationToken cancellationToken) => this.startupTask(this.serviceProvider, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Orleans.Runtime/Silo/Silo.cs b/src/Orleans.Runtime/Silo/Silo.cs index 22e3d574d60..a21d1fe5b8f 100644 --- a/src/Orleans.Runtime/Silo/Silo.cs +++ b/src/Orleans.Runtime/Silo/Silo.cs @@ -361,7 +361,6 @@ await scheduler.QueueAction(catalog.Start, catalog.SchedulingContext) // SystemTarget for provider init calls this.fallbackScheduler = Services.GetRequiredService(); - RegisterSystemTarget(fallbackScheduler); } diff --git a/test/Tester/StartupTaskTests.cs b/test/Tester/StartupTaskTests.cs new file mode 100644 index 00000000000..6c08602168b --- /dev/null +++ b/test/Tester/StartupTaskTests.cs @@ -0,0 +1,82 @@ +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Extensions.DependencyInjection; + +using Orleans; +using Orleans.Hosting; +using Orleans.Runtime; +using Orleans.TestingHost; + +using TestExtensions; + +using UnitTests.GrainInterfaces; + +using Xunit; + +namespace DefaultCluster.Tests +{ + [TestCategory("BVT")] + public class StartupTaskTests : IClassFixture + { + public class Fixture : BaseTestClusterFixture + { + protected override void ConfigureTestCluster(TestClusterBuilder builder) + { + builder.ConfigureHostConfiguration(TestDefaultConfiguration.ConfigureHostConfiguration); + builder.AddSiloBuilderConfigurator(); + } + + private class StartupTaskSiloConfigurator : ISiloBuilderConfigurator + { + public void Configure(ISiloHostBuilder hostBuilder) + { + hostBuilder.AddStartupTask(); + hostBuilder.AddStartupTask( + async (services, cancellation) => + { + var grainFactory = services.GetRequiredService(); + var grain = grainFactory.GetGrain(98052); + + // Modify "A" to validate ordering of events. + await grain.SetA(await grain.GetA() * 2); + }); + } + } + } + + public class CallGrainStartupTask : IStartupTask + { + private readonly IGrainFactory grainFactory; + + public CallGrainStartupTask(IGrainFactory grainFactory) + { + this.grainFactory = grainFactory; + } + + public async Task OnStarted(CancellationToken cancellationToken) + { + var grain = this.grainFactory.GetGrain(98052); + await grain.SetA(1331); + } + } + + private readonly Fixture fixture; + + public StartupTaskTests(Fixture fixture) + { + this.fixture = fixture; + } + + /// + /// Ensures that startup tasks can call grains and are executed in the registered order. + /// + [Fact] + public async Task StartupTaskCanCallGrains() + { + var grain = this.fixture.GrainFactory.GetGrain(98052); + var value = await grain.GetA(); + Assert.Equal(2662, value); + } + } +} From 3cdcc60f2aa739cab61e2b6a5ea76644590704b2 Mon Sep 17 00:00:00 2001 From: Sergey Bykov Date: Tue, 13 Feb 2018 09:50:16 -0800 Subject: [PATCH 2/3] Rename IStartupTask.OnStarted() to IStartupTask.Execute() --- src/Orleans.Runtime/Lifecycle/IStartupTask.cs | 2 +- src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs | 4 ++-- test/Tester/StartupTaskTests.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Orleans.Runtime/Lifecycle/IStartupTask.cs b/src/Orleans.Runtime/Lifecycle/IStartupTask.cs index c25d48e2d80..ebd1f50f037 100644 --- a/src/Orleans.Runtime/Lifecycle/IStartupTask.cs +++ b/src/Orleans.Runtime/Lifecycle/IStartupTask.cs @@ -13,6 +13,6 @@ public interface IStartupTask /// /// The cancellation token which is canceled when the method must abort. /// A representing the work performed. - Task OnStarted(CancellationToken cancellationToken); + Task Execute(CancellationToken cancellationToken); } } diff --git a/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs b/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs index 9b338c271bd..b69a5cbd24d 100644 --- a/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs +++ b/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs @@ -134,7 +134,7 @@ private Task OnStarted(CancellationToken cancellationToken) { foreach (var task in this.startupTasks) { - await task.OnStarted(cancellationToken); + await task.Execute(cancellationToken); } }, this.schedulingTarget.SchedulingContext); @@ -162,7 +162,7 @@ public DelegateStartupTask(IServiceProvider serviceProvider, Func this.startupTask(this.serviceProvider, cancellationToken); + public Task Execute(CancellationToken cancellationToken) => this.startupTask(this.serviceProvider, cancellationToken); } } } \ No newline at end of file diff --git a/test/Tester/StartupTaskTests.cs b/test/Tester/StartupTaskTests.cs index 6c08602168b..adeba93cd51 100644 --- a/test/Tester/StartupTaskTests.cs +++ b/test/Tester/StartupTaskTests.cs @@ -54,7 +54,7 @@ public CallGrainStartupTask(IGrainFactory grainFactory) this.grainFactory = grainFactory; } - public async Task OnStarted(CancellationToken cancellationToken) + public async Task Execute(CancellationToken cancellationToken) { var grain = this.grainFactory.GetGrain(98052); await grain.SetA(1331); From 5ba13efe84fffb7b908835233c9bd47c2dc59e1e Mon Sep 17 00:00:00 2001 From: ReubenBond Date: Wed, 14 Feb 2018 12:07:28 +1100 Subject: [PATCH 3/3] Review feedback --- .../Hosting/DefaultSiloServices.cs | 3 + .../Lifecycle/SiloBuilderStartupExtensions.cs | 133 +++++++----------- src/Orleans.Runtime/Silo/Silo.cs | 13 ++ test/Tester/StartupTaskTests.cs | 2 +- 4 files changed, 64 insertions(+), 87 deletions(-) diff --git a/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs b/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs index 802dd7828d9..458b2d326b7 100644 --- a/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs +++ b/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs @@ -260,6 +260,9 @@ internal static void AddDefaultServices(HostBuilderContext context, IServiceColl services.TryConfigureFormatter(); services.AddTransient(); + + services.AddSingleton(); + } } } \ No newline at end of file diff --git a/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs b/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs index b69a5cbd24d..2d2b0233c7c 100644 --- a/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs +++ b/src/Orleans.Runtime/Lifecycle/SiloBuilderStartupExtensions.cs @@ -1,17 +1,11 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using Orleans.Hosting; using Orleans.Runtime; -using Orleans.Runtime.Providers; using Orleans.Runtime.Scheduler; -using Orleans.Runtime.Startup; namespace Orleans.Hosting { @@ -26,18 +20,21 @@ public static class SiloBuilderStartupExtensions /// /// The builder. /// + /// + /// The stage to execute the startup task, see values in . + /// /// /// The startup task type. /// /// /// The provided . /// - public static ISiloHostBuilder AddStartupTask(this ISiloHostBuilder builder) + public static ISiloHostBuilder AddStartupTask( + this ISiloHostBuilder builder, + int stage = SiloLifecycleStage.SiloActive) where TStartup : class, IStartupTask { - EnsureStartupAdded(builder); - builder.ConfigureServices(services => services.AddSingleton()); - return builder; + return builder.AddStartupTask((sp, ct) => ActivatorUtilities.GetServiceOrCreateInstance(sp).Execute(ct), stage); } /// @@ -49,14 +46,18 @@ public static ISiloHostBuilder AddStartupTask(this ISiloHostBuilder bu /// /// The startup task. /// + /// + /// The stage to execute the startup task, see values in . + /// /// /// The provided . /// - public static ISiloHostBuilder AddStartupTask(this ISiloHostBuilder builder, IStartupTask startupTask) + public static ISiloHostBuilder AddStartupTask( + this ISiloHostBuilder builder, + IStartupTask startupTask, + int stage = SiloLifecycleStage.SiloActive) { - EnsureStartupAdded(builder); - builder.ConfigureServices(services => services.AddSingleton(startupTask)); - return builder; + return builder.AddStartupTask((sp, ct) => startupTask.Execute(ct), stage); } /// @@ -68,101 +69,61 @@ public static ISiloHostBuilder AddStartupTask(this ISiloHostBuilder builder, ISt /// /// The startup task. /// + /// + /// The stage to execute the startup task, see values in . + /// /// /// The provided . /// - public static ISiloHostBuilder AddStartupTask(this ISiloHostBuilder builder, Func startupTask) + public static ISiloHostBuilder AddStartupTask( + this ISiloHostBuilder builder, + Func startupTask, + int stage = SiloLifecycleStage.SiloActive) { - EnsureStartupAdded(builder); - builder.ConfigureServices(services => services.AddSingleton(sp => new DelegateStartupTask(sp, startupTask))); + builder.ConfigureServices(services => + services.AddTransient>(sp => + new StartupTask( + sp, + sp.GetRequiredService(), + sp.GetRequiredService(), + startupTask, + stage))); return builder; } - private static void EnsureStartupAdded(ISiloHostBuilder builder) - { - if (!builder.Properties.TryGetValue(typeof(StartupTask), out var _)) - { - builder.ConfigureServices( - services => - { - services.AddSingleton, StartupTask>(); - services.TryAddSingleton(); - }); - builder.Properties[typeof(StartupTask)] = true; - } - } - + /// private class StartupTask : ILifecycleParticipant { - private readonly IEnumerable startupTasks; - + private readonly IServiceProvider serviceProvider; private readonly OrleansTaskScheduler scheduler; - - private readonly SiloProviderRuntime siloProviderRuntime; - private readonly StartupTaskSystemTarget schedulingTarget; + private readonly Func startupTask; + + private readonly int stage; public StartupTask( - IEnumerable startupTasks, + IServiceProvider serviceProvider, OrleansTaskScheduler scheduler, StartupTaskSystemTarget schedulingTarget, - SiloProviderRuntime siloProviderRuntime) + Func startupTask, + int stage) { - this.startupTasks = startupTasks; + this.serviceProvider = serviceProvider; this.scheduler = scheduler; - this.siloProviderRuntime = siloProviderRuntime; this.schedulingTarget = schedulingTarget; + this.startupTask = startupTask; + this.stage = stage; } + /// public void Participate(ISiloLifecycle lifecycle) { - lifecycle.Subscribe(SiloLifecycleStage.RuntimeServices, this.RegisterSystemTarget); - lifecycle.Subscribe(SiloLifecycleStage.SiloActive, this.OnStarted); - } - - private Task RegisterSystemTarget(CancellationToken cancellationToken) - { - // Register the target used for scheduling startup tasks. - this.siloProviderRuntime.RegisterSystemTarget(this.schedulingTarget); - return Task.CompletedTask; + lifecycle.Subscribe( + this.stage, + cancellation => this.scheduler.QueueTask( + () => this.startupTask(this.serviceProvider, cancellation), + this.schedulingTarget.SchedulingContext)); } - - private Task OnStarted(CancellationToken cancellationToken) - { - return this.scheduler.QueueTask( - async () => - { - foreach (var task in this.startupTasks) - { - await task.Execute(cancellationToken); - } - }, - this.schedulingTarget.SchedulingContext); - } - } - - // A dummy system target for scheduling startup tasks. - internal class StartupTaskSystemTarget : SystemTarget - { - public StartupTaskSystemTarget(ILocalSiloDetails localSiloDetails, ILoggerFactory loggerFactory) - : base(Constants.StartupTaskSystemTargetId, localSiloDetails.SiloAddress, loggerFactory) - { - } - } - - private class DelegateStartupTask : IStartupTask - { - private readonly IServiceProvider serviceProvider; - - private readonly Func startupTask; - - public DelegateStartupTask(IServiceProvider serviceProvider, Func startupTask) - { - this.serviceProvider = serviceProvider; - this.startupTask = startupTask; - } - - public Task Execute(CancellationToken cancellationToken) => this.startupTask(this.serviceProvider, cancellationToken); } } } \ No newline at end of file diff --git a/src/Orleans.Runtime/Silo/Silo.cs b/src/Orleans.Runtime/Silo/Silo.cs index a21d1fe5b8f..9b369634835 100644 --- a/src/Orleans.Runtime/Silo/Silo.cs +++ b/src/Orleans.Runtime/Silo/Silo.cs @@ -362,6 +362,10 @@ await scheduler.QueueAction(catalog.Start, catalog.SchedulingContext) // SystemTarget for provider init calls this.fallbackScheduler = Services.GetRequiredService(); RegisterSystemTarget(fallbackScheduler); + + // SystemTarget for startup tasks + var startupTaskTarget = Services.GetRequiredService(); + RegisterSystemTarget(startupTaskTarget); } private Task OnRuntimeInitializeStart(CancellationToken ct) @@ -868,5 +872,14 @@ public FallbackSystemTarget(ILocalSiloDetails localSiloDetails, ILoggerFactory l { } } + + // A dummy system target for scheduling startup tasks. + internal class StartupTaskSystemTarget : SystemTarget + { + public StartupTaskSystemTarget(ILocalSiloDetails localSiloDetails, ILoggerFactory loggerFactory) + : base(Constants.StartupTaskSystemTargetId, localSiloDetails.SiloAddress, loggerFactory) + { + } + } } diff --git a/test/Tester/StartupTaskTests.cs b/test/Tester/StartupTaskTests.cs index adeba93cd51..a101465c81b 100644 --- a/test/Tester/StartupTaskTests.cs +++ b/test/Tester/StartupTaskTests.cs @@ -16,7 +16,7 @@ namespace DefaultCluster.Tests { - [TestCategory("BVT")] + [TestCategory("BVT"), TestCategory("Lifecycle")] public class StartupTaskTests : IClassFixture { public class Fixture : BaseTestClusterFixture