-
Notifications
You must be signed in to change notification settings - Fork 48
Worker Deployment Versioning #466
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
Conversation
8232611 to
495e196
Compare
|
Ready for review, but will have one small addition of version override options on start workflow after temporalio/api#579 is merged |
5d7f03c to
567c662
Compare
| /// <c>Assembly.GetEntryAssembly().ManifestModule.ModuleVersionId</c>. | ||
| /// </summary> | ||
| /// <remarks>Exclusive with <see cref="DeploymentOptions"/>.</remarks> | ||
| [Obsolete("Use DeploymentOptions instead")] |
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.
Is this obsolete? People may still want this build ID even without versioning, correct?
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.
We're going to move people towards using a deployment, even if versioning is off.
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.
I see. Then I think the AddHostedTemporalWorker(this IServiceCollection services, string taskQueue, string? buildId = null) overload of the DI creator should be delegating to the WorkerDeploymentOptions overload instead of setting this option.
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.
It can't - it would have to make up a deployment name. I'm deprecating it per the other comment (let's try to keep related ones like that together on one thread).
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.
That was my concern with deprecating BuildId, we're deprecating BuildId-only without a replacement. People use this field and now they're going to have to set their code to ignore deprecation because they don't have a dot in their build ID where they can convert. I'm not sure it's fair to make people make up a deployment name for no reason or benefit to them just to use build ID.
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.
That's a product decision, I'm just implementing it. At the end of the day I agree with it though. Defining an application/deployment name isn't exactly a huge burden. You do it one time and you're done.
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.
It's not whether it's a burden, it's whether it's necessary for us to retain the existing Temporal behavior they are used to that doesn't relate to this. I can bring this up internally, not blocking by any means.
| } | ||
| } | ||
|
|
||
| public static async Task<Temporalio.Api.WorkflowService.V1.SetWorkerDeploymentRampingVersionResponse> |
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.
Arguably this utility and that last one doesn't provide much value vs inlining the call heh
| }); | ||
| } | ||
|
|
||
| public static async Task<Temporalio.Api.WorkflowService.V1.DescribeWorkerDeploymentResponse> |
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.
I'd personally toss this in AssertMore and call it EventuallyWorkerDeploymentPresentAsync or something, but no biggie
cretz
left a comment
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.
Added a few more comments based on recent updates, but older unaddressed comments may still apply
| public static ITemporalWorkerServiceOptionsBuilder AddHostedTemporalWorker( | ||
| this IServiceCollection services, string taskQueue, string? buildId = null) | ||
| { | ||
| // TODO: Review - We probably need to deprecate buildId param & add deployment version? |
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.
If the BuildId worker option is deprecated, this should be too IMO
| #pragma warning disable 0618 | ||
| options.BuildId = buildId; | ||
| #pragma warning restore 0618 |
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.
If this is deprecated, does that mean that there's a way to use WorkerDeploymentOptions to represent "just build ID"? Otherwise, how do I represent just build ID in a non-deprecated way if I don't care about deployment name and won't ever enable versioning? And if I can do that, why does this not just call that overload?
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.
Yes, it means that, just Build ID is deprecated. You use a fully-formed deployment name now
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.
I think this is a bit rough on users who shouldn't have to care about deployment name to just have their existing build ID functionality remain
| /// </summary> | ||
| /// <param name="taskQueue">Task queue.</param> | ||
| /// <param name="buildId">Build ID.</param> | ||
| /// <param name="version">Worker Depoyment version or Build ID.</param> |
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.
| /// <param name="version">Worker Depoyment version or Build ID.</param> | |
| /// <param name="version">Worker Deployment version or Build ID.</param> |
| IOptionsMonitor<TemporalWorkerServiceOptions> optionsMonitor, | ||
| ITemporalClient? existingClient = null, | ||
| ILoggerFactory? loggerFactory = null) | ||
| : this( |
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.
If the BuildId worker option is deprecated, this constructor should be too IMO
| } | ||
| else | ||
| { | ||
| if (options.DeploymentOptions?.Version?.ToCanonicalString() != serviceId.Version) |
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.
Should there be a check to ensure options.BuildId is null and options.UseVersioning is false here? Meaning, if they are using new versioning, shouldn't they not be setting the old values away from their default?
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.
I don't think there needs to be, because if the versions match, then they couldn't possibly have set the other options on options, because they're exclusive.
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.
I think my concern was if I was using new deployment options and have manually set old build ID, but I guess worker creation throws for that, no worries
| /// Gets the options defined by the <see cref="WorkflowDynamicOptionsAttribute" /> annotated | ||
| /// method, if any. | ||
| /// </summary> | ||
| WorkflowDefinitionOptions? DynamicDefinitionOptions { get; } |
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.
I don't think we need to expose this to users
| Console.WriteLine("Established versioning behavior: " + establishedVersioningBehavior); | ||
| Console.WriteLine("Dynamic versioning behavior: " + dynamicDefinitionOptions?.VersioningBehavior); |
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.
Leftover stuff
| /// <see cref="Exceptions.FailureException" /> + cancellation and suspend via task failure | ||
| /// all others. But this default may change in the future. | ||
| /// </remarks> | ||
| public Type[]? FailureExceptionTypes { get; init; } |
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.
I think options should be get/set for those using builder patterns (otherwise records are better because at least you can with them)
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public WorkflowDefinitionOptions? DynamicDefinitionOptions |
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.
I was thinking this would be more like:
internal WorkflowDefinitionOptions DefinitionOptions
{
get
{
if (definitionOptions == null)
{
definitionOptions = new()
{
FailureExceptionTypes = Definition.FailureExceptionTypes,
VersioningBehavior = Definition.VersioningBehavior,
};
if (Definition.DynamicOptionsMethod != null)
{
var dynOptions = Definition.DynamicOptionsMethod.Invoke(Instance);
if (dynOptions.FailureExceptionTypes != null)
{
definitionOptions.FailureExceptionTypes = dynOptions.FailureExceptionTypes;
}
if (dynOptions.VersioningBehavior != VersioningBehavior.Unspecified)
{
definitionOptions.VersioningBehavior = dynOptions.VersioningBehavior;
}
}
}
return definitionOptions;
}
}and then DefinitionOptions can be used everywhere in code here and elsewhere. Though I could understand concern of it being accessed before instance made available. I wasn't expecting it to be user-facing.
And if you're always going to be initializing this in one place, no need to make it lazy.
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.
Ah, ok, I get what you mean now, use that as a bag for the things set on definition too, sure, makes sense.
It can't exactly work this way because the failure types on the definition can be used before initialization ever even happens, as in ExecuteWorkflowAsync_BadInput_CanFailWorkflow, which was fun to discover. The idea is pretty much the same still though.
| // Dynamic options method needs to be invoked at this point, after initting the | ||
| // workflow instance but before performing any activations. | ||
| _ = DynamicDefinitionOptions; |
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.
If you're always running this anyways, then you don't need to make it lazy IMO, can just set the definition options right here. Before I didn't think you cared about when you called the dynamic definition method, but I can see now it is better to be predictable. My concern was what happens if the dynamic options method accesses Workflow.Instance per #466 (comment)?
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.
Yeah, that was exactly the point of doing it inline there. It needs to happen at the right time
0c196a5 to
6fa38e5
Compare
| string clientNamespace, | ||
| string taskQueue, | ||
| string? buildId = null) => | ||
| string buildId) => |
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.
In order to make these obsolete, I had to make them not take null for the last param, and instead have that happen on the new one. I believe that should be a compatible change still
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.
With C# named parameters, it's technically not compatible since a user could have had buildId: null. Can you leave the string? type here (can still make the parameter required I believe).
a34bcc0 to
04bf08e
Compare
cretz
left a comment
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.
Only piddly things remain I think then we're good
| private WorkflowUpdateDefinition? dynamicUpdate; | ||
| private bool workflowInitialized; | ||
| private bool applyModernEventLoopLogic; | ||
| private WorkflowDefinitionOptions definitionOptions; |
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.
| private WorkflowDefinitionOptions definitionOptions; | |
| private readonly WorkflowDefinitionOptions definitionOptions; |
(and move to the readonly set of fields, unless I'm missing something)
| /// Additional options for a workflow definition that may be returned by | ||
| /// a function annotated with <see cref="WorkflowDynamicOptionsAttribute"/>. | ||
| /// </summary> | ||
| public class WorkflowDefinitionOptions |
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.
Can this be cloneable? Not really important, just a pattern we've been doing for "options"
| RunOnce(checkConditions); | ||
| } | ||
|
|
||
| if (definitionOptions?.VersioningBehavior != null) |
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.
I don't think this condition could ever evaluate to false now
| e is FailureException || | ||
| e is OperationCanceledException || | ||
| Definition.FailureExceptionTypes?.Any(t => t.IsAssignableFrom(e.GetType())) == true || | ||
| definitionOptions?.FailureExceptionTypes?.Any(t => t.IsAssignableFrom(e.GetType())) == true || |
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.
| definitionOptions?.FailureExceptionTypes?.Any(t => t.IsAssignableFrom(e.GetType())) == true || | |
| definitionOptions.FailureExceptionTypes?.Any(t => t.IsAssignableFrom(e.GetType())) == true || |
|
|
||
| if (options.DeploymentOptions?.UseWorkerVersioning == true) | ||
| { | ||
| if (options.DeploymentOptions?.DefaultVersioningBehavior is null or VersioningBehavior.Unspecified |
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.
Interesting, I have not used or in this project before, but it is C# 9, so works
| /// <summary> | ||
| /// Gets the dynamic options method. | ||
| /// </summary> | ||
| public Func<object?, WorkflowDefinitionOptions>? DynamicOptionsMethod { get; private init; } |
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.
Arguably not a "method" like "runMethod", more like a DynamicOptionsGetter like "creator", but doesn't really matter
cretz
left a comment
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.
LGTM, thanks! The concern over deprecating build ID for those users that don't care about deployment name is not .NET specific, so doesn't really affect this PR specifically.
|
Like TS, want to get temporalio/api#579 in so I can use it in this before merging, so I don't have to go back immediately after |
|
No prob, can leave this hanging open as long as needed. |
2844cf0 to
944a82c
Compare
dc17057 to
f43760f
Compare
cretz
left a comment
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.
Semantic things only
| public sealed record PinnedVersioningOverride( | ||
| WorkerDeploymentVersion Version, | ||
| PinnedOverrideBehavior Behavior = PinnedOverrideBehavior.Pinned) : VersioningOverride; | ||
|
|
||
| /// <summary> | ||
| /// The workflow will auto-upgrade to the current deployment version on the next workflow | ||
| /// task. | ||
| /// </summary> | ||
| public sealed record AutoUpgradeVersioningOverride() : VersioningOverride; |
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.
| public sealed record PinnedVersioningOverride( | |
| WorkerDeploymentVersion Version, | |
| PinnedOverrideBehavior Behavior = PinnedOverrideBehavior.Pinned) : VersioningOverride; | |
| /// <summary> | |
| /// The workflow will auto-upgrade to the current deployment version on the next workflow | |
| /// task. | |
| /// </summary> | |
| public sealed record AutoUpgradeVersioningOverride() : VersioningOverride; | |
| public sealed record Pinned( | |
| WorkerDeploymentVersion Version, | |
| PinnedOverrideBehavior Behavior = PinnedOverrideBehavior.Pinned) : VersioningOverride; | |
| /// <summary> | |
| /// The workflow will auto-upgrade to the current deployment version on the next workflow | |
| /// task. | |
| /// </summary> | |
| public sealed record AutoUpgrade() : VersioningOverride; |
Probably don't need the VersioningOverride suffix on these
| /// <summary> | ||
| /// Specifies different sub‐types of pinned override. | ||
| /// </summary> | ||
| public enum PinnedOverrideBehavior |
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.
Could also move this to be an inner class of Pinned called Behavior, leaving the only direct inner types of VersioningOverride to be implementations of it. Usually .NET doesn't have so many nested types, but in this case it makes sense I think
What was changed
Added the annotations and options for worker-deployment-based versioning
Why?
New feature in all SDKs
Checklist
Closes [Feature Request] Support New Worker Versioning API #437
How was this tested:
New & existing tests
Any docs updates needed?