feat(workflows-*): Allow to re run non idempotent but stored workflow with the same transaction id if considered done (#12362)
This commit is contained in:
committed by
GitHub
parent
97dd520c64
commit
80007f3afd
@@ -73,6 +73,7 @@ describe("Transaction Orchestrator", () => {
|
||||
await strategy.resume(transaction)
|
||||
|
||||
expect(transaction.transactionId).toBe("transaction_id_123")
|
||||
expect(transaction.runId).toEqual(expect.any(String))
|
||||
expect(transaction.getState()).toBe(TransactionState.DONE)
|
||||
|
||||
expect(mocks.one).toBeCalledWith(
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface IDistributedSchedulerStorage {
|
||||
export interface IDistributedTransactionStorage {
|
||||
get(
|
||||
key: string,
|
||||
options?: TransactionOptions
|
||||
options?: TransactionOptions & { isCancelling?: boolean }
|
||||
): Promise<TransactionCheckpoint | undefined>
|
||||
list(): Promise<TransactionCheckpoint[]>
|
||||
save(
|
||||
|
||||
@@ -79,6 +79,7 @@ export class TransactionPayload {
|
||||
class DistributedTransaction extends EventEmitter {
|
||||
public modelId: string
|
||||
public transactionId: string
|
||||
public runId: string
|
||||
|
||||
private readonly errors: TransactionStepError[] = []
|
||||
private readonly context: TransactionContext = new TransactionContext()
|
||||
@@ -109,7 +110,7 @@ class DistributedTransaction extends EventEmitter {
|
||||
|
||||
this.transactionId = flow.transactionId
|
||||
this.modelId = flow.modelId
|
||||
|
||||
this.runId = flow.runId
|
||||
if (errors) {
|
||||
this.errors = errors
|
||||
}
|
||||
@@ -220,7 +221,8 @@ class DistributedTransaction extends EventEmitter {
|
||||
|
||||
public static async loadTransaction(
|
||||
modelId: string,
|
||||
transactionId: string
|
||||
transactionId: string,
|
||||
options?: { isCancelling?: boolean }
|
||||
): Promise<TransactionCheckpoint | null> {
|
||||
const key = TransactionOrchestrator.getKeyName(
|
||||
DistributedTransaction.keyPrefix,
|
||||
@@ -228,12 +230,13 @@ class DistributedTransaction extends EventEmitter {
|
||||
transactionId
|
||||
)
|
||||
|
||||
const options = TransactionOrchestrator.getWorkflowOptions(modelId)
|
||||
const workflowOptions = TransactionOrchestrator.getWorkflowOptions(modelId)
|
||||
|
||||
const loadedData = await DistributedTransaction.keyValueStore.get(key, {
|
||||
...workflowOptions,
|
||||
isCancelling: options?.isCancelling,
|
||||
})
|
||||
|
||||
const loadedData = await DistributedTransaction.keyValueStore.get(
|
||||
key,
|
||||
options
|
||||
)
|
||||
if (loadedData) {
|
||||
return loadedData
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ulid } from "ulid"
|
||||
import {
|
||||
DistributedTransaction,
|
||||
DistributedTransactionType,
|
||||
@@ -786,10 +787,13 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
|
||||
const execution: Promise<void | unknown>[] = []
|
||||
for (const step of nextSteps.next) {
|
||||
const { stopStepExecution } = this.prepareStepForExecution(step, flow)
|
||||
const { shouldContinueExecution } = this.prepareStepForExecution(
|
||||
step,
|
||||
flow
|
||||
)
|
||||
|
||||
// Should stop the execution if next step cant be handled
|
||||
if (!stopStepExecution) {
|
||||
if (!shouldContinueExecution) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -867,7 +871,7 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
private prepareStepForExecution(
|
||||
step: TransactionStep,
|
||||
flow: TransactionFlow
|
||||
): { stopStepExecution: boolean } {
|
||||
): { shouldContinueExecution: boolean } {
|
||||
const curState = step.getStates()
|
||||
|
||||
step.lastAttempt = Date.now()
|
||||
@@ -883,7 +887,7 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
|
||||
if (step.definition.noCompensation) {
|
||||
step.changeState(TransactionStepState.REVERTED)
|
||||
return { stopStepExecution: false }
|
||||
return { shouldContinueExecution: false }
|
||||
}
|
||||
} else if (flow.state === TransactionState.INVOKING) {
|
||||
step.changeState(TransactionStepState.INVOKING)
|
||||
@@ -892,7 +896,7 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
|
||||
step.changeStatus(TransactionStepStatus.WAITING)
|
||||
|
||||
return { stopStepExecution: true }
|
||||
return { shouldContinueExecution: true }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1239,6 +1243,7 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
}
|
||||
|
||||
flow.state = TransactionState.WAITING_TO_COMPENSATE
|
||||
flow.cancelledAt = Date.now()
|
||||
|
||||
await this.executeNext(transaction)
|
||||
}
|
||||
@@ -1264,7 +1269,8 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
hasStepTimeouts ||
|
||||
hasRetriesTimeout ||
|
||||
hasTransactionTimeout ||
|
||||
isIdempotent
|
||||
isIdempotent ||
|
||||
this.options.retentionTime
|
||||
) {
|
||||
this.options.store = true
|
||||
}
|
||||
@@ -1292,6 +1298,7 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
modelId: this.id,
|
||||
options: this.options,
|
||||
transactionId: transactionId,
|
||||
runId: ulid(),
|
||||
metadata: flowMetadata,
|
||||
hasAsyncSteps: features.hasAsyncSteps,
|
||||
hasFailedSteps: false,
|
||||
@@ -1310,11 +1317,13 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
|
||||
private static async loadTransactionById(
|
||||
modelId: string,
|
||||
transactionId: string
|
||||
transactionId: string,
|
||||
options?: { isCancelling?: boolean }
|
||||
): Promise<TransactionCheckpoint | null> {
|
||||
const transaction = await DistributedTransaction.loadTransaction(
|
||||
modelId,
|
||||
transactionId
|
||||
transactionId,
|
||||
options
|
||||
)
|
||||
|
||||
if (transaction !== null) {
|
||||
@@ -1487,10 +1496,15 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
*/
|
||||
public async retrieveExistingTransaction(
|
||||
transactionId: string,
|
||||
handler: TransactionStepHandler
|
||||
handler: TransactionStepHandler,
|
||||
options?: { isCancelling?: boolean }
|
||||
): Promise<DistributedTransactionType> {
|
||||
const existingTransaction =
|
||||
await TransactionOrchestrator.loadTransactionById(this.id, transactionId)
|
||||
await TransactionOrchestrator.loadTransactionById(
|
||||
this.id,
|
||||
transactionId,
|
||||
{ isCancelling: options?.isCancelling }
|
||||
)
|
||||
|
||||
if (!existingTransaction) {
|
||||
throw new MedusaError(
|
||||
|
||||
@@ -261,6 +261,7 @@ export type TransactionFlow = {
|
||||
options?: TransactionModelOptions
|
||||
definition: TransactionStepsDefinition
|
||||
transactionId: string
|
||||
runId: string
|
||||
metadata?: {
|
||||
eventGroupId?: string
|
||||
parentIdempotencyKey?: string
|
||||
@@ -277,6 +278,7 @@ export type TransactionFlow = {
|
||||
hasRevertedSteps: boolean
|
||||
timedOutAt: number | null
|
||||
startedAt?: number
|
||||
cancelledAt?: number
|
||||
state: TransactionState
|
||||
steps: {
|
||||
[key: string]: TransactionStep
|
||||
|
||||
@@ -386,7 +386,8 @@ export class LocalWorkflow {
|
||||
|
||||
const transaction = await orchestrator.retrieveExistingTransaction(
|
||||
uniqueTransactionId,
|
||||
handler(this.container_, context)
|
||||
handler(this.container_, context),
|
||||
{ isCancelling: context?.isCancelling }
|
||||
)
|
||||
|
||||
return transaction
|
||||
|
||||
Reference in New Issue
Block a user