3.7.15. Locking Operations in Workflows
In this chapter, you’ll learn how to lock operations in your workflows to avoid race conditions and ensure data consistency.
What is a Lock?#
A lock is a mechanism that restricts multiple accesses to a resource or a piece of code, preventing multiple processes from modifying it simultaneously.
Locks are particularly useful in workflows where concurrent executions may lead to commerce risks. For example, Medusa uses locks in its cart workflows to prevent issues like overselling products or double charging customers.
The Locking Module handles the locking mechanism using the underlying provider. You can use the Locking Module's service in your workflows to create locks around critical sections of your code.
How to Use Locks in Workflows#
Medusa provides two steps that you can use to create locks in your workflows:
acquireLockStep
: Attempt to acquire a lock. If the lock is already held by another process, this step will wait until the lock is released. It will fail if a timeout occurs before acquiring the lock.releaseLockStep
: Release a previously acquired lock.
You can use these steps in your workflows to ensure that only one instance of a workflow can modify a resource at a time.
For example:
1import { createWorkflow } from "@medusajs/framework/workflows-sdk"2import { acquireLockStep, releaseLockStep } from "@medusajs/medusa/core-flows"3import { chargeCustomerStep } from "./steps/charge-customer-step"4 5type WorkflowInput = {6 customer_id: string;7 order_id: string;8}9 10export const chargeCustomerWorkflow = createWorkflow(11 "charge-customer",12 (input: WorkflowInput) => {13 acquireLockStep({14 key: input.order_id,15 // Attempt to acquire the lock for two seconds before timing out16 timeout: 2,17 // Lock is only held for a maximum of ten seconds18 ttl: 10,19 })20 21 chargeCustomerStep(input)22 23 releaseLockStep({24 key: input.order_id,25 })26 }27)
In this example, the workflow attempts to acquire a lock on an order using its ID as the lock key.
Once the lock is acquired, it executes the chargeCustomerStep
. After the step is complete, it releases the lock using the releaseLockStep
.
If the lock cannot be acquired within the specified timeout period, the acquireLockStep
will throw an error, and the workflow will fail.
acquireLockStep
, the step's compensation function will release the lock automatically.Locking Steps API#
Refer to the acquireLockStep and releaseLockStep for more information on the inputs of these steps.
How to Use Locks in Workflow Steps#
You can alternatively acquire and release locks within steps by resolving the Locking Module's service and using its acquire
and release
methods.
For example:
1import { createStep } from "@medusajs/framework/workflows-sdk"2 3type StepInput = {4 order_id: string5 customer_id: string6}7 8export const chargeCustomerStep = createStep(9 "charge-customer",10 async (input: StepInput, { container }) => {11 const lockingModuleService = container.resolve("locking")12 13 await lockingModuleService.acquire(input.order_id, {14 // Lock will auto-expire after 10 seconds15 expire: 10,16 })17 18 // TODO: charge customer19 20 await lockingModuleService.release(input.order_id)21 }22)
In this example, the chargeCustomerStep
step acquires a lock on the order using the order_id
as the lock key. After performing the operation, it releases the lock.
Locking Module Service API#
Refer to the Locking Module reference for more information on the Locking Module's service methods.