Add support for per type actor configuration (#870)

This commit allows different actor types to provide their own
configurations instead of relying on the top-level values. Any
value defined in them will be used instead of the top-level.
Anything that is left out will use the top-level or default if
it is undefined.

https://github.com/dapr/dotnet-sdk/issues/857

Signed-off-by: Hal Spang <halspang@microsoft.com>
This commit is contained in:
halspang 2022-05-26 21:47:46 +00:00 committed by GitHub
parent cda60cb382
commit e4236c4c54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 172 additions and 43 deletions

View File

@ -23,9 +23,19 @@ namespace Dapr.Actors.Runtime
/// Initializes a new instance of <see cref="ActorRegistration" />.
/// </summary>
/// <param name="type">The <see cref="ActorTypeInformation" /> for the actor type.</param>
public ActorRegistration(ActorTypeInformation type)
public ActorRegistration(ActorTypeInformation type) : this(type, null)
{
}
/// <summary>
/// Initializes a new instance of <see cref="ActorRegistration" />.
/// </summary>
/// <param name="type">The <see cref="ActorTypeInformation" /> for the actor type.</param>
/// <param name="options">The optional <see cref="ActorRuntimeOptions"/> that are specified for this type only.</param>
public ActorRegistration(ActorTypeInformation type, ActorRuntimeOptions options)
{
this.Type = type;
this.TypeOptions = options;
}
/// <summary>
@ -38,5 +48,10 @@ namespace Dapr.Actors.Runtime
/// activator of the runtime will be used.
/// </summary>
public ActorActivator Activator { get; set; }
/// <summary>
/// An optional set of options for this specific actor type. These will override the top level or default values.
/// </summary>
public ActorRuntimeOptions TypeOptions { get; }
}
}

View File

@ -42,6 +42,18 @@ namespace Dapr.Actors.Runtime
RegisterActor<TActor>(actorTypeName: null, configure);
}
/// <summary>
/// Registers an actor type in the collection.
/// </summary>
/// <typeparam name="TActor">Type of actor.</typeparam>
/// <param name="typeOptions">An optional <see cref="ActorRuntimeOptions"/> that defines values for this type alone.</param>
/// <param name="configure">An optional delegate used to configure the actor registration.</param>
public void RegisterActor<TActor>(ActorRuntimeOptions typeOptions, Action<ActorRegistration> configure = null)
where TActor : Actor
{
RegisterActor<TActor>(null, typeOptions, configure);
}
/// <summary>
/// Registers an actor type in the collection.
/// </summary>
@ -51,9 +63,23 @@ namespace Dapr.Actors.Runtime
/// <remarks>The value of <paramref name="actorTypeName"/> will have precedence over the default actor type name derived from the actor implementation type or any type name set via <see cref="ActorAttribute"/>.</remarks>
public void RegisterActor<TActor>(string actorTypeName, Action<ActorRegistration> configure = null)
where TActor : Actor
{
RegisterActor<TActor>(actorTypeName, null, configure);
}
/// <summary>
/// Registers an actor type in the collection.
/// </summary>
/// <typeparam name="TActor">Type of actor.</typeparam>
/// <param name="actorTypeName">The name of the actor type represented by the actor.</param>
/// <param name="typeOptions">An optional <see cref="ActorRuntimeOptions"/> that defines values for this type alone.</param>
/// <param name="configure">An optional delegate used to configure the actor registration.</param>
/// <remarks>The value of <paramref name="actorTypeName"/> will have precedence over the default actor type name derived from the actor implementation type or any type name set via <see cref="ActorAttribute"/>.</remarks>
public void RegisterActor<TActor>(string actorTypeName, ActorRuntimeOptions typeOptions, Action<ActorRegistration> configure = null)
where TActor : Actor
{
var actorTypeInfo = ActorTypeInformation.Get(typeof(TActor), actorTypeName);
var registration = new ActorRegistration(actorTypeInfo);
var registration = new ActorRegistration(actorTypeInfo, typeOptions);
configure?.Invoke(registration);
this.Add(registration);
}

View File

@ -11,18 +11,19 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors.Client;
using Microsoft.Extensions.Logging;
namespace Dapr.Actors.Runtime
{
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors.Client;
using Microsoft.Extensions.Logging;
/// <summary>
/// Contains methods to register actor types. Registering the types allows the runtime to create instances of the actor.
/// </summary>
@ -80,46 +81,72 @@ namespace Dapr.Actors.Runtime
writer.WriteEndArray();
if (this.options.ActorIdleTimeout != null)
{
writer.WriteString("actorIdleTimeout", ConverterUtils.ConvertTimeSpanValueInDaprFormat(this.options.ActorIdleTimeout));
}
writeActorOptions(writer, this.options);
if (this.options.ActorScanInterval != null)
{
writer.WriteString("actorScanInterval", ConverterUtils.ConvertTimeSpanValueInDaprFormat(this.options.ActorScanInterval));
}
var actorsWithConfigs = this.options.Actors.Where(actor => actor.TypeOptions != null).ToList();
if (this.options.DrainOngoingCallTimeout != null)
if (actorsWithConfigs.Count > 0)
{
writer.WriteString("drainOngoingCallTimeout", ConverterUtils.ConvertTimeSpanValueInDaprFormat(this.options.DrainOngoingCallTimeout));
}
writer.WritePropertyName("entitiesConfig");
writer.WriteStartArray();
foreach (var actor in actorsWithConfigs)
{
writer.WriteStartObject();
writer.WritePropertyName("entities");
writer.WriteStartArray();
writer.WriteStringValue(actor.Type.ActorTypeName);
writer.WriteEndArray();
// default is false, don't write it if default
if (this.options.DrainRebalancedActors != false)
{
writer.WriteBoolean("drainRebalancedActors", (this.options.DrainRebalancedActors));
}
writeActorOptions(writer, actor.TypeOptions);
// default is null, don't write it if default
if (this.options.RemindersStoragePartitions != null)
{
writer.WriteNumber("remindersStoragePartitions", this.options.RemindersStoragePartitions.Value);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
// Reentrancy has a default value so it is always included.
writer.WriteStartObject("reentrancy");
writer.WriteBoolean("enabled", this.options.ReentrancyConfig.Enabled);
if (this.options.ReentrancyConfig.MaxStackDepth != null)
{
writer.WriteNumber("maxStackDepth", this.options.ReentrancyConfig.MaxStackDepth.Value);
}
writer.WriteEndObject();
writer.WriteEndObject();
return writer.FlushAsync();
}
private void writeActorOptions(Utf8JsonWriter writer, ActorRuntimeOptions actorOptions)
{
if (actorOptions.ActorIdleTimeout != null)
{
writer.WriteString("actorIdleTimeout", ConverterUtils.ConvertTimeSpanValueInDaprFormat(actorOptions.ActorIdleTimeout));
}
if (actorOptions.ActorScanInterval != null)
{
writer.WriteString("actorScanInterval", ConverterUtils.ConvertTimeSpanValueInDaprFormat(actorOptions.ActorScanInterval));
}
if (actorOptions.DrainOngoingCallTimeout != null)
{
writer.WriteString("drainOngoingCallTimeout", ConverterUtils.ConvertTimeSpanValueInDaprFormat(actorOptions.DrainOngoingCallTimeout));
}
// default is false, don't write it if default
if (actorOptions.DrainRebalancedActors != false)
{
writer.WriteBoolean("drainRebalancedActors", (actorOptions.DrainRebalancedActors));
}
// default is null, don't write it if default
if (actorOptions.RemindersStoragePartitions != null)
{
writer.WriteNumber("remindersStoragePartitions", actorOptions.RemindersStoragePartitions.Value);
}
// Reentrancy has a default value so it is always included.
writer.WriteStartObject("reentrancy");
writer.WriteBoolean("enabled", actorOptions.ReentrancyConfig.Enabled);
if (actorOptions.ReentrancyConfig.MaxStackDepth != null)
{
writer.WriteNumber("maxStackDepth", actorOptions.ReentrancyConfig.MaxStackDepth.Value);
}
writer.WriteEndObject();
}
// Deactivates an actor for an actor type with given actor id.
internal async Task DeactivateAsync(string actorTypeName, string actorId)
{

View File

@ -262,6 +262,60 @@ namespace Dapr.Actors.Test
Assert.Equal(64, element.GetInt32());
}
[Fact]
public async Task TestActorSettingsWithPerActorConfigurations()
{
var actorType = typeof(TestActor);
var options = new ActorRuntimeOptions();
options.ActorIdleTimeout = TimeSpan.FromSeconds(33);
options.ActorScanInterval = TimeSpan.FromSeconds(44);
options.DrainOngoingCallTimeout = TimeSpan.FromSeconds(55);
options.DrainRebalancedActors = true;
options.ReentrancyConfig.Enabled = true;
options.ReentrancyConfig.MaxStackDepth = 32;
options.Actors.RegisterActor<TestActor>(options);
var runtime = new ActorRuntime(options, loggerFactory, activatorFactory, proxyFactory);
Assert.Contains(actorType.Name, runtime.RegisteredActors.Select(a => a.Type.ActorTypeName), StringComparer.InvariantCulture);
ArrayBufferWriter<byte> writer = new ArrayBufferWriter<byte>();
await runtime.SerializeSettingsAndRegisteredTypes(writer);
// read back the serialized json
var array = writer.WrittenSpan.ToArray();
string s = Encoding.UTF8.GetString(array, 0, array.Length);
JsonDocument document = JsonDocument.Parse(s);
JsonElement root = document.RootElement;
JsonElement element = root.GetProperty("entities");
Assert.Equal(1, element.GetArrayLength());
element = root.GetProperty("entitiesConfig");
Assert.Equal(1, element.GetArrayLength());
var perEntityConfig = element[0];
element = perEntityConfig.GetProperty("actorIdleTimeout");
Assert.Equal(TimeSpan.FromSeconds(33), ConverterUtils.ConvertTimeSpanFromDaprFormat(element.GetString()));
element = perEntityConfig.GetProperty("actorScanInterval");
Assert.Equal(TimeSpan.FromSeconds(44), ConverterUtils.ConvertTimeSpanFromDaprFormat(element.GetString()));
element = perEntityConfig.GetProperty("drainOngoingCallTimeout");
Assert.Equal(TimeSpan.FromSeconds(55), ConverterUtils.ConvertTimeSpanFromDaprFormat(element.GetString()));
element = perEntityConfig.GetProperty("drainRebalancedActors");
Assert.True(element.GetBoolean());
element = root.GetProperty("reentrancy").GetProperty("enabled");
Assert.True(element.GetBoolean());
element = root.GetProperty("reentrancy").GetProperty("maxStackDepth");
Assert.Equal(32, element.GetInt32());
}
private sealed class TestActor : Actor, ITestActor
{
public TestActor(ActorHost host)

View File

@ -27,8 +27,15 @@ namespace Dapr.E2E.Test.App.ReentrantActors
{
services.AddActors(options =>
{
options.Actors.RegisterActor<ReentrantActor>();
options.ReentrancyConfig = new() { Enabled = true };
// We force this to use a per-actor config as an easy way to validate that's working.
options.ReentrancyConfig = new() { Enabled = false };
options.Actors.RegisterActor<ReentrantActor>(typeOptions: new()
{
ReentrancyConfig = new()
{
Enabled = true,
}
});
});
}