Skip to content

Add multi-version support to [DurableTask] annotations#751

Open
halspang wants to merge 1 commit into
microsoft:mainfrom
halspang:halspang/multi-version-annotations
Open

Add multi-version support to [DurableTask] annotations#751
halspang wants to merge 1 commit into
microsoft:mainfrom
halspang:halspang/multi-version-annotations

Conversation

@halspang

Copy link
Copy Markdown
Member

Summary

What changed?

The Version property now accepts a comma-separated list (e.g. "v1,v2"), registering the type under each declared version and emitting call helpers that take a required 'version' parameter validated against the declared set. Single-version and unversioned behavior is unchanged.

Why is this change needed?

This allows customers to specify multiple versions of a single orchestration/activity without having to duplicate the code in their own codebase.

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): Copilot
  • AI-assisted areas/files: Mostly tests, some of the parsing logic
  • What you changed after AI output: Filtering logic

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):
  • Steps + observed results:
    1. App with multiple versions declared
    2. Filters had correct versions
    3. Correct app served correct versions
  • Evidence (optional):

Notes for reviewers

  • N/A

Copilot AI review requested due to automatic review settings June 18, 2026 16:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds multi-version support to [DurableTask] version annotations across reflection-based registration and the source generator, and updates worker work-item filter generation to respect CurrentOrOlder versioning semantics.

Changes:

  • Parse comma-separated DurableTaskAttribute.Version values into a distinct ordered version set and register tasks under each declared version.
  • Update the source generator to emit multi-version call helpers that require a version parameter with runtime validation.
  • Narrow auto-generated worker work-item filters to versions <= workerVersion when VersionMatchStrategy.CurrentOrOlder is enabled, with expanded test coverage.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
CHANGELOG.md Documents the new multi-version attribute support and filter narrowing behavior.
src/Abstractions/DurableTaskAttribute.cs Updates XML docs to describe comma-separated multi-version semantics and validation rules.
src/Abstractions/TypeExtensions.cs Adds reflection-side parsing for multi-version attributes (GetDurableTaskVersions) and preserves GetDurableTaskVersion semantics.
src/Abstractions/DurableTaskRegistry.Orchestrators.cs Fans out orchestrator type/singleton registrations to all declared versions.
src/Abstractions/DurableTaskRegistry.Activities.cs Fans out activity type/singleton registrations to all declared versions.
src/Abstractions/DurableTaskRegistry.cs Adds shared “register across all versions” helper for activities.
src/Generators/DurableTaskSourceGenerator.cs Implements multi-version parsing, deduplication, collision detection, and version-parameter helper generation with runtime guards.
src/Worker/Core/DurableTaskWorkerWorkItemFilters.cs Narrows generated versions for CurrentOrOlder by comparing against worker version.
test/Abstractions.Tests/DurableTaskAttributeVersionTests.cs Adds tests for multi-version parsing, trimming/dedup, unversioned behavior, and whitespace-only entry rejection.
test/Abstractions.Tests/DurableTaskRegistryVersioningTests.cs Adds tests ensuring multi-version types register under each version and collisions are rejected.
test/Generators.Tests/VersionedOrchestratorTests.cs Adds generator snapshot test for multi-version orchestrators producing version-parameter helpers and guards.
test/Generators.Tests/VersionedActivityTests.cs Adds generator snapshot test for multi-version activities producing version-parameter helpers and guards.
test/Worker/Core.Tests/DependencyInjection/UseWorkItemFiltersTests.cs Adds CurrentOrOlder filter tests that validate dropping newer versions and retaining unversioned + older versions.
test/Worker/Core.Tests/DurableTaskFactoryVersioningTests.cs Adds factory resolution test covering coexistence of unversioned and multi-version activity registrations.
Comments suppressed due to low confidence (1)

src/Worker/Core/DurableTaskWorkerWorkItemFilters.cs:118

  • Under MatchStrategy.CurrentOrOlder, workerVersion filtering can remove all versioned registrations for a name, leaving only the unversioned "" entry. The current normalized.Length == 1 && normalized[0].Length == 0 check then returns an empty Versions list (wildcard), which widens the backend filter and can cause newer versioned work items to be streamed (and then rejected) even though the registry originally had versioned registrations (which disable unversioned fallback). Consider emitting the wildcard only when the original registrations for the name were truly unversioned-only (no non-empty versions) before filtering; otherwise return [""] to restrict to unversioned only.
            // Unversioned-only: emit the wildcard match-all (empty list) so the backend can deliver
            // versioned work items that the factory will resolve via unversioned fallback. Without
            // this, callers asking for a specific version would be filtered out at the backend even
            // though the worker can handle them.
            if (normalized.Length == 1 && normalized[0].Length == 0)
            {
                return [];
            }

Comment thread CHANGELOG.md Outdated
# Changelog

## Unreleased
- Support declaring multiple versions in a single `[DurableTask]` attribute via a comma-separated list (for example `[DurableTask("MyActivity", Version = "1.0.0,1.1.0")]`); each version is registered and plumbed through code generation. Narrow auto-generated work item filters to current-or-older versions under the `CurrentOrOlder` match strategy.
@halspang halspang force-pushed the halspang/multi-version-annotations branch from d324252 to 5c4eda7 Compare June 18, 2026 17:04
The Version property now accepts a comma-separated list (e.g. "v1,v2"),
registering the type under each declared version and emitting call helpers
that take a required 'version' parameter validated against the declared set.
Single-version and unversioned behavior is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 18, 2026 17:06
@halspang halspang force-pushed the halspang/multi-version-annotations branch from 5c4eda7 to dd42561 Compare June 18, 2026 17:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 14 out of 14 changed files in this pull request and generated 1 comment.

Comment on lines +101 to 110
static IReadOnlyList<string> GetFilterVersions(IEnumerable<string?> versions, string? workerVersion)
{
// Normalize null to "" so an unversioned registration appears consistently.
string[] normalized = versions
.Select(version => version ?? string.Empty)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(version => version, StringComparer.OrdinalIgnoreCase)
.Where(version => workerVersion == null || TaskOrchestrationVersioningUtils.CompareVersions(workerVersion, version) >= 0)
.ToArray();

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