-
Notifications
You must be signed in to change notification settings - Fork 126
fix(workflow): Fix error when timer is cancelled and immediately fired in the same activation #466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* Tests that the workflow doesn't throw if condition resolves and expires in the same activation | ||
* | ||
* @module | ||
*/ | ||
import { condition, setHandler } from '@temporalio/workflow'; | ||
import { unblockSignal } from './definitions'; | ||
|
||
export async function conditionRacer(): Promise<void> { | ||
let blocked = true; | ||
setHandler(unblockSignal, () => void (blocked = false)); | ||
await condition(() => !blocked, '1s'); | ||
await condition(() => blocked); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,8 +104,10 @@ export class Activator implements ActivationHandler { | |
} | ||
|
||
public fireTimer(activation: coresdk.workflow_activation.IFireTimer): void { | ||
const { resolve } = consumeCompletion('timer', getSeq(activation)); | ||
resolve(undefined); | ||
// Timers are a special case where their completion might not be in Workflow state, | ||
// this is due to immediate timer cancellation that doesn't go wait for Core. | ||
const completion = maybeConsumeCompletion('timer', getSeq(activation)); | ||
completion?.resolve(undefined); | ||
} | ||
|
||
public async resolveActivity(activation: coresdk.workflow_activation.IResolveActivity): Promise<void> { | ||
|
@@ -525,12 +527,21 @@ async function failQuery(queryId: string, error: any) { | |
}); | ||
} | ||
|
||
export function consumeCompletion(type: keyof State['completions'], taskSeq: number): Completion { | ||
/** Consume a completion if it exists in Workflow state */ | ||
export function maybeConsumeCompletion(type: keyof State['completions'], taskSeq: number): Completion | undefined { | ||
const completion = state.completions[type].get(taskSeq); | ||
if (completion !== undefined) { | ||
state.completions[type].delete(taskSeq); | ||
} | ||
return completion; | ||
} | ||
|
||
/** Consume a completion if it exists in Workflow state, throws if it doesn't */ | ||
export function consumeCompletion(type: keyof State['completions'], taskSeq: number): Completion { | ||
const completion = maybeConsumeCompletion(type, taskSeq); | ||
if (completion === undefined) { | ||
throw new IllegalStateError(`No completion for taskSeq ${taskSeq}`); | ||
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.
Now, are there any cases in which a user would see this error? 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. A user could see this error if new workflow code processes history generated by old code. |
||
} | ||
state.completions[type].delete(taskSeq); | ||
return completion; | ||
} | ||
|
||
|
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.
How did you make this repro w/o the extra activation?
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.
"extra"? I think w/ these two jobs:
It processes the first, which results in a cancel (and immediate cleanup/removal of timer completion), and then when it tries to process the second, it drops it because it can't find the completion (and assumes it must have been canceled). (or in the repro case, throws)
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.
The issue I had initially is that the fireTimer job doesn't get delivered if the workflow is already complete.
Adding another condition at the end of the workflow prevents that.