fix: workflow async concurrency (#13769)

* executeAsync

* || 1

* wip

* stepId

* stepId

* wip

* wip

* continue versioning management changes

* fix and improve concurrency

* update in memory engine

* remove duplicated test

* fix script

* Create weak-drinks-confess.md

* fixes

* fix

* fix

* continuation

* centralize merge checkepoint

* centralize merge checkpoint

* fix locking

* rm only

* Continue improvements and fixes

* fixes

* fixes

* hasAwaiting will be recomputed

* fix orchestrator engine

* bump version on async parallel steps only

* mark as delivered fix

* changeset

* check partitions

* avoid saving when having parent step

* cart test

---------

Co-authored-by: Carlos R. L. Rodrigues <rodrigolr@gmail.com>
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2025-10-20 15:29:19 +02:00
committed by GitHub
parent d97a60d3c1
commit 516f5a3896
31 changed files with 2712 additions and 1406 deletions

View File

@@ -11,16 +11,20 @@ import {
adminHeaders,
createAdminUser,
} from "../../../../helpers/create-admin-user"
import { setTimeout } from "timers/promises"
import { IWorkflowEngineService } from "@medusajs/framework/types"
jest.setTimeout(300000)
medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
let container
let workflowOrcModule: IWorkflowEngineService
beforeEach(async () => {
container = getContainer()
await createAdminUser(dbConnection, adminHeaders, container)
workflowOrcModule = container.resolve(Modules.WORKFLOW_ENGINE)
})
describe("GET /admin/workflow-executions", () => {
@@ -90,5 +94,181 @@ medusaIntegrationTestRunner({
)
})
})
describe("Workflow Orchestrator module subscribe", function () {
it("should subscribe to a workflow and receive the response when it finishes", async () => {
const step1 = createStep({ name: "step1" }, async () => {
return new StepResponse("step1")
})
const step2 = createStep({ name: "step2" }, async () => {
await setTimeout(1000)
return new StepResponse("step2")
})
const workflowId =
"workflow" + Math.random().toString(36).substring(2, 15)
createWorkflow(workflowId, function (input) {
step1()
step2().config({
async: true,
})
return new WorkflowResponse("workflow")
})
const step1_1 = createStep({ name: "step1_1" }, async () => {
return new StepResponse("step1_1")
})
const step2_1 = createStep({ name: "step2_1" }, async () => {
await setTimeout(1000)
return new StepResponse("step2_1")
})
const workflow2Id =
"workflow_2" + Math.random().toString(36).substring(2, 15)
createWorkflow(workflow2Id, function (input) {
step1_1()
step2_1().config({
async: true,
})
return new WorkflowResponse("workflow_2")
})
const transactionId =
"trx_123" + Math.random().toString(36).substring(2, 15)
const transactionId2 =
"trx_124" + Math.random().toString(36).substring(2, 15)
const onWorkflowFinishSpy = jest.fn()
const onWorkflowFinishPromise = new Promise<void>((resolve) => {
void workflowOrcModule.subscribe({
workflowId: workflowId,
transactionId,
subscriber: (event) => {
console.log("event", event)
if (event.eventType === "onFinish") {
onWorkflowFinishSpy()
workflowOrcModule.run(workflow2Id, {
transactionId: transactionId2,
})
resolve()
}
},
})
})
const onWorkflow2FinishSpy = jest.fn()
const workflow2FinishPromise = new Promise<void>((resolve) => {
void workflowOrcModule.subscribe({
workflowId: workflow2Id,
subscriber: (event) => {
console.log("event", event)
if (event.eventType === "onFinish") {
onWorkflow2FinishSpy()
resolve()
}
},
})
})
workflowOrcModule.run(workflowId, {
transactionId,
})
await onWorkflowFinishPromise
await workflow2FinishPromise
expect(onWorkflowFinishSpy).toHaveBeenCalledTimes(1)
expect(onWorkflow2FinishSpy).toHaveBeenCalledTimes(1)
})
it("should subscribe to a workflow and receive the response when it finishes (2)", async () => {
const step1 = createStep({ name: "step1" }, async () => {
return new StepResponse("step1")
})
const step2 = createStep({ name: "step2" }, async () => {
await setTimeout(1000)
return new StepResponse("step2")
})
const workflowId =
"workflow" + Math.random().toString(36).substring(2, 15)
createWorkflow(workflowId, function (input) {
step1()
step2().config({
async: true,
})
return new WorkflowResponse("workflow")
})
const step1_1 = createStep({ name: "step1_1" }, async () => {
return new StepResponse("step1_1")
})
const step2_1 = createStep({ name: "step2_1" }, async () => {
await setTimeout(1000)
return new StepResponse("step2_1")
})
const workflow2Id =
"workflow_2" + Math.random().toString(36).substring(2, 15)
createWorkflow(workflow2Id, function (input) {
step1_1()
step2_1().config({
async: true,
})
return new WorkflowResponse("workflow_2")
})
const transactionId =
"trx_123" + Math.random().toString(36).substring(2, 15)
const transactionId2 =
"trx_124" + Math.random().toString(36).substring(2, 15)
const onWorkflowFinishSpy = jest.fn()
const onWorkflowFinishPromise = new Promise<void>((resolve) => {
void workflowOrcModule.subscribe({
workflowId: workflowId,
transactionId,
subscriber: (event) => {
console.log("event", event)
if (event.eventType === "onFinish") {
onWorkflowFinishSpy()
workflowOrcModule.run(workflow2Id, {
transactionId: transactionId2,
})
resolve()
}
},
})
})
const onWorkflow2FinishSpy = jest.fn()
const workflow2FinishPromise = new Promise<void>((resolve) => {
void workflowOrcModule.subscribe({
workflowId: workflow2Id,
subscriber: (event) => {
console.log("event", event)
if (event.eventType === "onFinish") {
onWorkflow2FinishSpy()
resolve()
}
},
})
})
workflowOrcModule.run(workflowId, {
transactionId,
})
await onWorkflowFinishPromise
await workflow2FinishPromise
expect(onWorkflowFinishSpy).toHaveBeenCalledTimes(1)
expect(onWorkflow2FinishSpy).toHaveBeenCalledTimes(1)
})
})
},
})