diff --git a/src/Dapr.Actors.Next.Abstractions/Options/DaprActorsOptions.cs b/src/Dapr.Actors.Next.Abstractions/Options/DaprActorsOptions.cs index 38648373f..0a41e9847 100644 --- a/src/Dapr.Actors.Next.Abstractions/Options/DaprActorsOptions.cs +++ b/src/Dapr.Actors.Next.Abstractions/Options/DaprActorsOptions.cs @@ -52,18 +52,21 @@ public sealed class DaprActorsOptions /// /// Gets or sets the idle timeout for actor activations. + /// leaves the value unset so the Dapr runtime default applies. /// - public TimeSpan ActorIdleTimeout { get; set; } = TimeSpan.FromMinutes(60); + public TimeSpan? ActorIdleTimeout { get; set; } /// /// Gets or sets the timeout used when draining ongoing actor calls. + /// leaves the value unset so the Dapr runtime default applies. /// - public TimeSpan DrainOngoingCallTimeout { get; private set; } = TimeSpan.FromSeconds(30); + public TimeSpan? DrainOngoingCallTimeout { get; set; } /// /// Gets or sets the timeout used when draining rebalanced actors. + /// leaves the value unset so the Dapr runtime default applies. /// - public TimeSpan DrainRebalancedActorsTimeout + public TimeSpan? DrainRebalancedActorsTimeout { get => DrainOngoingCallTimeout; set => DrainOngoingCallTimeout = value; @@ -71,13 +74,15 @@ public TimeSpan DrainRebalancedActorsTimeout /// /// Gets or sets a value indicating whether rebalanced actors drain in-flight calls. + /// leaves the value unset so the Dapr runtime default applies. /// - public bool DrainRebalancedActors { get; set; } = true; + public bool? DrainRebalancedActors { get; set; } /// /// Gets or sets a value indicating whether reentrant actor calls are allowed. + /// leaves the value unset so the Dapr runtime default applies. /// - public bool EnableReentrancy { get; set; } + public bool? EnableReentrancy { get; set; } /// /// Gets or sets a value indicating whether the sidecar (gRPC) transport is used for actor state, @@ -90,8 +95,9 @@ public TimeSpan DrainRebalancedActorsTimeout /// /// Gets or sets the maximum reentrant call depth when reentrancy is enabled. + /// leaves the value unset so the Dapr runtime default applies. /// - public int MaxReentrantDepth { get; set; } = 32; + public int? MaxReentrantDepth { get; set; } internal void CopyFrom(DaprActorsOptions source) { diff --git a/src/Dapr.Actors.Next.Abstractions/Options/DaprActorsOptionsValidator.cs b/src/Dapr.Actors.Next.Abstractions/Options/DaprActorsOptionsValidator.cs index 0d8e4a3c1..3c8b2d34b 100644 --- a/src/Dapr.Actors.Next.Abstractions/Options/DaprActorsOptionsValidator.cs +++ b/src/Dapr.Actors.Next.Abstractions/Options/DaprActorsOptionsValidator.cs @@ -26,10 +26,10 @@ public ValidateOptionsResult Validate(string? name, DaprActorsOptions options) List? failures = null; AddFailureIf(options.DefaultContractVersion <= 0, "DefaultContractVersion must be greater than zero.", ref failures); - AddFailureIf(options.ActorIdleTimeout <= TimeSpan.Zero, "ActorIdleTimeout must be greater than zero.", ref failures); - AddFailureIf(options.DrainOngoingCallTimeout <= TimeSpan.Zero, "DrainOngoingCallTimeout must be greater than zero.", ref failures); - AddFailureIf(options.DrainRebalancedActorsTimeout <= TimeSpan.Zero, "DrainRebalancedActorsTimeout must be greater than zero.", ref failures); - AddFailureIf(options.MaxReentrantDepth <= 0, "MaxReentrantDepth must be greater than zero.", ref failures); + AddFailureIf(options.ActorIdleTimeout is { } globalIdleTimeout && globalIdleTimeout <= TimeSpan.Zero, "ActorIdleTimeout must be greater than zero.", ref failures); + AddFailureIf(options.DrainOngoingCallTimeout is { } globalDrainTimeout && globalDrainTimeout <= TimeSpan.Zero, "DrainOngoingCallTimeout must be greater than zero.", ref failures); + AddFailureIf(options.DrainRebalancedActorsTimeout is { } globalDrainRebalancedTimeout && globalDrainRebalancedTimeout <= TimeSpan.Zero, "DrainRebalancedActorsTimeout must be greater than zero.", ref failures); + AddFailureIf(options.MaxReentrantDepth is { } globalMaxDepth && globalMaxDepth <= 0, "MaxReentrantDepth must be greater than zero.", ref failures); foreach (var registration in options.Actors.Registrations) { diff --git a/src/Dapr.Actors.Next.Core/Transport/DaprActorEventsTransport.cs b/src/Dapr.Actors.Next.Core/Transport/DaprActorEventsTransport.cs index ba8ab3b2a..281b1294b 100644 --- a/src/Dapr.Actors.Next.Core/Transport/DaprActorEventsTransport.cs +++ b/src/Dapr.Actors.Next.Core/Transport/DaprActorEventsTransport.cs @@ -240,17 +240,38 @@ private static P.SubscribeActorEventsRequestInitialAlpha1 CreateInitialRequest(S return request; } - var entityConfig = new P.ActorEntityConfig + // Only assign fields the app configured; unset fields keep their + // Dapr runtime defaults. + var entityConfig = new P.ActorEntityConfig(); + + if (config.ActorIdleTimeout is { } idleTimeout) { - ActorIdleTimeout = Duration.FromTimeSpan(config.ActorIdleTimeout), - DrainOngoingCallTimeout = Duration.FromTimeSpan(config.DrainOngoingCallTimeout), - DrainRebalancedActors = config.DrainRebalancedActors, - Reentrancy = new P.ActorReentrancyConfig + entityConfig.ActorIdleTimeout = Duration.FromTimeSpan(idleTimeout); + } + + if (config.DrainOngoingCallTimeout is { } drainTimeout) + { + entityConfig.DrainOngoingCallTimeout = Duration.FromTimeSpan(drainTimeout); + } + + if (config.DrainRebalancedActors is { } drainRebalanced) + { + entityConfig.DrainRebalancedActors = drainRebalanced; + } + + if (config.EnableReentrancy is not null || config.MaxReentrantDepth is not null) + { + var reentrancy = new P.ActorReentrancyConfig { - Enabled = config.EnableReentrancy, - MaxStackDepth = config.MaxReentrantDepth, - }, - }; + Enabled = config.EnableReentrancy ?? false, + }; + if (config.MaxReentrantDepth is { } maxDepth) + { + reentrancy.MaxStackDepth = maxDepth; + } + entityConfig.Reentrancy = reentrancy; + } + entityConfig.Entities.Add(request.Entities); request.EntitiesConfig.Add(entityConfig); return request; diff --git a/src/Dapr.Actors.Next.Core/Transport/SubscribeActorEventsResponse.cs b/src/Dapr.Actors.Next.Core/Transport/SubscribeActorEventsResponse.cs index 5561bbabe..dd3186b0e 100644 --- a/src/Dapr.Actors.Next.Core/Transport/SubscribeActorEventsResponse.cs +++ b/src/Dapr.Actors.Next.Core/Transport/SubscribeActorEventsResponse.cs @@ -66,10 +66,12 @@ public static SubscribeActorEventsResponse Failed(string id, string message, uin /// /// Runtime options advertised with the initial actor event stream registration. +/// A value leaves that field unset on the wire so the +/// Dapr runtime default applies. /// public sealed record SubscribeActorEventsInitialConfig( - TimeSpan ActorIdleTimeout, - TimeSpan DrainOngoingCallTimeout, - bool DrainRebalancedActors, - bool EnableReentrancy, - int MaxReentrantDepth); + TimeSpan? ActorIdleTimeout, + TimeSpan? DrainOngoingCallTimeout, + bool? DrainRebalancedActors, + bool? EnableReentrancy, + int? MaxReentrantDepth); diff --git a/test/Dapr.Actors.Next.Abstractions.Test/OptionsTests.cs b/test/Dapr.Actors.Next.Abstractions.Test/OptionsTests.cs index 519606d17..dbcf84504 100644 --- a/test/Dapr.Actors.Next.Abstractions.Test/OptionsTests.cs +++ b/test/Dapr.Actors.Next.Abstractions.Test/OptionsTests.cs @@ -146,6 +146,21 @@ public void RegisterActor_NullLiteral_ResolvesToNameOverload() Assert.Null(registration.TypeOptions); } + [MinimumDaprRuntimeFact("1.18")] + public void Options_DrainRebalancedActorsTimeout_AliasesDrainOngoingCallTimeout() + { + var options = new DaprActorsOptions(); + Assert.Null(options.DrainOngoingCallTimeout); + Assert.Null(options.DrainRebalancedActorsTimeout); + + options.DrainRebalancedActorsTimeout = TimeSpan.FromSeconds(7); + Assert.Equal(TimeSpan.FromSeconds(7), options.DrainOngoingCallTimeout); + Assert.Equal(TimeSpan.FromSeconds(7), options.DrainRebalancedActorsTimeout); + + options.DrainOngoingCallTimeout = null; + Assert.Null(options.DrainRebalancedActorsTimeout); + } + [MinimumDaprRuntimeFact("1.18")] public void TypeOptions_DrainRebalancedActorsTimeout_AliasesDrainOngoingCallTimeout() { diff --git a/test/Dapr.Actors.Next.Core.Test/CoreRuntimeTests.cs b/test/Dapr.Actors.Next.Core.Test/CoreRuntimeTests.cs index 90d38812f..9bd6a9094 100644 --- a/test/Dapr.Actors.Next.Core.Test/CoreRuntimeTests.cs +++ b/test/Dapr.Actors.Next.Core.Test/CoreRuntimeTests.cs @@ -117,11 +117,11 @@ public async Task Stream_advertises_global_defaults_in_initial_config() var config = advertisement.InitialConfig; Assert.NotNull(config); - Assert.Equal(TimeSpan.FromMinutes(60), config!.ActorIdleTimeout); - Assert.Equal(TimeSpan.FromSeconds(30), config.DrainOngoingCallTimeout); - Assert.True(config.DrainRebalancedActors); - Assert.False(config.EnableReentrancy); - Assert.Equal(32, config.MaxReentrantDepth); + Assert.Null(config!.ActorIdleTimeout); + Assert.Null(config.DrainOngoingCallTimeout); + Assert.Null(config.DrainRebalancedActors); + Assert.Null(config.EnableReentrancy); + Assert.Null(config.MaxReentrantDepth); } [MinimumDaprRuntimeFact("1.18")] @@ -151,17 +151,17 @@ public async Task Stream_advertises_merged_type_options_per_type() var overridden = advertisements["Counter"].InitialConfig; Assert.NotNull(overridden); Assert.Equal(TimeSpan.FromMinutes(2), overridden!.ActorIdleTimeout); - Assert.Equal(TimeSpan.FromSeconds(30), overridden.DrainOngoingCallTimeout); - Assert.True(overridden.DrainRebalancedActors); + Assert.Null(overridden.DrainOngoingCallTimeout); + Assert.Null(overridden.DrainRebalancedActors); Assert.True(overridden.EnableReentrancy); Assert.Equal(8, overridden.MaxReentrantDepth); var inherited = advertisements["OtherCounter"].InitialConfig; Assert.NotNull(inherited); Assert.Equal(TimeSpan.FromMinutes(10), inherited!.ActorIdleTimeout); - Assert.Equal(TimeSpan.FromSeconds(30), inherited.DrainOngoingCallTimeout); - Assert.True(inherited.DrainRebalancedActors); - Assert.False(inherited.EnableReentrancy); + Assert.Null(inherited.DrainOngoingCallTimeout); + Assert.Null(inherited.DrainRebalancedActors); + Assert.Null(inherited.EnableReentrancy); Assert.Equal(8, inherited.MaxReentrantDepth); } @@ -193,7 +193,7 @@ public async Task Type_options_override_registration_full_options() Assert.Equal(TimeSpan.FromMinutes(1), merged!.ActorIdleTimeout); Assert.Equal(TimeSpan.FromSeconds(7), merged.DrainOngoingCallTimeout); Assert.False(merged.DrainRebalancedActors); - Assert.False(merged.EnableReentrancy); + Assert.Null(merged.EnableReentrancy); Assert.Equal(3, merged.MaxReentrantDepth); } diff --git a/test/Dapr.Actors.Next.Core.Test/GeneratedActorEventsTransportTests.cs b/test/Dapr.Actors.Next.Core.Test/GeneratedActorEventsTransportTests.cs index dfba86cad..be0388aa8 100644 --- a/test/Dapr.Actors.Next.Core.Test/GeneratedActorEventsTransportTests.cs +++ b/test/Dapr.Actors.Next.Core.Test/GeneratedActorEventsTransportTests.cs @@ -116,6 +116,32 @@ await stream.WriteAsync(SubscribeActorEventsResponse.RegisteredActors( Assert.Equal(7, entityConfig.Reentrancy.MaxStackDepth); } + [MinimumDaprRuntimeFact("1.18")] + public async Task Transport_omits_unset_fields_in_initial_config() + { + var harness = new GeneratedActorEventsHarness(); + var transport = new DaprActorEventsTransport(harness.Client); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + + await using var stream = await transport.OpenStreamAsync(cts.Token); + await stream.WriteAsync(SubscribeActorEventsResponse.RegisteredActors( + ["Sparse"], + new SubscribeActorEventsInitialConfig( + null, + TimeSpan.FromSeconds(5), + null, + null, + null)), cts.Token); + var initial = await harness.ReceiveAsync(cts.Token); + + var entityConfig = Assert.Single(initial.InitialRequest.EntitiesConfig); + Assert.Equal(new[] { "Sparse" }, entityConfig.Entities.ToArray()); + Assert.Null(entityConfig.ActorIdleTimeout); + Assert.Equal(5, entityConfig.DrainOngoingCallTimeout.Seconds); + Assert.False(entityConfig.HasDrainRebalancedActors); + Assert.Null(entityConfig.Reentrancy); + } + [MinimumDaprRuntimeFact("1.18")] public async Task Transport_maps_failures_and_non_invoke_callbacks() { diff --git a/test/Dapr.IntegrationTest.Actors.Next/RealSidecarActorTests.cs b/test/Dapr.IntegrationTest.Actors.Next/RealSidecarActorTests.cs index fd610bdde..cdadfbe44 100644 --- a/test/Dapr.IntegrationTest.Actors.Next/RealSidecarActorTests.cs +++ b/test/Dapr.IntegrationTest.Actors.Next/RealSidecarActorTests.cs @@ -164,6 +164,7 @@ private static void ConfigureActorHost(WebApplicationBuilder builder) builder.Services.AddDaprActors(options => { options.DrainRebalancedActorsTimeout = TimeSpan.FromSeconds(1); + options.DrainRebalancedActors = true; options.EnableReentrancy = true; options.MaxReentrantDepth = 8; });