-
Notifications
You must be signed in to change notification settings - Fork 58
Add multi-version support to [DurableTask] annotations #751
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,14 +29,70 @@ public static TaskName GetTaskName(this Type type) | |
| /// </summary> | ||
| /// <param name="type">The type to get the durable task version for.</param> | ||
| /// <returns>The durable task version.</returns> | ||
| /// <remarks> | ||
| /// When the <see cref="DurableTaskAttribute.Version"/> declares multiple comma-separated versions, this | ||
| /// returns only the first declared version. Prefer <see cref="GetDurableTaskVersions"/> when all declared | ||
| /// versions are needed (for example, when registering a type under every version it supports). | ||
| /// </remarks> | ||
| internal static TaskVersion GetDurableTaskVersion(this Type type) | ||
| { | ||
| // IMPORTANT: This logic needs to be kept consistent with the source generator logic. | ||
| Check.NotNull(type); | ||
| return Attribute.GetCustomAttribute(type, typeof(DurableTaskAttribute)) switch | ||
| IReadOnlyList<TaskVersion> versions = type.GetDurableTaskVersions(); | ||
| return versions.Count > 0 ? versions[0] : default; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets every durable task version declared for a type via <see cref="DurableTaskAttribute.Version"/>. | ||
| /// </summary> | ||
| /// <param name="type">The type to get the durable task versions for.</param> | ||
| /// <returns> | ||
| /// The distinct (case-insensitive) set of declared versions in declaration order. An unversioned type | ||
| /// (no attribute, or an empty/unset <see cref="DurableTaskAttribute.Version"/>) yields a single | ||
| /// <see cref="TaskVersion.Unversioned"/> entry so callers can always register at least once. | ||
| /// </returns> | ||
| /// <exception cref="ArgumentException"> | ||
| /// Thrown when any comma-separated entry is whitespace-only. This mirrors the source generator's | ||
| /// <c>DURABLE3005</c> diagnostic so the reflection-based registration path fails closed for types whose | ||
| /// attribute the generator did not see. | ||
| /// </exception> | ||
| internal static IReadOnlyList<TaskVersion> GetDurableTaskVersions(this Type type) | ||
| { | ||
| // IMPORTANT: This logic needs to be kept consistent with the source generator logic. | ||
| Check.NotNull(type); | ||
| if (Attribute.GetCustomAttribute(type, typeof(DurableTaskAttribute)) is not DurableTaskAttribute attr | ||
| || string.IsNullOrEmpty(attr.Version)) | ||
| { | ||
| DurableTaskAttribute { Version: not null and not "" } attr => new TaskVersion(attr.Version), | ||
| _ => default, | ||
| }; | ||
| return new[] { TaskVersion.Unversioned }; | ||
| } | ||
|
|
||
| List<TaskVersion> versions = new(); | ||
| foreach (string segment in attr.Version!.Split(',')) | ||
| { | ||
| if (segment.Length == 0) | ||
| { | ||
| // Truly-empty entry (e.g. a trailing or doubled comma). Skip silently. | ||
| continue; | ||
| } | ||
|
|
||
| string trimmed = segment.Trim(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This trims a single version value that was previously preserved verbatim. For example, |
||
| if (trimmed.Length == 0) | ||
| { | ||
| // Whitespace-only entry. Fail closed, consistent with the TaskVersion constructor and the | ||
| // source generator's DURABLE3005 diagnostic. | ||
| throw new ArgumentException( | ||
| "A [DurableTask] Version entry must not be whitespace-only. Provide non-empty version " + | ||
| "values or omit the Version argument to declare an unversioned task.", | ||
| nameof(type)); | ||
| } | ||
|
|
||
| TaskVersion version = new(trimmed); | ||
| if (!versions.Contains(version)) | ||
| { | ||
| versions.Add(version); | ||
| } | ||
| } | ||
|
|
||
| return versions.Count > 0 ? versions : new[] { TaskVersion.Unversioned }; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fan-out mutates the registry as it goes. If
v2is already registered and the attribute declaresv1,v2,v1is added beforev2throws, so a failedAddActivitycall still changes dispatch state. Please preflight every key before adding any of them; the same atomicity fix is needed inAddOrchestratorAllVersions.