From 4ee4595716ec3dad0b57abab14ec27464a668329 Mon Sep 17 00:00:00 2001 From: Erica Sadun Date: Wed, 22 Jan 2025 09:21:55 -0700 Subject: [PATCH 01/13] EDU-3824: Add best practice advice to Typescript Versioning Source of truth: Grant Smith --- docs/develop/typescript/versioning.mdx | 82 ++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/docs/develop/typescript/versioning.mdx b/docs/develop/typescript/versioning.mdx index 406b4e7f8e..7af33e4064 100644 --- a/docs/develop/typescript/versioning.mdx +++ b/docs/develop/typescript/versioning.mdx @@ -179,6 +179,88 @@ Patching is a three-step process: 2. Remove old code and `deprecatePatch` 3. When you are sure all old Workflows are done executing, remove `deprecatePatch` +#### Overview + +The following sample shows how the `patched` function behaves, providing explanations at each stage of the patching flow: + + + +```ts +if (patched('v3')) { + // This is the newest version of the code. + + // The above patched statement following will do + // one of the following three things: + + // 1. If the execution is not Replaying, it will evaluate + // to True and write a Marker Event to the history + // with a patch id v3. This code block will run. + // 2. If the execution is Replaying, and the original + // run put a Patch ID v3 at this location in the event + // history, it will evaluate to True, and this code block + // will run. + // 3. If the execution is Replaying, and the original + // run has a Patch ID other than v3 at this location in the event + // history, it will evaluate to False, and this code block won't + // run. +} else if (patched('v2')) { + // This is the second version of the code. + + // The above patched statement following will do + // one of the following three things: + + // 1. If the execution is not Replaying, the execution + // won't get here because the first patched statement + // will be True. + // 2. If the execution is Replaying, and the original + // run put a Patch ID v2 marker at this location in the event + // history, it will evaluate to True, and this code block + // will run. + // 3. If the execution is Replaying, and the original + // run has a Patch ID other than v2 at this location in the event + // history, or doesn't have a patch marker at this location in the event + // history, it will evaluate to False, and this code block won't + // run. +} else { + // This is the original version of the code. + // + // The above patched statement following will do + // one of the following three things: + // + // 1. If the execution is not Replaying, the execution + // won't get here because the first patched statement + // will be True. + // 2. If the execution is Replaying, and the original + // run had a patch marker v3 or v2 at this location in the event + // history, the execution + // won't get here because the first or second patched statement + // will be True (respectively). + // 3. If the execution is Replaying, and condition 2 + // doesn't hold, then it will run this code. +} + ``` + + +Avoid pitfalls by carefully structuring your conditional checks in newest-first order. +The following sample shows a common mistake that ends up running the `v2` patch each time. +During your first run -- that is, before any Replays -- all `patched()` calls return `True`. +Make sure your latest behavior is chosen for all newly initiated Workflow Executions and avoid falling into the following trap: + + + +```ts +if (patched('v2')) { + // This is bad because when doing an original execution (i.e. not replaying), + // all patched statements evaluate to True (and put a marker + // in the event history), which means that new executions + // will use v2, and miss v3 below +} +else if (patched('v3')) {} +else {} +``` + + + #### Step 1: Patch in new code `patched` inserts a marker into the Workflow history. From 5db47f6a2351ca4a257195eb124cdd2411dcb86c Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Wed, 22 Jan 2025 14:05:33 -0600 Subject: [PATCH 02/13] addition to Erica's initial notes --- docs/develop/dotnet/versioning.mdx | 84 ++++++++++++++++++++++++ docs/develop/python/versioning.mdx | 90 +++++++++++++++++++++++++- docs/develop/typescript/versioning.mdx | 14 ++-- 3 files changed, 181 insertions(+), 7 deletions(-) diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx index 8b02cb5e54..cb04eca007 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/versioning.mdx @@ -108,6 +108,90 @@ Implementing patching involves three steps: 2. Remove the old code and apply [DeprecatePatch](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_DeprecatePatch_System_String_). 3. Once you're confident that all old Workflows have finished executing, remove `DeprecatePatch`. +#### Overview + +The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: + + + +```csharp +if (patched('v3')) { + // This is the newest version of the code. + + // The above patched statement following will do + // one of the following three things: + + // 1. If the execution is not Replaying, it will evaluate + // to true and write a Marker Event to the history + // with a patch id v3. This code block will run. + // 2. If the execution is Replaying, and the original + // run put a Patch ID v3 at this location in the event + // history, it will evaluate to True, and this code block + // will run. + // 3. If the execution is Replaying, and the original + // run has a Patch ID other than v3 at this location in the event + // history, it will evaluate to False, and this code block won't + // run. +} else if (patched('v2')) { + // This is the second version of the code. + + // The above patched statement following will do + // one of the following three things: + + // 1. If the execution is not Replaying, the execution + // won't get here because the first patched statement + // will be True. + // 2. If the execution is Replaying, and the original + // run put a Patch ID v2 marker at this location in the event + // history, it will evaluate to True, and this code block + // will run. + // 3. If the execution is Replaying, and the original + // run has a Patch ID other than v2 at this location in the event + // history, or doesn't have a patch marker at this location in the event + // history, it will evaluate to False, and this code block won't + // run. +} else { + // This is the original version of the code. + // + // The above patched statement following will do + // one of the following three things: + // + // 1. If the execution is not Replaying, the execution + // won't get here because the first patched statement + // will be True. + // 2. If the execution is Replaying, and the original + // run had a patch marker v3 or v2 at this location in the event + // history, the execution + // won't get here because the first or second patched statement + // will be True (respectively). + // 3. If the execution is Replaying, and condition 2 + // doesn't hold, then it will run this code. +} + ``` + + +To add more clarity, the following sample shows how `patched()` will behave in a different conditional block. +In this case, the code's conditional block doesn't have the newest code at the top. +Because `patched()` will always return `true` when not Replaying, this snippet will run the `v2` branch instead of `v3` in new executions. + + + +```csharp +if (patched('v2')) { + // This is bad because when doing an original execution (i.e. not replaying), + // all patched statements evaluate to True (and put a marker + // in the event history), which means that new executions + // will use v2, and miss v3 below +} +else if (patched('v3')) {} +else {} +``` + +The moral of the story is that when not Replaying, `patched()` will return true and write the patch ID to the Event History. +And when Replaying, it will only return true if the patch ID matches that in the Event History. + + + ### Patching in new code {#using-patched-for-workflow-history-markers} Using `Patched` inserts a marker into the Workflow History. diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index 86519e7baa..3c12ad439b 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -113,9 +113,97 @@ Implementing patching involves three steps: 2. Remove the old code and apply [deprecate_patch](https://python.temporal.io/temporalio.workflow.html#deprecate_patch). 3. Once you're confident that all old Workflows have finished executing, remove `deprecate_patch`. +#### Overview + +The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: + + + +```python +if patched('v3'): + # This is the newest version of the code. + + # The above patched statement following will do + # one of the following three things: + + # 1. If the execution is not Replaying, it will evaluate + # to true and write a Marker Event to the history + # with a patch id v3. This code block will run. + # 2. If the execution is Replaying, and the original + # run put a Patch ID v3 at this location in the event + # history, it will evaluate to True, and this code block + # will run. + # 3. If the execution is Replaying, and the original + # run has a Patch ID other than v3 at this location in the event + # history, it will evaluate to False, and this code block won't + # run. + pass +elif patched('v2'): + # This is the second version of the code. + + # The above patched statement following will do + # one of the following three things: + + # 1. If the execution is not Replaying, the execution + # won't get here because the first patched statement + # will be True. + # 2. If the execution is Replaying, and the original + # run put a Patch ID v2 marker at this location in the event + # history, it will evaluate to True, and this code block + # will run. + # 3. If the execution is Replaying, and the original + # run has a Patch ID other than v2 at this location in the event + # history, or doesn't have a patch marker at this location in the event + # history, it will evaluate to False, and this code block won't + # run. + pass +else: + # This is the original version of the code. + + # The above patched statement following will do + # one of the following three things: + + # 1. If the execution is not Replaying, the execution + # won't get here because the first patched statement + # will be True. + # 2. If the execution is Replaying, and the original + # run had a patch marker v3 or v2 at this location in the event + # history, the execution + # won't get here because the first or second patched statement + # will be True (respectively). + # 3. If the execution is Replaying, and condition 2 + # doesn't hold, then it will run this code. + pass + ``` + + +To add more clarity, the following sample shows how `patched()` will behave in a different conditional block. +In this case, the code's conditional block doesn't have the newest code at the top. +Because `patched()` will always return `True` when not Replaying, this snippet will run the `v2` branch instead of `v3` in new executions. + + + +```python +if patched('v2'): + # This is bad because when doing an original execution (i.e. not replaying), + # all patched statements evaluate to True (and put a marker + # in the event history), which means that new executions + # will use v2, and miss v3 below + pass +elif patched('v3'): + pass +else: + pass +``` + +The moral of the story is that when not Replaying, `patched()` will return `True` and write the patch ID to the Event History. +And when Replaying, it will only return true if the patch ID matches that in the Event History. + + + ### Patching in new code {#using-patched-for-workflow-history-markers} -Using `patched` inserts a marker into the Workflow History. +Using `patched()` inserts a marker into the Workflow History. ![image](https://user-images.githubusercontent.com/6764957/139673361-35d61b38-ab94-401e-ae7b-feaa52eae8c6.png) diff --git a/docs/develop/typescript/versioning.mdx b/docs/develop/typescript/versioning.mdx index 7af33e4064..182342785c 100644 --- a/docs/develop/typescript/versioning.mdx +++ b/docs/develop/typescript/versioning.mdx @@ -181,7 +181,7 @@ Patching is a three-step process: #### Overview -The following sample shows how the `patched` function behaves, providing explanations at each stage of the patching flow: +The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: @@ -193,7 +193,7 @@ if (patched('v3')) { // one of the following three things: // 1. If the execution is not Replaying, it will evaluate - // to True and write a Marker Event to the history + // to true and write a Marker Event to the history // with a patch id v3. This code block will run. // 2. If the execution is Replaying, and the original // run put a Patch ID v3 at this location in the event @@ -241,10 +241,9 @@ if (patched('v3')) { ``` -Avoid pitfalls by carefully structuring your conditional checks in newest-first order. -The following sample shows a common mistake that ends up running the `v2` patch each time. -During your first run -- that is, before any Replays -- all `patched()` calls return `True`. -Make sure your latest behavior is chosen for all newly initiated Workflow Executions and avoid falling into the following trap: +To add more clarity, the following sample shows how `patched()` will behave in a different conditional block. +In this case, the code's conditional block doesn't have the newest code at the top. +Because `patched()` will always return `true` when not Replaying, this snippet will run the `v2` branch instead of `v3` in new executions. @@ -259,6 +258,9 @@ else if (patched('v3')) {} else {} ``` +The moral of the story is that when not Replaying, `patched()` will return true and write the patch ID to the Event History. +And when Replaying, it will only return true if the patch ID matches that in the Event History. + #### Step 1: Patch in new code From 916d80c72d2f53cb7b9c48d1ff7252e3fc92d040 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Wed, 22 Jan 2025 15:21:09 -0600 Subject: [PATCH 03/13] added best practice of accepting and returning single objects --- docs/develop/dotnet/versioning.mdx | 8 ++++++++ docs/develop/python/versioning.mdx | 8 ++++++++ docs/develop/typescript/versioning.mdx | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx index cb04eca007..d8bd51745b 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/versioning.mdx @@ -267,3 +267,11 @@ public class MyWorkflow } } ``` + +### Best Practice of Using Classes as Arguments and Returns + +As a side note on the Patching API, its behavior is why Temporal recommends using a single object as arguments and returns from Signals, Queries, Updates, and Activities, rather than using multiple arguments/returns. +The Patching API's main use case is to support branching in an `if` block of a method body. +It is not designed to be used to set different methods or method signatures for different Workflow Versions. + +Because of this, Temporal recommends that each Signal, Activity, etc, accepts a single object and returns a single object, so the method signature can stay constant, and you can do your versioning logic using `patched()` within the method body. diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index 3c12ad439b..78499dbe50 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -287,6 +287,14 @@ class MyWorkflow: ) ``` +### Best Practice of Using Python Dataclasses as Arguments and Returns + +As a side note on the Patching API, its behavior is why Temporal recommends using single dataclasses as arguments and returns from Signals, Queries, Updates, and Activities, rather than using multiple arguments. +The Patching API's main use case is to support branching in an `if` block of a method body. +It is not designed to be used to set different methods or method signatures for different Workflow Versions. + +Because of this, Temporal recommends that each Signal, Activity, etc, accepts a single dataclass and returns a single dataclass, so the method signature can stay constant, and you can do your versioning logic using `patched()` within the method body. + ## How to use Worker Versioning in Python {#worker-versioning} :::caution diff --git a/docs/develop/typescript/versioning.mdx b/docs/develop/typescript/versioning.mdx index 182342785c..c1fbd3ac59 100644 --- a/docs/develop/typescript/versioning.mdx +++ b/docs/develop/typescript/versioning.mdx @@ -322,6 +322,14 @@ export async function myWorkflow(): Promise { `vFinal` is safe to deploy once all `v2` or earlier Workflows are complete due to the assertion mentioned above. +### Best Practice of Using TypeScript Objects as Arguments and Returns + +As a side note on the Patching API, its behavior is why Temporal recommends using single objects as arguments and returns from Signals, Queries, Updates, and Activities, rather than using multiple arguments. +The Patching API's main use case is to support branching in an `if` block of a function body. +It is not designed to be used to set different functions or function signatures for different Workflow Versions. + +Because of this, Temporal recommends that each Signal, Activity, etc, accepts a single object and returns a single object, so the function signature can stay constant, and you can do your versioning logic using `patched()` within the function body. + ### Upgrading Workflow dependencies Upgrading Workflow dependencies (such as ones installed into `node_modules`) _might_ break determinism in unpredictable ways. From 0a0dacee781d59daf88ed42acdbb10174f2717c6 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Mon, 3 Feb 2025 09:42:25 -0600 Subject: [PATCH 04/13] added youtube tutorials to versioning docs --- docs/develop/dotnet/versioning.mdx | 14 +++++++++++++- docs/develop/python/versioning.mdx | 24 +++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx index d8bd51745b..812a17c9ae 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/versioning.mdx @@ -36,7 +36,7 @@ This page shows how to do the following: ## Introduction to Versioning -Because we design for potentially long running Workflows at scale, versioning with Temporal works differently. We explain more in this optional 30 minute introduction: +Because we design for potentially long running Workflows at scale, versioning with Temporal works differently. We explain more in this optional 30 minute introduction...
+
+ ## Use the .NET SDK Patching API {#dotnet-sdk-patching-api} **How to use the .NET SDK Patching API using the Temporal .NET SDK** diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index 78499dbe50..d62437cf42 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -35,7 +35,29 @@ a non-deterministic issue if not handled correctly. ## Introduction to Versioning -Because we design for potentially long running Workflows at scale, versioning with Temporal works differently. We explain more in this optional 30 minute introduction: [https://www.youtube.com/watch?v=kkP899WxgzY](https://www.youtube.com/watch?v=kkP899WxgzY) +Because we design for potentially long running Workflows at scale, versioning with Temporal works differently. We explain more in this optional 30 minute introduction... + +
+ +
+ +... and this optional 37 minute YouTube series that takes a deep dive into the patched function. + +
+ +
## How to use the Python SDK Patching API {#python-sdk-patching-api} From ba83ccfbd094b1978c89c7d637f694be9fbe7f03 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Mon, 3 Feb 2025 10:12:49 -0600 Subject: [PATCH 05/13] moved the youtube playlist down to the patched section --- docs/develop/dotnet/versioning.mdx | 27 ++++++++++++++------------- docs/develop/python/versioning.mdx | 27 ++++++++++++++------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx index 812a17c9ae..b35d6b9f08 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/versioning.mdx @@ -36,7 +36,7 @@ This page shows how to do the following: ## Introduction to Versioning -Because we design for potentially long running Workflows at scale, versioning with Temporal works differently. We explain more in this optional 30 minute introduction... +Because we design for potentially long running Workflows at scale, versioning with Temporal works differently. We explain more in this optional 30 minute introduction:
-
- ## Use the .NET SDK Patching API {#dotnet-sdk-patching-api} **How to use the .NET SDK Patching API using the Temporal .NET SDK** @@ -122,6 +110,19 @@ Implementing patching involves three steps: #### Overview + +We take a deep dive into the behavior of the `patched()` function in this optional 37 minute YouTube series: + +
+ +
+ The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index d62437cf42..0870a8c4df 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -35,7 +35,7 @@ a non-deterministic issue if not handled correctly. ## Introduction to Versioning -Because we design for potentially long running Workflows at scale, versioning with Temporal works differently. We explain more in this optional 30 minute introduction... +Because we design for potentially long running Workflows at scale, versioning with Temporal works differently. We explain more in this optional 30 minute introduction:
-
- ## How to use the Python SDK Patching API {#python-sdk-patching-api} In principle, the Python SDK's patching mechanism operates similarly to other SDKs in a "feature-flag" fashion. However, the "versioning" API now uses the concept of "patching in" code. @@ -137,6 +125,19 @@ Implementing patching involves three steps: #### Overview +We take a deep dive into the behavior of the `patched()` function in this optional 37 minute YouTube series: + +
+ +
+ + The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: From 7b5113e7d0e3c32396b8f6c4ce3a91ee140746f9 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Mon, 3 Feb 2025 11:56:50 -0600 Subject: [PATCH 06/13] added writeup on patched function behavior --- docs/develop/dotnet/versioning.mdx | 120 +++++++++++++++++++++++++ docs/develop/python/versioning.mdx | 118 ++++++++++++++++++++++++ docs/develop/typescript/versioning.mdx | 74 +++++++++++++++ 3 files changed, 312 insertions(+) diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx index b35d6b9f08..f5a23cb94d 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/versioning.mdx @@ -123,6 +123,126 @@ We take a deep dive into the behavior of the `patched()` function in this option +And here is a detailed explanation of how the `patched()` function behaves. + +##### Behavior When Not Replaying + +If not replaying, and the execution hits a call to patched, it first checks the event history, and: + +- If the patch ID is not in the event history, it will add a marker to the event history and upsert a search attribute (you can think of this like the first block of patching with a given patch ID). +- If the patch ID is in the event history, it won't modify the history (you can think of this like the second block of patching of a given patch ID). + +In either case, it will return true + +> There is a caveat to the above, and we will discuss that below. + +##### Behavior When Replaying With Marker Before-Or-At Current Location + +If Replaying: + +- If the code has a call to patched, and if the event history + has a marker from a call to patched in the same place (which means it + will match the original event history), then + write a marker to the replay event history and return true. + *This is similar to the behavior of the non-replay case, and + just like in that one, you can think of this like the first block of patching with + a given patch ID* +- If the code has a call to patched, and the event history + has a marker with that Patch ID earlier in the history, + then it will simply return true and not modify the + replay event history. + *This is similar to the behavior of the non-replay case, and just + like in that case, you can think of this like the second + block of patching of a given patch ID* + +##### Behavior When Replaying With Marker After Current Location or No Marker at All + +We have covered what happens when replaying and the code +hits a call to patched and there's a marker in the event +history on or before that spot in the execution. What remains +is what happens if (1) the marker is after that spot in the +execution or (2) if there is no marker at all for that patch. + +###### (1) There is a marker after that spot in the execution + +If the event is after where we currently are +in the event history, then, in other words, +our patch is before the +event, then our patch is too early. it will +attempt to write the marker to the replay event +history, but it will throw a non-deterministic +exception because the replay and original event +histories don't match + +###### (2) There is no marker for that Patch ID + +It will return false and not add anything to +the event history. Furthermore, ***and this is the +caveat mentioned in the very first section***, it will make all future calls to patched +with that ID false -- even after it is done replaying +and is running new code. + +Why is this a caveat? + +In the first section, we said that ***if not replaying, +the patched function will always return true***, and if +the marker doesn't exist, it will add it, and if +the marker already exists, it won't re-add it. + +But what this +is saying is that this doesn't hold if there was already +a call to patched with that ID in the replay code, but not +in the event history. In this situation, it won't return +true. + +##### A Summary of the Two Potentially Unexpected Behaviors + +1. When Replaying, in the scenario of ***it hits a call to + patched, but that patch ID isn't before/on that point in + the event history***, you may not expect that + the event history *after* where you currently + are matters. Because: + 1. If that patch ID exists later, you get an NDE [(see above: (1) There is a marker after that spot in the execution)](#1-there-is-a-marker-after-that-spot-in-the-execution). + 2. If it doesn't exist later, you don't get an NDE, and + it returns false + [(see above: (2) There is no marker for that Patch ID)](#2-there-is-no-marker-for-that-patch-id). + +2. When Replaying, if you hit a call to patched with an ID that + doesn't exist in the history, then not only will it return + false in that occurence, but it will also return false if + the execution surpasses the Replay threshold and is running new code. + [(see above: (2) There is no marker for that Patch ID)](#2-there-is-no-marker-for-that-patch-id). + + (This doesn't happen in TS -- it will + return false in that occurence, but this doesn't modify the behavior + of future calls). + +##### Implications of the Behaviors + +If you deploy new code while a worker is down, +any workflows that were in the middle of executing will replay +using old code and then for the rest of the execution, they +will either: + +1. Use new code if there was no call to patched in the replay code +2. If there was a call to patched in the replay code, they will + run the non-patched code during and after replay + +This might sound odd, but it's actually exactly what's needed because +that means that if the future patched code depends on earlier patched code, +then it won't use the new code -- it will use the old code. But if +there's new code in the future, and there was no code earlier in the +body that required the new patch, then it can switch over to the new code, +and it will do that. + +Note that this behavior means that the Workflow ***does not always run +the newest code***. It only does that if not replaying or if +surpassed replay and there hasn't been a call to patched (with that ID) throughout +the replay. + + +##### Examples + The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index 0870a8c4df..1e6874b99b 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -137,6 +137,124 @@ We take a deep dive into the behavior of the `patched()` function in this option +And here is a detailed explanation of how the `patched()` function behaves. + +##### Behavior When Not Replaying + +If not replaying, and the execution hits a call to patched, it first checks the event history, and: + +- If the patch ID is not in the event history, it will add a marker to the event history and upsert a search attribute (you can think of this like the first block of patching with a given patch ID). +- If the patch ID is in the event history, it won't modify the history (you can think of this like the second block of patching of a given patch ID). + +In either case, it will return true + +> There is a caveat to the above, and we will discuss that below. + +##### Behavior When Replaying With Marker Before-Or-At Current Location + +If Replaying: + +- If the code has a call to patched, and if the event history + has a marker from a call to patched in the same place (which means it + will match the original event history), then + write a marker to the replay event history and return true. + *This is similar to the behavior of the non-replay case, and + just like in that one, you can think of this like the first block of patching with + a given patch ID* +- If the code has a call to patched, and the event history + has a marker with that Patch ID earlier in the history, + then it will simply return true and not modify the + replay event history. + *This is similar to the behavior of the non-replay case, and just + like in that case, you can think of this like the second + block of patching of a given patch ID* + +##### Behavior When Replaying With Marker After Current Location or No Marker at All + +We have covered what happens when replaying and the code +hits a call to patched and there's a marker in the event +history on or before that spot in the execution. What remains +is what happens if (1) the marker is after that spot in the +execution or (2) if there is no marker at all for that patch. + +###### (1) There is a marker after that spot in the execution + +If the event is after where we currently are +in the event history, then, in other words, +our patch is before the +event, then our patch is too early. it will +attempt to write the marker to the replay event +history, but it will throw a non-deterministic +exception because the replay and original event +histories don't match + +###### (2) There is no marker for that Patch ID + +It will return false and not add anything to +the event history. Furthermore, ***and this is the +caveat mentioned in the very first section***, it will make all future calls to patched +with that ID false -- even after it is done replaying +and is running new code. + +Why is this a caveat? + +In the first section, we said that ***if not replaying, +the patched function will always return true***, and if +the marker doesn't exist, it will add it, and if +the marker already exists, it won't re-add it. + +But what this +is saying is that this doesn't hold if there was already +a call to patched with that ID in the replay code, but not +in the event history. In this situation, it won't return +true. + +##### A Summary of the Two Potentially Unexpected Behaviors + +1. When Replaying, in the scenario of ***it hits a call to + patched, but that patch ID isn't before/on that point in + the event history***, you may not expect that + the event history *after* where you currently + are matters. Because: + 1. If that patch ID exists later, you get an NDE [(see above: (1) There is a marker after that spot in the execution)](#1-there-is-a-marker-after-that-spot-in-the-execution). + 2. If it doesn't exist later, you don't get an NDE, and + it returns false + [(see above: (2) There is no marker for that Patch ID)](#2-there-is-no-marker-for-that-patch-id). + +2. When Replaying, if you hit a call to patched with an ID that + doesn't exist in the history, then not only will it return + false in that occurence, but it will also return false if + the execution surpasses the Replay threshold and is running new code. + [(see above: (2) There is no marker for that Patch ID)](#2-there-is-no-marker-for-that-patch-id). + + (This doesn't happen in TS -- it will + return false in that occurence, but this doesn't modify the behavior + of future calls). + +##### Implications of the Behaviors + +If you deploy new code while a worker is down, +any workflows that were in the middle of executing will replay +using old code and then for the rest of the execution, they +will either: + +1. Use new code if there was no call to patched in the replay code +2. If there was a call to patched in the replay code, they will + run the non-patched code during and after replay + +This might sound odd, but it's actually exactly what's needed because +that means that if the future patched code depends on earlier patched code, +then it won't use the new code -- it will use the old code. But if +there's new code in the future, and there was no code earlier in the +body that required the new patch, then it can switch over to the new code, +and it will do that. + +Note that this behavior means that the Workflow ***does not always run +the newest code***. It only does that if not replaying or if +surpassed replay and there hasn't been a call to patched (with that ID) throughout +the replay. + +##### Examples The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: diff --git a/docs/develop/typescript/versioning.mdx b/docs/develop/typescript/versioning.mdx index c1fbd3ac59..55fc8e7081 100644 --- a/docs/develop/typescript/versioning.mdx +++ b/docs/develop/typescript/versioning.mdx @@ -181,6 +181,80 @@ Patching is a three-step process: #### Overview +Here is a detailed explanation of how the `patched()` function behaves. + +##### Behavior When Not Replaying + +If not replaying, and the execution hits a call to patched, it first checks the event history, and: + +- If the patch ID is not in the event history, it will add a marker to the event history and upsert a search attribute (you can think of this like the first block of patching with a given patch ID). +- If the patch ID is in the event history, it won't modify the history (you can think of this like the second block of patching of a given patch ID). + +In either case, it will return true + +##### Behavior When Replaying With Marker Before-Or-At Current Location + +If Replaying: + +- If the code has a call to patched, and if the event history + has a marker from a call to patched in the same place (which means it + will match the original event history), then + write a marker to the replay event history and return true. + *This is similar to the behavior of the non-replay case, and + just like in that one, you can think of this like the first block of patching with + a given patch ID* +- If the code has a call to patched, and the event history + has a marker with that Patch ID earlier in the history, + then it will simply return true and not modify the + replay event history. + *This is similar to the behavior of the non-replay case, and just + like in that case, you can think of this like the second + block of patching of a given patch ID* +- If the code has a call to patched, and there is a marker after that spot in the execution, or if there is no marker for that patch ID anywhere in the event history, it returns false. + +##### Implications of the Behaviors + + +If you deploy new code, it will run the new code if it is +not replaying, and if it is replaying, it will just do what +it did the previous time. + +This means that if it has gotten through some of your code, then +you stop the worker and deploy new code, then when it replays, +it will use the old code throughout the replay, but switch over +to new code after it has passed the replay threshold. This means +your new code and your old code must work together. For example, +if your Workflow Definition originally looked like this: + +```ts +console.log('original code before the sleep') +await sleep(10000); // <-- Stop the Worker while this is waiting, and deploy the new code below +console.log('original code after the sleep') +``` + +Now we stop the Worker during the sleep, and wrap our original +code in the else part of a patched `if` statement, and start +our Worker again. + +```ts +if (patched('my-change-id')) { + console.log('new code before the sleep') +} else { + console.log('original code before the sleep') // this will run +} +await sleep(10000); +if (patched('my-change-id')) { + console.log('new code after the sleep') // this will run +} else { + console.log('original code after the sleep') +} +``` + +In the first part, it will be Replaying, and it will run the old code, +and after the sleep, it won't be Replaying, and it will run the new code. + +##### Examples + The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: From 11fda12c7cdaee5106ca3626422efab0ef7e9aa9 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Mon, 3 Feb 2025 15:12:11 -0600 Subject: [PATCH 07/13] modified python and dotnet examples --- docs/develop/dotnet/versioning.mdx | 72 ++++++------------------------ docs/develop/python/versioning.mdx | 70 ++++++----------------------- 2 files changed, 28 insertions(+), 114 deletions(-) diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx index f5a23cb94d..f3cd454281 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/versioning.mdx @@ -110,7 +110,6 @@ Implementing patching involves three steps: #### Overview - We take a deep dive into the behavior of the `patched()` function in this optional 37 minute YouTube series:
@@ -240,79 +239,35 @@ the newest code***. It only does that if not replaying or if surpassed replay and there hasn't been a call to patched (with that ID) throughout the replay. +##### Recommendations -##### Examples - -The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: +Based on this behavior and the implications, when patching in new code, always put the newest code at the top of an if-patched-block. ```csharp if (patched('v3')) { // This is the newest version of the code. - - // The above patched statement following will do - // one of the following three things: - - // 1. If the execution is not Replaying, it will evaluate - // to true and write a Marker Event to the history - // with a patch id v3. This code block will run. - // 2. If the execution is Replaying, and the original - // run put a Patch ID v3 at this location in the event - // history, it will evaluate to True, and this code block - // will run. - // 3. If the execution is Replaying, and the original - // run has a Patch ID other than v3 at this location in the event - // history, it will evaluate to False, and this code block won't - // run. + // put this at the top, so when it is running + // a fresh execution and not replaying, + // this patched statement will return true + // and it will run the new code. } else if (patched('v2')) { - // This is the second version of the code. - - // The above patched statement following will do - // one of the following three things: - - // 1. If the execution is not Replaying, the execution - // won't get here because the first patched statement - // will be True. - // 2. If the execution is Replaying, and the original - // run put a Patch ID v2 marker at this location in the event - // history, it will evaluate to True, and this code block - // will run. - // 3. If the execution is Replaying, and the original - // run has a Patch ID other than v2 at this location in the event - // history, or doesn't have a patch marker at this location in the event - // history, it will evaluate to False, and this code block won't - // run. } else { - // This is the original version of the code. - // - // The above patched statement following will do - // one of the following three things: - // - // 1. If the execution is not Replaying, the execution - // won't get here because the first patched statement - // will be True. - // 2. If the execution is Replaying, and the original - // run had a patch marker v3 or v2 at this location in the event - // history, the execution - // won't get here because the first or second patched statement - // will be True (respectively). - // 3. If the execution is Replaying, and condition 2 - // doesn't hold, then it will run this code. } ``` -To add more clarity, the following sample shows how `patched()` will behave in a different conditional block. +The following sample shows how `patched()` will behave in a conditional block that's arranged differently. In this case, the code's conditional block doesn't have the newest code at the top. -Because `patched()` will always return `true` when not Replaying, this snippet will run the `v2` branch instead of `v3` in new executions. +Because `patched()` will return `true` when not Replaying (except with the preceding caveats), this snippet will run the `v2` branch instead of `v3` in new executions. ```csharp if (patched('v2')) { - // This is bad because when doing an original execution (i.e. not replaying), - // all patched statements evaluate to True (and put a marker + // This is bad because when doing a new execution (i.e. not replaying), + // patched statements evaluate to True (and put a marker // in the event history), which means that new executions // will use v2, and miss v3 below } @@ -320,11 +275,12 @@ else if (patched('v3')) {} else {} ``` -The moral of the story is that when not Replaying, `patched()` will return true and write the patch ID to the Event History. -And when Replaying, it will only return true if the patch ID matches that in the Event History. - + +The moral of the story is that when not Replaying, `patched()` will return `True` (except with caveats) and write the patch ID to the Event History. +And when Replaying, it will only return true if the patch ID matches that in the Event History. + ### Patching in new code {#using-patched-for-workflow-history-markers} Using `Patched` inserts a marker into the Workflow History. diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index 1e6874b99b..a707fe5158 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -254,80 +254,37 @@ the newest code***. It only does that if not replaying or if surpassed replay and there hasn't been a call to patched (with that ID) throughout the replay. -##### Examples +##### Recommendations -The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: +Based on this behavior and the implications, when patching in new code, always put the newest code at the top of an if-patched-block. ```python if patched('v3'): # This is the newest version of the code. - - # The above patched statement following will do - # one of the following three things: - - # 1. If the execution is not Replaying, it will evaluate - # to true and write a Marker Event to the history - # with a patch id v3. This code block will run. - # 2. If the execution is Replaying, and the original - # run put a Patch ID v3 at this location in the event - # history, it will evaluate to True, and this code block - # will run. - # 3. If the execution is Replaying, and the original - # run has a Patch ID other than v3 at this location in the event - # history, it will evaluate to False, and this code block won't - # run. + # put this at the top, so when it is running + # a fresh execution and not replaying, + # this patched statement will return true + # and it will run the new code. pass elif patched('v2'): - # This is the second version of the code. - - # The above patched statement following will do - # one of the following three things: - - # 1. If the execution is not Replaying, the execution - # won't get here because the first patched statement - # will be True. - # 2. If the execution is Replaying, and the original - # run put a Patch ID v2 marker at this location in the event - # history, it will evaluate to True, and this code block - # will run. - # 3. If the execution is Replaying, and the original - # run has a Patch ID other than v2 at this location in the event - # history, or doesn't have a patch marker at this location in the event - # history, it will evaluate to False, and this code block won't - # run. pass else: - # This is the original version of the code. - - # The above patched statement following will do - # one of the following three things: - - # 1. If the execution is not Replaying, the execution - # won't get here because the first patched statement - # will be True. - # 2. If the execution is Replaying, and the original - # run had a patch marker v3 or v2 at this location in the event - # history, the execution - # won't get here because the first or second patched statement - # will be True (respectively). - # 3. If the execution is Replaying, and condition 2 - # doesn't hold, then it will run this code. pass ``` -To add more clarity, the following sample shows how `patched()` will behave in a different conditional block. +The following sample shows how `patched()` will behave in a conditional block that's arranged differently. In this case, the code's conditional block doesn't have the newest code at the top. -Because `patched()` will always return `True` when not Replaying, this snippet will run the `v2` branch instead of `v3` in new executions. +Because `patched()` will return `True` when not Replaying (except with the preceding caveats), this snippet will run the `v2` branch instead of `v3` in new executions. ```python if patched('v2'): - # This is bad because when doing an original execution (i.e. not replaying), - # all patched statements evaluate to True (and put a marker + # This is bad because when doing a new execution (i.e. not replaying), + # patched statements evaluate to True (and put a marker # in the event history), which means that new executions # will use v2, and miss v3 below pass @@ -337,11 +294,12 @@ else: pass ``` -The moral of the story is that when not Replaying, `patched()` will return `True` and write the patch ID to the Event History. -And when Replaying, it will only return true if the patch ID matches that in the Event History. - + +The moral of the story is that when not Replaying, `patched()` will return `True` (except with caveats) and write the patch ID to the Event History. +And when Replaying, it will only return true if the patch ID matches that in the Event History. + ### Patching in new code {#using-patched-for-workflow-history-markers} Using `patched()` inserts a marker into the Workflow History. From c714c0a68d8574631e787e795830856bb1c50368 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Mon, 3 Feb 2025 15:37:30 -0600 Subject: [PATCH 08/13] dotnet and python versioning look good --- docs/develop/dotnet/versioning.mdx | 51 ++++++++++-------------------- docs/develop/python/versioning.mdx | 51 ++++++++++-------------------- 2 files changed, 34 insertions(+), 68 deletions(-) diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx index f3cd454281..4fcbc624f0 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/versioning.mdx @@ -122,16 +122,14 @@ We take a deep dive into the behavior of the `patched()` function in this option
-And here is a detailed explanation of how the `patched()` function behaves. - ##### Behavior When Not Replaying If not replaying, and the execution hits a call to patched, it first checks the event history, and: -- If the patch ID is not in the event history, it will add a marker to the event history and upsert a search attribute (you can think of this like the first block of patching with a given patch ID). -- If the patch ID is in the event history, it won't modify the history (you can think of this like the second block of patching of a given patch ID). - -In either case, it will return true +- If the patch ID is not in the event history, it will add a marker to the event history, upsert a search attribute, and return true. + This happens in a given patch ID's first block. +- If the patch ID is in the event history, it won't modify the history, and it will return true. + This happens in a given patch ID's subsequent blocks. > There is a caveat to the above, and we will discuss that below. @@ -142,38 +140,27 @@ If Replaying: - If the code has a call to patched, and if the event history has a marker from a call to patched in the same place (which means it will match the original event history), then - write a marker to the replay event history and return true. + it writes a marker to the replay event history and returns true. *This is similar to the behavior of the non-replay case, and - just like in that one, you can think of this like the first block of patching with - a given patch ID* + just like in that case, this happens in a given patch ID's first block* - If the code has a call to patched, and the event history has a marker with that Patch ID earlier in the history, then it will simply return true and not modify the replay event history. - *This is similar to the behavior of the non-replay case, and just - like in that case, you can think of this like the second - block of patching of a given patch ID* - -##### Behavior When Replaying With Marker After Current Location or No Marker at All - -We have covered what happens when replaying and the code -hits a call to patched and there's a marker in the event -history on or before that spot in the execution. What remains -is what happens if (1) the marker is after that spot in the -execution or (2) if there is no marker at all for that patch. + *This is similar to the behavior of the non-replay case, and + just like in that case, this happens in a given patch ID's subsequent blocks* -###### (1) There is a marker after that spot in the execution +##### Behavior When Replaying With Marker After Current Location -If the event is after where we currently are +If the Marker Event is after where the execution currently is in the event history, then, in other words, -our patch is before the -event, then our patch is too early. it will +the patch is before the original patch, then the patch is too early. It will attempt to write the marker to the replay event history, but it will throw a non-deterministic exception because the replay and original event histories don't match -###### (2) There is no marker for that Patch ID +##### Behavior When Replaying With No Marker For that Patch ID It will return false and not add anything to the event history. Furthermore, ***and this is the @@ -183,8 +170,8 @@ and is running new code. Why is this a caveat? -In the first section, we said that ***if not replaying, -the patched function will always return true***, and if +In the [preceding section](#behavior-when-not-replaying) where we discussed the behavior when not replaying , we said that if not replaying, +the patched function will always return true, and if the marker doesn't exist, it will add it, and if the marker already exists, it won't re-add it. @@ -201,20 +188,16 @@ true. the event history***, you may not expect that the event history *after* where you currently are matters. Because: - 1. If that patch ID exists later, you get an NDE [(see above: (1) There is a marker after that spot in the execution)](#1-there-is-a-marker-after-that-spot-in-the-execution). + 1. If that patch ID exists later, you get an NDE [(see above: Behavior When Replaying With Marker After Current Location)](#behavior-when-replaying-with-marker-after-current-location). 2. If it doesn't exist later, you don't get an NDE, and it returns false - [(see above: (2) There is no marker for that Patch ID)](#2-there-is-no-marker-for-that-patch-id). + [(see above: Behavior When Replaying With No Marker For that Patch ID)](#behavior-when-replaying-with-no-marker-for-that-patch-id). 2. When Replaying, if you hit a call to patched with an ID that doesn't exist in the history, then not only will it return false in that occurence, but it will also return false if the execution surpasses the Replay threshold and is running new code. - [(see above: (2) There is no marker for that Patch ID)](#2-there-is-no-marker-for-that-patch-id). - - (This doesn't happen in TS -- it will - return false in that occurence, but this doesn't modify the behavior - of future calls). + [(see above: Behavior When Replaying With No Marker For that Patch ID)](#behavior-when-replaying-with-no-marker-for-that-patch-id). ##### Implications of the Behaviors diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index a707fe5158..af6152f5c3 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -137,16 +137,14 @@ We take a deep dive into the behavior of the `patched()` function in this option -And here is a detailed explanation of how the `patched()` function behaves. - ##### Behavior When Not Replaying If not replaying, and the execution hits a call to patched, it first checks the event history, and: -- If the patch ID is not in the event history, it will add a marker to the event history and upsert a search attribute (you can think of this like the first block of patching with a given patch ID). -- If the patch ID is in the event history, it won't modify the history (you can think of this like the second block of patching of a given patch ID). - -In either case, it will return true +- If the patch ID is not in the event history, it will add a marker to the event history, upsert a search attribute, and return true. + This happens in a given patch ID's first block. +- If the patch ID is in the event history, it won't modify the history, and it will return true. + This happens in a given patch ID's subsequent blocks. > There is a caveat to the above, and we will discuss that below. @@ -157,38 +155,27 @@ If Replaying: - If the code has a call to patched, and if the event history has a marker from a call to patched in the same place (which means it will match the original event history), then - write a marker to the replay event history and return true. + it writes a marker to the replay event history and returns true. *This is similar to the behavior of the non-replay case, and - just like in that one, you can think of this like the first block of patching with - a given patch ID* + just like in that case, this happens in a given patch ID's first block* - If the code has a call to patched, and the event history has a marker with that Patch ID earlier in the history, then it will simply return true and not modify the replay event history. - *This is similar to the behavior of the non-replay case, and just - like in that case, you can think of this like the second - block of patching of a given patch ID* - -##### Behavior When Replaying With Marker After Current Location or No Marker at All - -We have covered what happens when replaying and the code -hits a call to patched and there's a marker in the event -history on or before that spot in the execution. What remains -is what happens if (1) the marker is after that spot in the -execution or (2) if there is no marker at all for that patch. + *This is similar to the behavior of the non-replay case, and + just like in that case, this happens in a given patch ID's subsequent blocks* -###### (1) There is a marker after that spot in the execution +##### Behavior When Replaying With Marker After Current Location -If the event is after where we currently are +If the Marker Event is after where the execution currently is in the event history, then, in other words, -our patch is before the -event, then our patch is too early. it will +the patch is before the original patch, then the patch is too early. It will attempt to write the marker to the replay event history, but it will throw a non-deterministic exception because the replay and original event histories don't match -###### (2) There is no marker for that Patch ID +##### Behavior When Replaying With No Marker For that Patch ID It will return false and not add anything to the event history. Furthermore, ***and this is the @@ -198,8 +185,8 @@ and is running new code. Why is this a caveat? -In the first section, we said that ***if not replaying, -the patched function will always return true***, and if +In the [preceding section](#behavior-when-not-replaying) where we discussed the behavior when not replaying , we said that if not replaying, +the patched function will always return true, and if the marker doesn't exist, it will add it, and if the marker already exists, it won't re-add it. @@ -216,20 +203,16 @@ true. the event history***, you may not expect that the event history *after* where you currently are matters. Because: - 1. If that patch ID exists later, you get an NDE [(see above: (1) There is a marker after that spot in the execution)](#1-there-is-a-marker-after-that-spot-in-the-execution). + 1. If that patch ID exists later, you get an NDE [(see above: Behavior When Replaying With Marker After Current Location)](#behavior-when-replaying-with-marker-after-current-location). 2. If it doesn't exist later, you don't get an NDE, and it returns false - [(see above: (2) There is no marker for that Patch ID)](#2-there-is-no-marker-for-that-patch-id). + [(see above: Behavior When Replaying With No Marker For that Patch ID)](#behavior-when-replaying-with-no-marker-for-that-patch-id). 2. When Replaying, if you hit a call to patched with an ID that doesn't exist in the history, then not only will it return false in that occurence, but it will also return false if the execution surpasses the Replay threshold and is running new code. - [(see above: (2) There is no marker for that Patch ID)](#2-there-is-no-marker-for-that-patch-id). - - (This doesn't happen in TS -- it will - return false in that occurence, but this doesn't modify the behavior - of future calls). + [(see above: Behavior When Replaying With No Marker For that Patch ID)](#behavior-when-replaying-with-no-marker-for-that-patch-id). ##### Implications of the Behaviors From f5c42767d644a4566a6dec1191345eb0c78a9896 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Mon, 3 Feb 2025 15:51:46 -0600 Subject: [PATCH 09/13] ready for Erica --- docs/develop/dotnet/versioning.mdx | 6 +- docs/develop/python/versioning.mdx | 6 +- docs/develop/typescript/versioning.mdx | 86 ++++++-------------------- 3 files changed, 21 insertions(+), 77 deletions(-) diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx index 4fcbc624f0..26c41f75b6 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/versioning.mdx @@ -164,7 +164,7 @@ histories don't match It will return false and not add anything to the event history. Furthermore, ***and this is the -caveat mentioned in the very first section***, it will make all future calls to patched +caveat mentioned in the preceeding section [Behavior When Not Replaying](#behavior-when-not-replaying)***, it will make all future calls to patched with that ID false -- even after it is done replaying and is running new code. @@ -260,10 +260,6 @@ else {} - -The moral of the story is that when not Replaying, `patched()` will return `True` (except with caveats) and write the patch ID to the Event History. -And when Replaying, it will only return true if the patch ID matches that in the Event History. - ### Patching in new code {#using-patched-for-workflow-history-markers} Using `Patched` inserts a marker into the Workflow History. diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index af6152f5c3..0e2bd20041 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -179,7 +179,7 @@ histories don't match It will return false and not add anything to the event history. Furthermore, ***and this is the -caveat mentioned in the very first section***, it will make all future calls to patched +caveat mentioned in the preceeding section [Behavior When Not Replaying](#behavior-when-not-replaying)***, it will make all future calls to patched with that ID false -- even after it is done replaying and is running new code. @@ -279,10 +279,6 @@ else: - -The moral of the story is that when not Replaying, `patched()` will return `True` (except with caveats) and write the patch ID to the Event History. -And when Replaying, it will only return true if the patch ID matches that in the Event History. - ### Patching in new code {#using-patched-for-workflow-history-markers} Using `patched()` inserts a marker into the Workflow History. diff --git a/docs/develop/typescript/versioning.mdx b/docs/develop/typescript/versioning.mdx index 55fc8e7081..995abff571 100644 --- a/docs/develop/typescript/versioning.mdx +++ b/docs/develop/typescript/versioning.mdx @@ -187,10 +187,10 @@ Here is a detailed explanation of how the `patched()` function behaves. If not replaying, and the execution hits a call to patched, it first checks the event history, and: -- If the patch ID is not in the event history, it will add a marker to the event history and upsert a search attribute (you can think of this like the first block of patching with a given patch ID). -- If the patch ID is in the event history, it won't modify the history (you can think of this like the second block of patching of a given patch ID). - -In either case, it will return true +- If the patch ID is not in the event history, it will add a marker to the event history, upsert a search attribute, and return true. + This happens in a given patch ID's first block. +- If the patch ID is in the event history, it won't modify the history, and it will return true. + This happens in a given patch ID's subsequent blocks. ##### Behavior When Replaying With Marker Before-Or-At Current Location @@ -199,18 +199,16 @@ If Replaying: - If the code has a call to patched, and if the event history has a marker from a call to patched in the same place (which means it will match the original event history), then - write a marker to the replay event history and return true. + it writes a marker to the replay event history and returns true. *This is similar to the behavior of the non-replay case, and - just like in that one, you can think of this like the first block of patching with - a given patch ID* + just like in that case, this happens in a given patch ID's first block* - If the code has a call to patched, and the event history has a marker with that Patch ID earlier in the history, then it will simply return true and not modify the replay event history. - *This is similar to the behavior of the non-replay case, and just - like in that case, you can think of this like the second - block of patching of a given patch ID* -- If the code has a call to patched, and there is a marker after that spot in the execution, or if there is no marker for that patch ID anywhere in the event history, it returns false. + *This is similar to the behavior of the non-replay case, and + just like in that case, this happens in a given patch ID's subsequent blocks* +- If the code has a call to patched, and there no marker on or before that spot in the execution, it returns false. ##### Implications of the Behaviors @@ -253,78 +251,35 @@ if (patched('my-change-id')) { In the first part, it will be Replaying, and it will run the old code, and after the sleep, it won't be Replaying, and it will run the new code. -##### Examples +##### Recommendations -The following sample shows how the `patched()` function behaves, providing explanations at each stage of the patching flow: +Based on this behavior and the implications, when patching in new code, always put the newest code at the top of an if-patched-block. ```ts if (patched('v3')) { // This is the newest version of the code. - - // The above patched statement following will do - // one of the following three things: - - // 1. If the execution is not Replaying, it will evaluate - // to true and write a Marker Event to the history - // with a patch id v3. This code block will run. - // 2. If the execution is Replaying, and the original - // run put a Patch ID v3 at this location in the event - // history, it will evaluate to True, and this code block - // will run. - // 3. If the execution is Replaying, and the original - // run has a Patch ID other than v3 at this location in the event - // history, it will evaluate to False, and this code block won't - // run. + // put this at the top, so when it is running + // a fresh execution and not replaying, + // this patched statement will return true + // and it will run the new code. } else if (patched('v2')) { - // This is the second version of the code. - - // The above patched statement following will do - // one of the following three things: - - // 1. If the execution is not Replaying, the execution - // won't get here because the first patched statement - // will be True. - // 2. If the execution is Replaying, and the original - // run put a Patch ID v2 marker at this location in the event - // history, it will evaluate to True, and this code block - // will run. - // 3. If the execution is Replaying, and the original - // run has a Patch ID other than v2 at this location in the event - // history, or doesn't have a patch marker at this location in the event - // history, it will evaluate to False, and this code block won't - // run. } else { - // This is the original version of the code. - // - // The above patched statement following will do - // one of the following three things: - // - // 1. If the execution is not Replaying, the execution - // won't get here because the first patched statement - // will be True. - // 2. If the execution is Replaying, and the original - // run had a patch marker v3 or v2 at this location in the event - // history, the execution - // won't get here because the first or second patched statement - // will be True (respectively). - // 3. If the execution is Replaying, and condition 2 - // doesn't hold, then it will run this code. } ``` -To add more clarity, the following sample shows how `patched()` will behave in a different conditional block. +The following sample shows how `patched()` will behave in a conditional block that's arranged differently. In this case, the code's conditional block doesn't have the newest code at the top. -Because `patched()` will always return `true` when not Replaying, this snippet will run the `v2` branch instead of `v3` in new executions. +Because `patched()` will return `true` when not Replaying (except with the preceding caveats), this snippet will run the `v2` branch instead of `v3` in new executions. ```ts if (patched('v2')) { - // This is bad because when doing an original execution (i.e. not replaying), - // all patched statements evaluate to True (and put a marker + // This is bad because when doing a new execution (i.e. not replaying), + // patched statements evaluate to True (and put a marker // in the event history), which means that new executions // will use v2, and miss v3 below } @@ -332,9 +287,6 @@ else if (patched('v3')) {} else {} ``` -The moral of the story is that when not Replaying, `patched()` will return true and write the patch ID to the Event History. -And when Replaying, it will only return true if the patch ID matches that in the Event History. - #### Step 1: Patch in new code From f86af98bfc78cdc2f01e1f40f7f9977fdb7fbec0 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Mon, 3 Feb 2025 22:27:54 -0600 Subject: [PATCH 10/13] fix header levels --- docs/develop/dotnet/versioning.mdx | 16 ++++++++-------- docs/develop/python/versioning.mdx | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx index 26c41f75b6..3ece9d1fa4 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/versioning.mdx @@ -108,7 +108,7 @@ Implementing patching involves three steps: 2. Remove the old code and apply [DeprecatePatch](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_DeprecatePatch_System_String_). 3. Once you're confident that all old Workflows have finished executing, remove `DeprecatePatch`. -#### Overview +### Overview We take a deep dive into the behavior of the `patched()` function in this optional 37 minute YouTube series: @@ -122,7 +122,7 @@ We take a deep dive into the behavior of the `patched()` function in this option -##### Behavior When Not Replaying +#### Behavior When Not Replaying If not replaying, and the execution hits a call to patched, it first checks the event history, and: @@ -133,7 +133,7 @@ If not replaying, and the execution hits a call to patched, it first checks the > There is a caveat to the above, and we will discuss that below. -##### Behavior When Replaying With Marker Before-Or-At Current Location +#### Behavior When Replaying With Marker Before-Or-At Current Location If Replaying: @@ -150,7 +150,7 @@ If Replaying: *This is similar to the behavior of the non-replay case, and just like in that case, this happens in a given patch ID's subsequent blocks* -##### Behavior When Replaying With Marker After Current Location +#### Behavior When Replaying With Marker After Current Location If the Marker Event is after where the execution currently is in the event history, then, in other words, @@ -160,7 +160,7 @@ history, but it will throw a non-deterministic exception because the replay and original event histories don't match -##### Behavior When Replaying With No Marker For that Patch ID +#### Behavior When Replaying With No Marker For that Patch ID It will return false and not add anything to the event history. Furthermore, ***and this is the @@ -181,7 +181,7 @@ a call to patched with that ID in the replay code, but not in the event history. In this situation, it won't return true. -##### A Summary of the Two Potentially Unexpected Behaviors +#### A Summary of the Two Potentially Unexpected Behaviors 1. When Replaying, in the scenario of ***it hits a call to patched, but that patch ID isn't before/on that point in @@ -199,7 +199,7 @@ true. the execution surpasses the Replay threshold and is running new code. [(see above: Behavior When Replaying With No Marker For that Patch ID)](#behavior-when-replaying-with-no-marker-for-that-patch-id). -##### Implications of the Behaviors +#### Implications of the Behaviors If you deploy new code while a worker is down, any workflows that were in the middle of executing will replay @@ -222,7 +222,7 @@ the newest code***. It only does that if not replaying or if surpassed replay and there hasn't been a call to patched (with that ID) throughout the replay. -##### Recommendations +#### Recommendations Based on this behavior and the implications, when patching in new code, always put the newest code at the top of an if-patched-block. diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index 0e2bd20041..26ca78a552 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -123,7 +123,7 @@ Implementing patching involves three steps: 2. Remove the old code and apply [deprecate_patch](https://python.temporal.io/temporalio.workflow.html#deprecate_patch). 3. Once you're confident that all old Workflows have finished executing, remove `deprecate_patch`. -#### Overview +### Overview We take a deep dive into the behavior of the `patched()` function in this optional 37 minute YouTube series: @@ -137,7 +137,7 @@ We take a deep dive into the behavior of the `patched()` function in this option -##### Behavior When Not Replaying +#### Behavior When Not Replaying If not replaying, and the execution hits a call to patched, it first checks the event history, and: @@ -148,7 +148,7 @@ If not replaying, and the execution hits a call to patched, it first checks the > There is a caveat to the above, and we will discuss that below. -##### Behavior When Replaying With Marker Before-Or-At Current Location +#### Behavior When Replaying With Marker Before-Or-At Current Location If Replaying: @@ -165,7 +165,7 @@ If Replaying: *This is similar to the behavior of the non-replay case, and just like in that case, this happens in a given patch ID's subsequent blocks* -##### Behavior When Replaying With Marker After Current Location +#### Behavior When Replaying With Marker After Current Location If the Marker Event is after where the execution currently is in the event history, then, in other words, @@ -175,7 +175,7 @@ history, but it will throw a non-deterministic exception because the replay and original event histories don't match -##### Behavior When Replaying With No Marker For that Patch ID +#### Behavior When Replaying With No Marker For that Patch ID It will return false and not add anything to the event history. Furthermore, ***and this is the @@ -196,7 +196,7 @@ a call to patched with that ID in the replay code, but not in the event history. In this situation, it won't return true. -##### A Summary of the Two Potentially Unexpected Behaviors +#### A Summary of the Two Potentially Unexpected Behaviors 1. When Replaying, in the scenario of ***it hits a call to patched, but that patch ID isn't before/on that point in @@ -214,7 +214,7 @@ true. the execution surpasses the Replay threshold and is running new code. [(see above: Behavior When Replaying With No Marker For that Patch ID)](#behavior-when-replaying-with-no-marker-for-that-patch-id). -##### Implications of the Behaviors +#### Implications of the Behaviors If you deploy new code while a worker is down, any workflows that were in the middle of executing will replay @@ -237,7 +237,7 @@ the newest code***. It only does that if not replaying or if surpassed replay and there hasn't been a call to patched (with that ID) throughout the replay. -##### Recommendations +#### Recommendations Based on this behavior and the implications, when patching in new code, always put the newest code at the top of an if-patched-block. From 702c078105bef0b144efbe4fc55ae2bcaa4637c1 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Mon, 3 Feb 2025 22:36:11 -0600 Subject: [PATCH 11/13] python and dotnet versioning look good --- docs/develop/dotnet/versioning.mdx | 153 ++++++++++++------------- docs/develop/python/versioning.mdx | 174 +++++++++++++++-------------- 2 files changed, 165 insertions(+), 162 deletions(-) diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx index 3ece9d1fa4..83ea7f264b 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/versioning.mdx @@ -108,7 +108,83 @@ Implementing patching involves three steps: 2. Remove the old code and apply [DeprecatePatch](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_DeprecatePatch_System_String_). 3. Once you're confident that all old Workflows have finished executing, remove `DeprecatePatch`. -### Overview +### Patching in new code {#using-patched-for-workflow-history-markers} + +Using `Patched` inserts a marker into the Workflow History. + +During replay, if a Worker encounters a history with that marker, it will fail the Workflow task when the Workflow code doesn't produce the same patch marker (in this case, `my-patch`). This ensures you can safely deploy code from `PostPatchActivity` as a "feature flag" alongside the original version (`PrePatchActivity`). + +```csharp +[Workflow] +public class MyWorkflow +{ + [WorkflowRun] + public async Task RunAsync() + { + if (Workflow.Patched("my-patch")) + { + this.result = await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.PostPatchActivity(), + new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); + } + else + { + this.result = await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.PrePatchActivity(), + new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); + } + + // ... + } +} +``` + +### Understanding deprecated Patches in the .NET SDK {#deprecated-patches} + +After ensuring that all Workflows started with `PrePatchActivity` code have finished, you can [deprecate the patch](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_DeprecatePatch_System_String_). + +Deprecated patches serve as a bridge between `PrePatchActivity` and `PostPatchActivity`. They function similarly to regular patches by adding a marker to the Workflow History. However, this marker won't cause a replay failure when the Workflow code doesn't produce it. + +If, during the deployment of `PostPatchActivity`, there are still live Workers running `PrePatchActivity` code and these Workers pick up Workflow histories generated by `PostPatchActivity`, they will safely use the patched branch. + +```csharp +[Workflow] +public class MyWorkflow +{ + [WorkflowRun] + public async Task RunAsync() + { + Workflow.DeprecatePatch("my-patch") + this.result = await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.PostPatchActivity(), + new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); + + // ... + } +} +``` + +### Safe Deployment of PostPatchActivity {#deploy-postpatchactivity} + +You can safely deploy `PostPatchActivity` once all Workflows labeled my-patch or earlier are finished, based on the previously mentioned assertion. + +```csharp +[Workflow] +public class MyWorkflow +{ + [WorkflowRun] + public async Task RunAsync() + { + this.result = await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.PostPatchActivity(), + new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); + + // ... + } +} +``` + +### Detailed Description of the Patched Function We take a deep dive into the behavior of the `patched()` function in this optional 37 minute YouTube series: @@ -260,81 +336,6 @@ else {} -### Patching in new code {#using-patched-for-workflow-history-markers} - -Using `Patched` inserts a marker into the Workflow History. - -During replay, if a Worker encounters a history with that marker, it will fail the Workflow task when the Workflow code doesn't produce the same patch marker (in this case, `my-patch`). This ensures you can safely deploy code from `PostPatchActivity` as a "feature flag" alongside the original version (`PrePatchActivity`). - -```csharp -[Workflow] -public class MyWorkflow -{ - [WorkflowRun] - public async Task RunAsync() - { - if (Workflow.Patched("my-patch")) - { - this.result = await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.PostPatchActivity(), - new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); - } - else - { - this.result = await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.PrePatchActivity(), - new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); - } - - // ... - } -} -``` - -### Understanding deprecated Patches in the .NET SDK {#deprecated-patches} - -After ensuring that all Workflows started with `PrePatchActivity` code have finished, you can [deprecate the patch](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_DeprecatePatch_System_String_). - -Deprecated patches serve as a bridge between `PrePatchActivity` and `PostPatchActivity`. They function similarly to regular patches by adding a marker to the Workflow History. However, this marker won't cause a replay failure when the Workflow code doesn't produce it. - -If, during the deployment of `PostPatchActivity`, there are still live Workers running `PrePatchActivity` code and these Workers pick up Workflow histories generated by `PostPatchActivity`, they will safely use the patched branch. - -```csharp -[Workflow] -public class MyWorkflow -{ - [WorkflowRun] - public async Task RunAsync() - { - Workflow.DeprecatePatch("my-patch") - this.result = await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.PostPatchActivity(), - new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); - - // ... - } -} -``` - -### Safe Deployment of PostPatchActivity {#deploy-postpatchactivity} - -You can safely deploy `PostPatchActivity` once all Workflows labeled my-patch or earlier are finished, based on the previously mentioned assertion. - -```csharp -[Workflow] -public class MyWorkflow -{ - [WorkflowRun] - public async Task RunAsync() - { - this.result = await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.PostPatchActivity(), - new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); - - // ... - } -} -``` ### Best Practice of Using Classes as Arguments and Returns diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index 26ca78a552..190a7da938 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -123,7 +123,94 @@ Implementing patching involves three steps: 2. Remove the old code and apply [deprecate_patch](https://python.temporal.io/temporalio.workflow.html#deprecate_patch). 3. Once you're confident that all old Workflows have finished executing, remove `deprecate_patch`. -### Overview +### Patching in new code {#using-patched-for-workflow-history-markers} + +Using `patched()` inserts a marker into the Workflow History. + +![image](https://user-images.githubusercontent.com/6764957/139673361-35d61b38-ab94-401e-ae7b-feaa52eae8c6.png) + +During replay, if a Worker encounters a history with that marker, it will fail the Workflow task when the Workflow code doesn't produce the same patch marker (in this case, `my-patch`). This ensures you can safely deploy code from `post_patch_activity` as a "feature flag" alongside the original version (`pre_patch_activity`). + +
+ + View the source code + {' '} + in the context of the rest of the application code. +
+ +```python +# ... +@workflow.defn +class MyWorkflow: + @workflow.run + async def run(self) -> None: + if workflow.patched("my-patch"): + self._result = await workflow.execute_activity( + post_patch_activity, + schedule_to_close_timeout=timedelta(minutes=5), + ) + else: + self._result = await workflow.execute_activity( + pre_patch_activity, + schedule_to_close_timeout=timedelta(minutes=5), + ) +``` + +### Understanding deprecated Patches in the Python SDK {#deprecated-patches} + +After ensuring that all Workflows started with `pre_patch_activity` code have finished, you can [deprecate the patch](https://python.temporal.io/temporalio.workflow.html#deprecate_patch). + +Once you're confident that your Workflows are no longer running the pre-patch code paths, you can deploy your code with `deprecate_patch()`. +These Workers will be running the most up-to-date version of the Workflow code, which no longer requires the patch. +The `deprecate_patch()` function works similarly to the `patched()` function by recording a marker in the Workflow history. +This marker does not fail replay when Workflow code does not emit it. +Deprecated patches serve as a bridge between the pre-patch code paths and the post-patch code paths, and are useful for avoiding errors resulting from patched code paths in your Workflow history. + +
+ + View the source code + {' '} + in the context of the rest of the application code. +
+ +```python +# ... +@workflow.defn +class MyWorkflow: + @workflow.run + async def run(self) -> None: + workflow.deprecate_patch("my-patch") + self._result = await workflow.execute_activity( + post_patch_activity, + schedule_to_close_timeout=timedelta(minutes=5), + ) +``` + +### Safe Deployment of post_patch_activity {#deploy-new-code} + +Once you're sure that you will no longer need to Query or Replay any of your pre-patch Workflows, you can then safely deploy Workers that no longer use either the `patched()` or `deprecate_patch()` calls: + +
+ + View the source code + {' '} + in the context of the rest of the application code. +
+ +```python +# ... +@workflow.defn +class MyWorkflow: + @workflow.run + async def run(self) -> None: + self._result = await workflow.execute_activity( + post_patch_activity, + schedule_to_close_timeout=timedelta(minutes=5), + ) +``` + + +### Detailed Description of the Patched Function We take a deep dive into the behavior of the `patched()` function in this optional 37 minute YouTube series: @@ -279,91 +366,6 @@ else: -### Patching in new code {#using-patched-for-workflow-history-markers} - -Using `patched()` inserts a marker into the Workflow History. - -![image](https://user-images.githubusercontent.com/6764957/139673361-35d61b38-ab94-401e-ae7b-feaa52eae8c6.png) - -During replay, if a Worker encounters a history with that marker, it will fail the Workflow task when the Workflow code doesn't produce the same patch marker (in this case, `my-patch`). This ensures you can safely deploy code from `post_patch_activity` as a "feature flag" alongside the original version (`pre_patch_activity`). - -
- - View the source code - {' '} - in the context of the rest of the application code. -
- -```python -# ... -@workflow.defn -class MyWorkflow: - @workflow.run - async def run(self) -> None: - if workflow.patched("my-patch"): - self._result = await workflow.execute_activity( - post_patch_activity, - schedule_to_close_timeout=timedelta(minutes=5), - ) - else: - self._result = await workflow.execute_activity( - pre_patch_activity, - schedule_to_close_timeout=timedelta(minutes=5), - ) -``` - -### Understanding deprecated Patches in the Python SDK {#deprecated-patches} - -After ensuring that all Workflows started with `pre_patch_activity` code have finished, you can [deprecate the patch](https://python.temporal.io/temporalio.workflow.html#deprecate_patch). - -Once you're confident that your Workflows are no longer running the pre-patch code paths, you can deploy your code with `deprecate_patch()`. -These Workers will be running the most up-to-date version of the Workflow code, which no longer requires the patch. -The `deprecate_patch()` function works similarly to the `patched()` function by recording a marker in the Workflow history. -This marker does not fail replay when Workflow code does not emit it. -Deprecated patches serve as a bridge between the pre-patch code paths and the post-patch code paths, and are useful for avoiding errors resulting from patched code paths in your Workflow history. - -
- - View the source code - {' '} - in the context of the rest of the application code. -
- -```python -# ... -@workflow.defn -class MyWorkflow: - @workflow.run - async def run(self) -> None: - workflow.deprecate_patch("my-patch") - self._result = await workflow.execute_activity( - post_patch_activity, - schedule_to_close_timeout=timedelta(minutes=5), - ) -``` - -### Safe Deployment of post_patch_activity {#deploy-new-code} - -Once you're sure that you will no longer need to Query or Replay any of your pre-patch Workflows, you can then safely deploy Workers that no longer use either the `patched()` or `deprecate_patch()` calls: - -
- - View the source code - {' '} - in the context of the rest of the application code. -
- -```python -# ... -@workflow.defn -class MyWorkflow: - @workflow.run - async def run(self) -> None: - self._result = await workflow.execute_activity( - post_patch_activity, - schedule_to_close_timeout=timedelta(minutes=5), - ) -``` ### Best Practice of Using Python Dataclasses as Arguments and Returns From 6e15089b2c71e878fda471f539ba31037386ba37 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Mon, 3 Feb 2025 22:37:26 -0600 Subject: [PATCH 12/13] TOC depth --- docs/develop/python/versioning.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx index 190a7da938..25e932f5b5 100644 --- a/docs/develop/python/versioning.mdx +++ b/docs/develop/python/versioning.mdx @@ -4,7 +4,7 @@ title: Versioning - Python SDK sidebar_label: Versioning description: Learn how to ensure deterministic Temporal Workflow execution and safely deploy updates using the Python SDK's patching and Worker Versioning APIs, for scalable long-running Workflows. slug: /develop/python/versioning -toc_max_heading_level: 2 +toc_max_heading_level: 4 keywords: - best practices - code sample From 5c84698d9843802b6349a9cf358f55b47a8eb374 Mon Sep 17 00:00:00 2001 From: GSmithApps Date: Mon, 3 Feb 2025 22:40:29 -0600 Subject: [PATCH 13/13] moved TS patched description to a good place --- docs/develop/typescript/versioning.mdx | 121 +++++++++++++------------ 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/docs/develop/typescript/versioning.mdx b/docs/develop/typescript/versioning.mdx index 995abff571..cc227d4038 100644 --- a/docs/develop/typescript/versioning.mdx +++ b/docs/develop/typescript/versioning.mdx @@ -179,7 +179,67 @@ Patching is a three-step process: 2. Remove old code and `deprecatePatch` 3. When you are sure all old Workflows are done executing, remove `deprecatePatch` -#### Overview +#### Step 1: Patch in new code + +`patched` inserts a marker into the Workflow history. + +![image](https://user-images.githubusercontent.com/6764957/139673361-35d61b38-ab94-401e-ae7b-feaa52eae8c6.png) + +During replay, when a Worker picks up a history with that marker it will fail the Workflow task when running Workflow code that does not emit the same patch marker (in this case `your-change-id`); therefore it is safe to deploy code from `v2` in a "feature flag" alongside the original version (`v1`). + + + +[patching-api/src/workflows-v2.ts](https://github.com/temporalio/samples-typescript/blob/main/patching-api/src/workflows-v2.ts) + +```ts +// v2 +import { patched } from '@temporalio/workflow'; +export async function myWorkflow(): Promise { + if (patched('my-change-id')) { + await activityB(); + await sleep('1 days'); + } else { + await activityA(); + await sleep('1 days'); + await activityThatMustRunAfterA(); + } +} +``` + + + +#### Step 2: Deprecate patch + +When we know that all Workflows started with `v1` code have completed, we can [deprecate the patch](https://typescript.temporal.io/api/namespaces/workflow#deprecatepatch). +Deprecated patches bridge between `v2` and `vFinal` (the end result). +They work similarly to regular patches by recording a marker in the Workflow history. +This marker does not fail replay when Workflow code does not emit it. + +If while we're deploying `v3` (below) there are still live Workers running `v2` code and those Workers pick up Workflow histories generated by `v3`, they will safely use the patched branch. + + + +[patching-api/src/workflows-v3.ts](https://github.com/temporalio/samples-typescript/blob/main/patching-api/src/workflows-v3.ts) + +```ts +// v3 +import { deprecatePatch } from '@temporalio/workflow'; + +export async function myWorkflow(): Promise { + deprecatePatch('my-change-id'); + await activityB(); + await sleep('1 days'); +} +``` + + + +#### Step 3: Solely deploy new code + +`vFinal` is safe to deploy once all `v2` or earlier Workflows are complete due to the assertion mentioned above. + + +#### Detailed Description of the Patched Function Here is a detailed explanation of how the `patched()` function behaves. @@ -289,65 +349,6 @@ else {} -#### Step 1: Patch in new code - -`patched` inserts a marker into the Workflow history. - -![image](https://user-images.githubusercontent.com/6764957/139673361-35d61b38-ab94-401e-ae7b-feaa52eae8c6.png) - -During replay, when a Worker picks up a history with that marker it will fail the Workflow task when running Workflow code that does not emit the same patch marker (in this case `your-change-id`); therefore it is safe to deploy code from `v2` in a "feature flag" alongside the original version (`v1`). - - - -[patching-api/src/workflows-v2.ts](https://github.com/temporalio/samples-typescript/blob/main/patching-api/src/workflows-v2.ts) - -```ts -// v2 -import { patched } from '@temporalio/workflow'; -export async function myWorkflow(): Promise { - if (patched('my-change-id')) { - await activityB(); - await sleep('1 days'); - } else { - await activityA(); - await sleep('1 days'); - await activityThatMustRunAfterA(); - } -} -``` - - - -#### Step 2: Deprecate patch - -When we know that all Workflows started with `v1` code have completed, we can [deprecate the patch](https://typescript.temporal.io/api/namespaces/workflow#deprecatepatch). -Deprecated patches bridge between `v2` and `vFinal` (the end result). -They work similarly to regular patches by recording a marker in the Workflow history. -This marker does not fail replay when Workflow code does not emit it. - -If while we're deploying `v3` (below) there are still live Workers running `v2` code and those Workers pick up Workflow histories generated by `v3`, they will safely use the patched branch. - - - -[patching-api/src/workflows-v3.ts](https://github.com/temporalio/samples-typescript/blob/main/patching-api/src/workflows-v3.ts) - -```ts -// v3 -import { deprecatePatch } from '@temporalio/workflow'; - -export async function myWorkflow(): Promise { - deprecatePatch('my-change-id'); - await activityB(); - await sleep('1 days'); -} -``` - - - -#### Step 3: Solely deploy new code - -`vFinal` is safe to deploy once all `v2` or earlier Workflows are complete due to the assertion mentioned above. - ### Best Practice of Using TypeScript Objects as Arguments and Returns As a side note on the Patching API, its behavior is why Temporal recommends using single objects as arguments and returns from Signals, Queries, Updates, and Activities, rather than using multiple arguments.