Skip to content

Conversation

@halspang
Copy link
Member

Summary

What changed?

This change adds WorkItemFilters into the grpc worker. This includes builder methods to specify them and the connection into the GetWorkItems flow inside the worker processor.

Why is this change needed?

Every orchestration, activity, and entity would go to any worker regardless of their actual ability. This change allows workers to specify what they can work on and receive those changes.

Issues / work items

  • Resolves #
  • Related #

Project checklist

  • Release notes are not required for the next release
    • Otherwise: Notes added to release_notes.md
  • Backport is not required
    • Otherwise: Backport tracked by issue/PR #issue_or_pr
  • All required tests have been added/updated (unit tests, E2E tests)
  • Breaking change?
    • If yes:
      • Impact:
      • Migration guidance:

AI-assisted code disclosure (required)

Was an AI tool used? (select one)

  • No
  • Yes, AI helped write parts of this PR (e.g., GitHub Copilot)
  • Yes, an AI agent generated most of this PR

If AI was used:

  • Tool(s):
  • AI-assisted areas/files: Tests
  • What you changed after AI output:

AI verification (required if AI was used):

  • I understand the code and can explain it
  • I verified referenced APIs/types exist and are correct
  • I reviewed edge cases/failure paths (timeouts, retries, cancellation, exceptions)
  • I reviewed concurrency/async behavior
  • I checked for unintended breaking or behavior changes

Testing

Automated tests

  • Result: Passed

Manual validation (only if runtime/behavior changed)

  • Environment (OS, .NET version, components): Windows/Aspire, .NET, Custom test bed

  • Steps + observed results:

    1. Hookup a variety of workers with different filters specified (including auto-gen).
    2. Run tests on them and ensure the appropriate work lands on the appropriate worker.
  • Evidence (optional):
    I didn't take screenshots but I can go back if required.


Notes for reviewers

  • N/A

This change adds WorkItemFilters into the grpc worker. This includes
builder methods to specify them and the connection into the
GetWorkItems flow inside the worker processor.

Signed-off-by: Hal Spang <halspang@microsoft.com>
Copilot AI review requested due to automatic review settings January 15, 2026 06:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces WorkItemFilters functionality to the gRPC worker, enabling workers to specify which orchestrations, activities, and entities they can process. This prevents workers from receiving work items they cannot handle.

Changes:

  • Added DurableTaskWorkerWorkItemFilters class with filter structs for orchestrations, activities, and entities
  • Extended builder API with UseWorkItemFilters() method supporting both explicit and auto-generated filters
  • Integrated filters into gRPC worker constructor and GetWorkItems request flow

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/Worker/Core/DurableTaskWorkerWorkItemFilters.cs New public API class defining filter structures with support for versioning
src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs Extension method for registering filters with auto-generation from registry
src/Worker/Grpc/Internal/DurableTaskWorkerWorkItemFiltersExtension.cs Extension to convert SDK filters to gRPC protobuf types
src/Worker/Grpc/GrpcDurableTaskWorker.cs Constructor updated to accept optional workItemFilters parameter
src/Worker/Grpc/GrpcDurableTaskWorker.Processor.cs Integration of filters into GetWorkItems request
src/Grpc/orchestrator_service.proto Protobuf definitions for WorkItemFilters and related filter types
test/Worker/Core.Tests/DependencyInjection/UseWorkItemFiltersTests.cs Comprehensive tests for filter registration and auto-generation scenarios

{
DurableTaskRegistry registry = provider.GetRequiredService<IOptionsMonitor<DurableTaskRegistry>>().Get(builder.Name);
DurableTaskWorkerOptions? options = provider.GetOptions<DurableTaskWorkerOptions>(builder.Name);
return new DurableTaskWorkerWorkItemFilters(registry, options);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious what the difference is between the following two options:

  1. I build a worker without configuring filtering (basically, what people are doing today)
  2. I build a worker with .UseWorkItemFilters() (no arguments)

Intuitively I wouldn't expect there to be a scenario where I'd use filters without any configuration, so I need a bit of help understanding the use case for option 2.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That auto-generates the filters for you based on what is registered in the DurableTaskRegistry. Would you prefer I had a different method like UseAutoGeneratedWorkItemFitlers()? I combined them into one because I didn't want someone to be able to specify filters and auto-gen at the same time.

entityActions.Add(new EntityFilter
{
// Entity names are normalized to lowercase in the backend.
Name = entity.Key.ToString().ToLowerInvariant(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the backend is already normalizing these, do we need to lower-case them here? I worry a bit about operations like this since I've frequently seen them lead to subtle bugs. My personal preference is that we instead rely on case-insensitive string comparisons everywhere.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll actually make sure I move this logic to the backend only. Right now, the backend is actually what forces the normalization so we can at least keep it all in one place/use case-insensitive comparisons where appropriate.

Signed-off-by: Hal Spang <halspang@microsoft.com>
@halspang halspang force-pushed the halspang/workitemfilters branch from 1e69535 to 787ecb2 Compare January 15, 2026 22:55
Copilot AI review requested due to automatic review settings January 15, 2026 22:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

}

/// <summary>
/// Struct specifying an orchestration filter.
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation says 'Struct' but these are declared as structs. However, the summary should use the standard format 'Represents an orchestration filter' or 'Specifies an orchestration filter' without explicitly mentioning 'Struct' since that's already clear from the type declaration itself.

Copilot uses AI. Check for mistakes.
}

/// <summary>
/// Struct specifying an activity filter.
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation says 'Struct' but this is already clear from the type declaration. The summary should use the standard format 'Represents an activity filter' or 'Specifies an activity filter'.

Copilot uses AI. Check for mistakes.
}

/// <summary>
/// Struct specifying an entity filter.
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation says 'Struct' but this is already clear from the type declaration. The summary should use the standard format 'Represents an entity filter' or 'Specifies an entity filter'.

Suggested change
/// Struct specifying an entity filter.
/// Specifies an entity filter.

Copilot uses AI. Check for mistakes.
Comment on lines +158 to +163
builder.Services.AddSingleton(provider =>
{
DurableTaskRegistry registry = provider.GetRequiredService<IOptionsMonitor<DurableTaskRegistry>>().Get(builder.Name);
DurableTaskWorkerOptions? options = provider.GetOptions<DurableTaskWorkerOptions>(builder.Name);
return DurableTaskWorkerWorkItemFilters.FromDurableTaskRegistry(registry, options);
});
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AddSingleton registration adds filters without keying them to the builder name, which could cause issues when multiple named workers are configured with different filters. The registration should be keyed to the builder name similar to how other named options are handled. Consider using AddKeyedSingleton or a similar pattern to ensure each worker gets its own filter instance. The test 'UseWorkItemFilters_NamedBuilders_HaveUniqueFilters' (line 147-167 in the test file) may be passing due to test implementation details rather than correct DI registration.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +49
foreach (var entityFilter in workItemFilter.Entities)
{
var grpcEntityFilter = new P.EntityFilter
{
Name = entityFilter.Name,
};
grpcWorkItemFilters.Entities.Add(grpcEntityFilter);
}
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants