chore(): Workflow engine timers and notification improvements (#13434)

RESOLVES CORE-1177

**What**
main changes are:
- not blocking execution when notifying
- timers management
- race condition checks improvements
This commit is contained in:
Adrien de Peretti
2025-09-08 20:19:55 +02:00
committed by GitHub
parent b776fd55dc
commit fc4d5f0ac9
14 changed files with 464 additions and 213 deletions

View File

@@ -9,11 +9,13 @@ import {
import {
ContainerLike,
Context,
Logger,
MedusaContainer,
} from "@medusajs/framework/types"
import {
isString,
MedusaError,
promiseAll,
TransactionState,
} from "@medusajs/framework/utils"
import {
@@ -101,6 +103,7 @@ export class WorkflowOrchestratorService {
private subscribers: Subscribers = new Map()
private container_: MedusaContainer
private inMemoryDistributedTransactionStorage_: InMemoryDistributedTransactionStorage
readonly #logger: Logger
constructor({
inMemoryDistributedTransactionStorage,
@@ -113,6 +116,10 @@ export class WorkflowOrchestratorService {
this.container_ = sharedContainer
this.inMemoryDistributedTransactionStorage_ =
inMemoryDistributedTransactionStorage
this.#logger =
this.container_.resolve("logger", { allowUnregistered: true }) ?? console
inMemoryDistributedTransactionStorage.setWorkflowOrchestratorService(this)
DistributedTransaction.setStorage(inMemoryDistributedTransactionStorage)
WorkflowScheduler.setStorage(inMemoryDistributedTransactionStorage)
@@ -673,46 +680,49 @@ export class WorkflowOrchestratorService {
}
private notify(options: NotifyOptions) {
const {
eventType,
workflowId,
transactionId,
errors,
result,
step,
response,
state,
} = options
// Process subscribers asynchronously to avoid blocking workflow execution
setImmediate(() => this.processSubscriberNotifications(options))
}
private async processSubscriberNotifications(options: NotifyOptions) {
const { workflowId, transactionId, eventType } = options
const subscribers: TransactionSubscribers =
this.subscribers.get(workflowId) ?? new Map()
const notifySubscribers = (handlers: SubscriberHandler[]) => {
handlers.forEach((handler) => {
handler({
eventType,
workflowId,
transactionId,
step,
response,
result,
errors,
state,
})
const notifySubscribersAsync = async (handlers: SubscriberHandler[]) => {
const promises = handlers.map(async (handler) => {
try {
const result = handler(options) as void | Promise<any>
if (result && typeof result === "object" && "then" in result) {
await (result as Promise<any>)
}
} catch (error) {
this.#logger.error(`Subscriber error: ${error}`)
}
})
await promiseAll(promises)
}
const tasks: Promise<void>[] = []
if (transactionId) {
const transactionSubscribers = subscribers.get(transactionId) ?? []
notifySubscribers(transactionSubscribers)
if (transactionSubscribers.length > 0) {
tasks.push(notifySubscribersAsync(transactionSubscribers))
}
if (options.eventType === "onFinish") {
if (eventType === "onFinish") {
subscribers.delete(transactionId)
}
}
const workflowSubscribers = subscribers.get(AnySubscriber) ?? []
notifySubscribers(workflowSubscribers)
if (workflowSubscribers.length > 0) {
tasks.push(notifySubscribersAsync(workflowSubscribers))
}
await promiseAll(tasks)
}
private buildWorkflowEvents({

View File

@@ -284,36 +284,26 @@ export class WorkflowsModuleService<
} as any)
}
@InjectSharedContext()
async subscribe(
args: {
workflowId: string
transactionId?: string
subscriber: Function
subscriberId?: string
},
@MedusaContext() context: Context = {}
) {
async subscribe(args: {
workflowId: string
transactionId?: string
subscriber: Function
subscriberId?: string
}) {
return this.workflowOrchestratorService_.subscribe(args as any)
}
@InjectSharedContext()
async unsubscribe(
args: {
workflowId: string
transactionId?: string
subscriberOrId: string | Function
},
@MedusaContext() context: Context = {}
) {
async unsubscribe(args: {
workflowId: string
transactionId?: string
subscriberOrId: string | Function
}) {
return this.workflowOrchestratorService_.unsubscribe(args as any)
}
@InjectSharedContext()
async cancel<TWorkflow extends string | ReturnWorkflow<any, any, any>>(
workflowIdOrWorkflow: TWorkflow,
options: WorkflowOrchestratorCancelOptions,
@MedusaContext() context: Context = {}
options: WorkflowOrchestratorCancelOptions
) {
return await this.workflowOrchestratorService_.cancel(
workflowIdOrWorkflow,