-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add default update handler #1640
base: main
Are you sure you want to change the base?
Changes from 3 commits
7b5cb99
ea626f5
e5be9d0
7e9971a
15b1503
69507da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { defineUpdate, setDefaultUpdateHandler, setHandler } from '@temporalio/workflow'; | ||
|
||
const updateA = defineUpdate<ProcessedUpdate, [number]>('updateA'); | ||
const updateB = defineUpdate<ProcessedUpdate, [number]>('updateB'); | ||
const updateC = defineUpdate<ProcessedUpdate, [number]>('updateC'); | ||
|
||
interface ProcessedUpdate { | ||
handler: string; | ||
updateName?: string; | ||
args: unknown[]; | ||
} | ||
|
||
/* | ||
There's a surprising amount going on with the workflow below. Let's simplify it to just updateA and updateB | ||
(no updateC or the default) and walk through it. | ||
1. setHandler for updateA | ||
- this is all synchronous code until we run `UpdateScope.updateWithInfo(updateId, name, doUpdateImpl)`, | ||
which calls `doUpdateImpl` which is promise/async, so... | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be exact, it is sync until the point where we actually call the update handler (i.e. the And in the specific case of this Workflow, the update handler is purely sync (i.e. there is no promise awaited on that code path), so it will also execute synchronously. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused.
This makes sense to me. But how can the code be sync up until we call the
This is the async call at the end of This is irrespective of the update handler code (async or not). |
||
2. queue doUpdateImpl for A on node event queue: [doUpdateImplA] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAICS, in the case of this specific workflow, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
3. continue running the workflow code (currently running code, we aren't awaiting the promise) | ||
4. setHandler for updateB | ||
- same deal as A | ||
5. queue doUpdateImpl for B on node event queue: [doUpdateImplA, doUpdateImplB] | ||
6. finished workflow code, go through the event queue | ||
7. doUpdateImplA | ||
- synchronous until we get to `execute`, which means we've accepted the update, command ordering [acceptA] | ||
8. `execute` returns a promise, add it to the node event queue: [doUpdateImplB, executeA] | ||
9. doUpdateImplB | ||
- same deal as A, command ordering [acceptA, acceptB] | ||
- `execute` returns promise, node event queue [executeA, executeB] | ||
10. execute update A, node event queue [executeB], command ordering [acceptA, acceptB, executeA] | ||
11. execute update B, node event queue [] (empty), command ordering [acceptA, acceptB, executeA, executeB] | ||
The only additional complexity with the workflow below is that once the default handler is registered, buffered updates for C will be | ||
dispatched to the default handler. So in this scenario: C1, C2 -> default registered -> C registered, both C1 and C2 will be dispatched | ||
to the default handler. | ||
*/ | ||
export async function updatesOrdering(): Promise<void> { | ||
setHandler(updateA, (...args: any[]) => { | ||
return { handler: 'updateA', args }; | ||
}); | ||
setHandler(updateB, (...args: any[]) => { | ||
return { handler: 'updateB', args }; | ||
}); | ||
setDefaultUpdateHandler((updateName, ...args: any[]) => { | ||
return { handler: 'default', updateName, args }; | ||
}); | ||
setHandler(updateC, (...args: any[]) => { | ||
return { handler: 'updateC', args }; | ||
}); | ||
} | ||
|
||
export async function updatesAreReentrant(): Promise<void> { | ||
function handlerA(...args: any[]) { | ||
setHandler(updateA, undefined); | ||
setHandler(updateB, handlerB); | ||
return { handler: 'updateA', args }; | ||
} | ||
function handlerB(...args: any[]) { | ||
setHandler(updateB, undefined); | ||
setHandler(updateC, handlerC); | ||
return { handler: 'updateB', args }; | ||
} | ||
function handlerC(...args: any[]) { | ||
setHandler(updateC, undefined); | ||
setDefaultUpdateHandler(defaultHandler); | ||
return { handler: 'updateC', args }; | ||
} | ||
function defaultHandler(updateName: string, ...args: any[]) { | ||
setDefaultUpdateHandler(undefined); | ||
setHandler(updateA, handlerA); | ||
return { handler: 'default', updateName, args }; | ||
} | ||
|
||
setHandler(updateA, handlerA); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the second argument of
toPayloads
be an array? If so, theinput
argument should beunknown[]
instead of justunknown
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but
input
is a rest parameter intoPayloads
, its a variadic function. I've changed it anyways for consistency.