Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
where TTarget : DurableTaskWorker
where TOptions : DurableTaskWorkerOptions
{
builder.UseBuildTarget(typeof(TTarget));

Check warning on line 81 in src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Prefer the generic overload 'Microsoft.DurableTask.Worker.DurableTaskWorkerBuilderExtensions.UseBuildTarget<TTarget>(Microsoft.DurableTask.Worker.IDurableTaskWorkerBuilder)' instead of 'Microsoft.DurableTask.Worker.DurableTaskWorkerBuilderExtensions.UseBuildTarget(Microsoft.DurableTask.Worker.IDurableTaskWorkerBuilder, System.Type)' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2263)

Check warning on line 81 in src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs

View workflow job for this annotation

GitHub Actions / smoke-tests

Prefer the generic overload 'Microsoft.DurableTask.Worker.DurableTaskWorkerBuilderExtensions.UseBuildTarget<TTarget>(Microsoft.DurableTask.Worker.IDurableTaskWorkerBuilder)' instead of 'Microsoft.DurableTask.Worker.DurableTaskWorkerBuilderExtensions.UseBuildTarget(Microsoft.DurableTask.Worker.IDurableTaskWorkerBuilder, System.Type)' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2263)
builder.Services.AddOptions<TOptions>(builder.Name)
.PostConfigure<IOptionsMonitor<DurableTaskWorkerOptions>>((options, baseOptions) =>
{
Expand Down Expand Up @@ -143,10 +143,10 @@
/// </summary>
/// <param name="builder">The builder to set the builder target for.</param>
/// <param name="workItemFilters">The instance of a <see cref="DurableTaskWorkerWorkItemFilters"/> to use.
/// If <c>null</c>, the auto-generated default filters will be cleared.</param>
/// If <c>null</c>, any previously configured filters will be cleared and filtering will be disabled.</param>
/// <returns>The same <see cref="IDurableTaskWorkerBuilder"/> instance, allowing for method chaining.</returns>
/// <remarks>Work item filters are auto-generated from the registry by default.
/// Use this method with explicit filters to override the defaults, or with <c>null</c> to opt out of filtering entirely.</remarks>
/// <remarks>By default, no work item filters are applied and the worker processes all work items.
/// Use this method with explicit filters to enable filtering, or with <c>null</c> to disable filtering.</remarks>
public static IDurableTaskWorkerBuilder UseWorkItemFilters(this IDurableTaskWorkerBuilder builder, DurableTaskWorkerWorkItemFilters? workItemFilters)
{
Check.NotNull(builder);
Expand All @@ -172,4 +172,43 @@

return builder;
}

/// <summary>
/// Enables work item filtering by auto-generating filters from the <see cref="DurableTaskRegistry"/>.
/// When enabled, the backend will only dispatch work items for registered orchestrations, activities,
/// and entities to this worker.
/// </summary>
/// <param name="builder">The builder to set the builder target for.</param>
/// <returns>The same <see cref="IDurableTaskWorkerBuilder"/> instance, allowing for method chaining.</returns>
/// <remarks>
/// <para>
/// Work item filtering can improve efficiency in multi-worker deployments by ensuring each worker
/// only receives work items it can handle. However, if an orchestration calls a task type
/// (e.g., an entity, activity, or sub-orchestrator) that is not registered with any connected worker,
/// the call may hang indefinitely instead of failing with an error.
/// </para>
/// <para>
/// Only use this method when all task types referenced by orchestrations are guaranteed to be
/// registered with at least one connected worker.
/// </para>
/// </remarks>
public static IDurableTaskWorkerBuilder UseWorkItemFilters(this IDurableTaskWorkerBuilder builder)
{
Check.NotNull(builder);

builder.Services.AddOptions<DurableTaskWorkerWorkItemFilters>(builder.Name)
.PostConfigure<IOptionsMonitor<DurableTaskRegistry>, IOptionsMonitor<DurableTaskWorkerOptions>>(
(opts, registryMonitor, workerOptionsMonitor) =>
{
DurableTaskRegistry registry = registryMonitor.Get(builder.Name);
DurableTaskWorkerOptions workerOptions = workerOptionsMonitor.Get(builder.Name);
DurableTaskWorkerWorkItemFilters generated =
DurableTaskWorkerWorkItemFilters.FromDurableTaskRegistry(registry, workerOptions);
opts.Orchestrations = generated.Orchestrations;
opts.Activities = generated.Activities;
opts.Entities = generated.Entities;
});

return builder;
}
}
15 changes: 0 additions & 15 deletions src/Worker/Core/DependencyInjection/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,6 @@ static IServiceCollection ConfigureDurableOptions(IServiceCollection services, s
}
});

// Auto-generate work item filters from the registry by default.
// Users can override these by calling UseWorkItemFilters(customFilters) on the builder.
services.AddOptions<DurableTaskWorkerWorkItemFilters>(name)
.Configure<IOptionsMonitor<DurableTaskRegistry>, IOptionsMonitor<DurableTaskWorkerOptions>>(
(opts, registryMonitor, workerOptionsMonitor) =>
{
DurableTaskRegistry registry = registryMonitor.Get(name);
DurableTaskWorkerOptions workerOptions = workerOptionsMonitor.Get(name);
DurableTaskWorkerWorkItemFilters generated =
DurableTaskWorkerWorkItemFilters.FromDurableTaskRegistry(registry, workerOptions);
opts.Orchestrations = generated.Orchestrations;
opts.Activities = generated.Activities;
opts.Entities = generated.Entities;
});

return services;
}

Expand Down
6 changes: 3 additions & 3 deletions src/Worker/Core/DurableTaskWorkerWorkItemFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
/// <summary>
/// A class that represents work item filters for a Durable Task Worker. These filters are passed to the backend
/// and only work items matching the filters will be processed by the worker. If no filters are provided,
/// the worker will process all work items. By default, these are auto-generated from the registered orchestrations,
/// activities, and entities in the <see cref="DurableTaskRegistry"/>. To opt-out of filters, provide a <c>null</c>
/// value to the <see cref="DurableTaskWorkerBuilderExtensions.UseWorkItemFilters"/> method when configuring the worker.
/// the worker will process all work items. To opt-in to work item filtering, call
/// <see cref="DurableTaskWorkerBuilderExtensions.UseWorkItemFilters"/> on the worker builder with either

Check warning on line 10 in src/Worker/Core/DurableTaskWorkerWorkItemFilters.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Ambiguous reference in cref attribute: 'DurableTaskWorkerBuilderExtensions.UseWorkItemFilters'. Assuming 'Microsoft.DurableTask.Worker.DurableTaskWorkerBuilderExtensions.UseWorkItemFilters(Microsoft.DurableTask.Worker.IDurableTaskWorkerBuilder, Microsoft.DurableTask.Worker.DurableTaskWorkerWorkItemFilters?)', but could have also matched other overloads including 'Microsoft.DurableTask.Worker.DurableTaskWorkerBuilderExtensions.UseWorkItemFilters(Microsoft.DurableTask.Worker.IDurableTaskWorkerBuilder)'.

Check warning on line 10 in src/Worker/Core/DurableTaskWorkerWorkItemFilters.cs

View workflow job for this annotation

GitHub Actions / smoke-tests

Ambiguous reference in cref attribute: 'DurableTaskWorkerBuilderExtensions.UseWorkItemFilters'. Assuming 'Microsoft.DurableTask.Worker.DurableTaskWorkerBuilderExtensions.UseWorkItemFilters(Microsoft.DurableTask.Worker.IDurableTaskWorkerBuilder, Microsoft.DurableTask.Worker.DurableTaskWorkerWorkItemFilters?)', but could have also matched other overloads including 'Microsoft.DurableTask.Worker.DurableTaskWorkerBuilderExtensions.UseWorkItemFilters(Microsoft.DurableTask.Worker.IDurableTaskWorkerBuilder)'.

Check warning on line 10 in src/Worker/Core/DurableTaskWorkerWorkItemFilters.cs

View workflow job for this annotation

GitHub Actions / build

Ambiguous reference in cref attribute: 'DurableTaskWorkerBuilderExtensions.UseWorkItemFilters'. Assuming 'Microsoft.DurableTask.Worker.DurableTaskWorkerBuilderExtensions.UseWorkItemFilters(Microsoft.DurableTask.Worker.IDurableTaskWorkerBuilder, Microsoft.DurableTask.Worker.DurableTaskWorkerWorkItemFilters?)', but could have also matched other overloads including 'Microsoft.DurableTask.Worker.DurableTaskWorkerBuilderExtensions.UseWorkItemFilters(Microsoft.DurableTask.Worker.IDurableTaskWorkerBuilder)'.
/// explicit filters or auto-generated filters from the <see cref="DurableTaskRegistry"/>.
/// </summary>
public class DurableTaskWorkerWorkItemFilters
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void UseWorkItemFilters_ReturnsBuilder_ForChaining()
}

[Fact]
public void WorkItemFilters_DefaultFromRegistry_WhenNoExplicitFiltersConfigured()
public void WorkItemFilters_DefaultFromRegistry_WhenExplicitlyOptedIn()
{
// Arrange
ServiceCollection services = new();
Expand All @@ -74,6 +74,7 @@ public void WorkItemFilters_DefaultFromRegistry_WhenNoExplicitFiltersConfigured(
registry.AddOrchestrator<TestOrchestrator>();
registry.AddActivity<TestActivity>();
});
builder.UseWorkItemFilters();
});

// Act
Expand All @@ -88,7 +89,7 @@ public void WorkItemFilters_DefaultFromRegistry_WhenNoExplicitFiltersConfigured(
}

[Fact]
public void WorkItemFilters_DefaultWithEntity_WhenNoExplicitFiltersConfigured()
public void WorkItemFilters_DefaultWithEntity_WhenExplicitlyOptedIn()
{
// Arrange
ServiceCollection services = new();
Expand All @@ -98,6 +99,7 @@ public void WorkItemFilters_DefaultWithEntity_WhenNoExplicitFiltersConfigured()
{
registry.AddEntity<TestEntity>();
});
builder.UseWorkItemFilters();
});

// Act
Expand All @@ -111,7 +113,7 @@ public void WorkItemFilters_DefaultWithEntity_WhenNoExplicitFiltersConfigured()
}

[Fact]
public void WorkItemFilters_DefaultNullWithVersioningCurrentOrOlder_WhenNoExplicitFiltersConfigured()
public void WorkItemFilters_DefaultNullWithVersioningCurrentOrOlder_WhenExplicitlyOptedIn()
{
// Arrange
ServiceCollection services = new();
Expand All @@ -130,6 +132,7 @@ public void WorkItemFilters_DefaultNullWithVersioningCurrentOrOlder_WhenNoExplic
MatchStrategy = DurableTaskWorkerOptions.VersionMatchStrategy.CurrentOrOlder,
};
});
builder.UseWorkItemFilters();
});

// Act
Expand All @@ -144,7 +147,7 @@ public void WorkItemFilters_DefaultNullWithVersioningCurrentOrOlder_WhenNoExplic
}

[Fact]
public void WorkItemFilters_DefaultNullWithVersioningNone_WhenNoExplicitFiltersConfigured()
public void WorkItemFilters_DefaultNullWithVersioningNone_WhenExplicitlyOptedIn()
{
// Arrange
ServiceCollection services = new();
Expand All @@ -163,6 +166,7 @@ public void WorkItemFilters_DefaultNullWithVersioningNone_WhenNoExplicitFiltersC
MatchStrategy = DurableTaskWorkerOptions.VersionMatchStrategy.None,
};
});
builder.UseWorkItemFilters();
});

// Act
Expand All @@ -177,7 +181,7 @@ public void WorkItemFilters_DefaultNullWithVersioningNone_WhenNoExplicitFiltersC
}

[Fact]
public void WorkItemFilters_DefaultVersionWithVersioningStrict_WhenNoExplicitFiltersConfigured()
public void WorkItemFilters_DefaultVersionWithVersioningStrict_WhenExplicitlyOptedIn()
{
// Arrange
ServiceCollection services = new();
Expand All @@ -196,6 +200,7 @@ public void WorkItemFilters_DefaultVersionWithVersioningStrict_WhenNoExplicitFil
MatchStrategy = DurableTaskWorkerOptions.VersionMatchStrategy.Strict,
};
});
builder.UseWorkItemFilters();
});

// Act
Expand Down Expand Up @@ -232,7 +237,34 @@ public void WorkItemFilters_DefaultEmptyRegistry_ProducesEmptyFilters()
}

[Fact]
public void WorkItemFilters_ExplicitFiltersOverrideDefaults()
public void WorkItemFilters_DefaultNoFilters_WhenNoExplicitOptIn()
{
// Arrange - register tasks but do NOT call UseWorkItemFilters()
ServiceCollection services = new();
services.AddDurableTaskWorker("test", builder =>
{
builder.AddTasks(registry =>
{
registry.AddOrchestrator<TestOrchestrator>();
registry.AddActivity<TestActivity>();
registry.AddEntity<TestEntity>();
});
});

// Act
ServiceProvider provider = services.BuildServiceProvider();
IOptionsMonitor<DurableTaskWorkerWorkItemFilters> filtersMonitor =
provider.GetRequiredService<IOptionsMonitor<DurableTaskWorkerWorkItemFilters>>();
DurableTaskWorkerWorkItemFilters actual = filtersMonitor.Get("test");

// Assert - with no explicit opt-in, filters should be empty (legacy behavior)
actual.Orchestrations.Should().BeEmpty();
actual.Activities.Should().BeEmpty();
actual.Entities.Should().BeEmpty();
}

[Fact]
public void WorkItemFilters_ExplicitFiltersOverrideAutoGenerated()
{
// Arrange
ServiceCollection services = new();
Expand Down Expand Up @@ -266,7 +298,7 @@ public void WorkItemFilters_ExplicitFiltersOverrideDefaults()
}

[Fact]
public void WorkItemFilters_NullOverwritesDefaults()
public void WorkItemFilters_NullClearsAutoGeneratedFilters()
{
// Arrange
ServiceCollection services = new();
Expand All @@ -277,7 +309,8 @@ public void WorkItemFilters_NullOverwritesDefaults()
registry.AddOrchestrator<TestOrchestrator>();
registry.AddActivity<TestActivity>();
});
builder.UseWorkItemFilters(null);
builder.UseWorkItemFilters(); // opt-in to auto-generated filters
builder.UseWorkItemFilters(null); // then clear them
});

// Act
Expand All @@ -293,7 +326,7 @@ public void WorkItemFilters_NullOverwritesDefaults()
}

[Fact]
public void WorkItemFilters_EmptyFiltersOverrideDefaults()
public void WorkItemFilters_EmptyFiltersClearAutoGenerated()
{
// Arrange
ServiceCollection services = new();
Expand All @@ -311,7 +344,8 @@ public void WorkItemFilters_EmptyFiltersOverrideDefaults()
registry.AddOrchestrator<TestOrchestrator>();
registry.AddActivity<TestActivity>();
});
builder.UseWorkItemFilters(emptyFilters);
builder.UseWorkItemFilters(); // opt-in to auto-generated filters
builder.UseWorkItemFilters(emptyFilters); // then clear them with empty
});

// Act
Expand All @@ -327,17 +361,19 @@ public void WorkItemFilters_EmptyFiltersOverrideDefaults()
}

[Fact]
public void WorkItemFilters_NamedBuilders_HaveUniqueDefaultFilters()
public void WorkItemFilters_NamedBuilders_HaveUniqueDefaultFilters_WhenExplicitlyOptedIn()
{
// Arrange
ServiceCollection services = new();
services.AddDurableTaskWorker("worker1", builder =>
{
builder.AddTasks(registry => registry.AddOrchestrator<TestOrchestrator>());
builder.UseWorkItemFilters();
});
services.AddDurableTaskWorker("worker2", builder =>
{
builder.AddTasks(registry => registry.AddActivity<TestActivity>());
builder.UseWorkItemFilters();
});

// Act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ public void ToGrpcWorkItemFilters_WithMixedFilters_ConvertsAll()
}

[Fact]
public void WorkerConstruction_DefaultFilters_FlowToWorker()
public void WorkerConstruction_NoOptIn_HasEmptyFilters()
{
// Arrange
ServiceCollection services = new();
Expand All @@ -325,10 +325,10 @@ public void WorkerConstruction_DefaultFilters_FlowToWorker()
.GetField("workItemFilters", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!
.GetValue(hosted);

// Assert
// Assert - without UseWorkItemFilters(), filters should be empty (no filtering)
filters.Should().NotBeNull();
filters!.Orchestrations.Should().ContainSingle(o => o.Name == nameof(TestOrchestrator));
filters.Activities.Should().ContainSingle(a => a.Name == nameof(TestActivity));
filters!.Orchestrations.Should().BeEmpty();
filters.Activities.Should().BeEmpty();
}

[Fact]
Expand Down
Loading