Skip to content

Commit 8b6775b

Browse files
committed
Validate IReminderTable during startup if any grains implement IRemindable
1 parent bbca08f commit 8b6775b

File tree

3 files changed

+114
-1
lines changed

3 files changed

+114
-1
lines changed

src/Orleans.Runtime/Hosting/DefaultSiloServices.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939
using Orleans.Configuration.Validators;
4040
using Orleans.Runtime.Configuration;
41+
using System.Linq;
4142

4243
namespace Orleans.Hosting
4344
{
@@ -92,7 +93,6 @@ internal static void AddDefaultServices(HostBuilderContext context, IServiceColl
9293

9394
services.AddLogging();
9495
services.TryAddSingleton<ITimerRegistry, TimerRegistry>();
95-
services.TryAddSingleton<IReminderRegistry, ReminderRegistry>();
9696
services.TryAddSingleton<GrainRuntime>();
9797
services.TryAddSingleton<IGrainRuntime, GrainRuntime>();
9898
services.TryAddSingleton<IGrainCancellationTokenRuntime, GrainCancellationTokenRuntime>();
@@ -143,6 +143,15 @@ internal static void AddDefaultServices(HostBuilderContext context, IServiceColl
143143
services.TryAddSingleton<Factory<Grain, IMultiClusterRegistrationStrategy, ILogConsistencyProtocolServices>>(FactoryUtility.Create<Grain, IMultiClusterRegistrationStrategy, ProtocolServices>);
144144
services.TryAddSingleton(FactoryUtility.Create<GrainDirectoryPartition>);
145145

146+
// Reminders
147+
services.TryAddSingleton<IReminderRegistry, ReminderRegistry>();
148+
if (services.LastOrDefault()?.ServiceType == typeof(IReminderRegistry))
149+
{
150+
// If the user has not overridden the reminder registry, also add a configuration validator
151+
// to ensure that a reminder table is configured if any grains require reminders.
152+
services.AddSingleton<IConfigurationValidator, ReminderTableConfigurationValidator>();
153+
}
154+
146155
// Placement
147156
services.AddSingleton<IConfigurationValidator, ActivationCountBasedPlacementOptionsValidator>();
148157
services.TryAddSingleton<PlacementDirectorsManager>();
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
using Microsoft.Extensions.DependencyInjection;
7+
8+
namespace Orleans.Runtime.ReminderService
9+
{
10+
internal class ReminderTableConfigurationValidator : IConfigurationValidator
11+
{
12+
internal const string ReminderServiceNotConfigured =
13+
"The reminder service has not been configured. Reminders can be configured using extension methods from the following packages:"
14+
+ "\n * Microsoft.Orleans.Reminders.AzureStorage via ISiloHostBuilder.UseAzureTableReminderService(...)"
15+
+ "\n * Microsoft.Orleans.Reminders.AdoNet via ISiloHostBuilder.UseAdoNetReminderService(...)"
16+
+ "\n * Microsoft.Orleans.Reminders.DynamoDB via via ISiloHostBuilder.UseDynamoDBReminderService(...)"
17+
+ "\n * Microsoft.Orleans.OrleansRuntime via ISiloHostBuilder.UseInMemoryReminderService(...) (Note: for development purposes only)"
18+
+ "\n * Others, see: https://www.nuget.org/packages?q=Microsoft.Orleans.Reminders.";
19+
private readonly GrainTypeManager grainTypeManager;
20+
private readonly IServiceProvider serviceProvider;
21+
22+
public ReminderTableConfigurationValidator(GrainTypeManager grainTypeManager, IServiceProvider serviceProvider)
23+
{
24+
this.grainTypeManager = grainTypeManager;
25+
this.serviceProvider = serviceProvider;
26+
}
27+
28+
public void ValidateConfiguration()
29+
{
30+
var reminderTable = this.serviceProvider.GetService<IReminderTable>();
31+
if (reminderTable != null) return;
32+
33+
var allGrains = this.grainTypeManager.GrainClassTypeData.Select(data => data.Value);
34+
35+
var remindableGrains = new List<Type>();
36+
foreach (var grain in allGrains)
37+
{
38+
if (typeof(IRemindable).IsAssignableFrom(grain.Type))
39+
{
40+
remindableGrains.Add(grain.Type);
41+
}
42+
}
43+
44+
if (remindableGrains.Count > 0)
45+
{
46+
var message = new StringBuilder(ReminderServiceNotConfigured);
47+
message.AppendLine("\nThe following grain classes require reminders:");
48+
49+
foreach (var grain in remindableGrains)
50+
{
51+
message.AppendLine($" * {grain.GetParseableName(TypeFormattingOptions.LogFormat)}");
52+
}
53+
54+
throw new OrleansConfigurationException(message.ToString());
55+
}
56+
}
57+
}
58+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Orleans.ApplicationParts;
2+
using Orleans.Hosting;
3+
using Orleans.Metadata;
4+
using Orleans.Runtime;
5+
using System.Collections.Generic;
6+
using UnitTests.Grains;
7+
using Xunit;
8+
9+
namespace NonSilo.Tests.Reminders
10+
{
11+
[TestCategory("BVT")]
12+
[TestCategory("Reminders")]
13+
public class ReminderServiceConfigurationTests
14+
{
15+
/// <summary>
16+
/// Tests that if an IRemindable grain is registered, then an IReminderTable must also be registered.
17+
/// </summary>
18+
[Fact]
19+
public void ReminderService_ConfigurationValidatorTest()
20+
{
21+
var exception = Assert.Throws<OrleansConfigurationException>(() => new SiloHostBuilder()
22+
.UseLocalhostClustering()
23+
.ConfigureApplicationParts(parts => parts.AddFeatureProvider(new RemindableGrainFeatureProvider()))
24+
.Build());
25+
26+
Assert.Contains(nameof(ReminderTestGrain2), exception.Message);
27+
28+
var builder = new SiloHostBuilder()
29+
.UseLocalhostClustering()
30+
.ConfigureApplicationParts(parts => parts.AddFeatureProvider(new RemindableGrainFeatureProvider()))
31+
.UseInMemoryReminderService();
32+
using (var silo = builder.Build())
33+
{
34+
Assert.NotNull(silo);
35+
}
36+
}
37+
38+
private class RemindableGrainFeatureProvider : IApplicationFeatureProvider<GrainClassFeature>
39+
{
40+
public void PopulateFeature(IEnumerable<IApplicationPart> parts, GrainClassFeature feature)
41+
{
42+
feature.Classes.Add(new GrainClassMetadata(typeof(ReminderTestGrain2)));
43+
}
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)