Building an API with Locking

Protect read-modify-write endpoints from concurrent callers: claim a lock around the critical section, and make sure it is always released, on success and on failure.

Two requests updating the same document at the same moment lose one of the updates: both read, both compute, both write, and the second write overwrites the first. This tutorial wraps that read-modify-write section in a lock, so concurrent callers queue up instead of colliding. It assumes a registered endpoint and document updates; see Build Your First API and Storages. Background on when the platform locks for you (and when it does not) is in Locking and Concurrency.

1. The Lock

Three blocks, one job each:

Block Does what
function_claim_lock_wait (lock or wait) Claims the lock for a key, waiting until the current holder releases it. The right default for an API: every caller gets its turn.
function_claim_lock_v2 (claim lock) Tries once and reports through its condition out pin whether it got the lock. Use it when a failed attempt should answer "try again later" instead of waiting, for example a sync endpoint where a retry is cheaper than a queue.
function_claim_unlock (free lock) Releases the key. Must run on every path, including the failures.

The key names what is being protected. Build it from the identifier of the thing being updated with value_concatenate (concatenate), for example member- plus the member guid: one lock per document, so updates to different members never wait on each other. The expiry (seconds) is the safety net that frees a key whose holder crashed before unlocking.

2. The Critical Section

Between claim and release sits the work that must not interleave: read the document, compute, write it back.

The critical-section shape: a function trigger runs the flow, lock or wait claims the key (waiting when another request holds it), the document is read with get document and written with update document, and a funnel merges the paths so free lock always releases the key.

Studio canvas example for the lock or wait block: lock around a document update.

On the canvas: after the lock is claimed, function_get_document (get document) reads the current state, mutations_set_bp_field_multiple (set fields) builds the changes, and function_update_document_mutations (update document) writes them. The funnel is the load-bearing detail: it merges the exit paths (success, validation failure, not found) into one flow that always reaches the unlock block. Every path you add later gets the same treatment: point its end at the funnel, never straight out of the function.

Notes From Practice

  • Keep the critical section small. Everything slow (external calls, file work, email) belongs outside the lock; every second inside it is a second other callers wait.
  • Size the expiry to the worst realistic run of the section, not the average. Too short and a slow run loses its lock mid-write; too long and a crashed holder blocks everyone until timeout.
  • A lock is not a transaction. When the write itself must be atomic, use the document's own update blocks, which lock themselves per document (see Locking and Concurrency); claim your own lock for invariants that span the read-modify-write as a whole, like "only one active subscription per member".
  • Waiting callers still count against the request timeout. When waits can be long, hand the work to function_custom_execute_from_queue (execute in queue) with a unique_id per key instead; the queue serializes for you and the API replies immediately. See Queue.
Locking and Concurrency Which blocks lock themselves, and when to claim a lock manually. Queue Serialize work per key without holding an HTTP request open. Common Blueprint Patterns The endpoint and error-handling shapes around the lock.

Frequently asked

How do I lock a critical section in a RUAL API?

Claim a key with function_claim_lock_wait before the read-modify-write section and release it with function_claim_unlock afterwards, merging every exit path through a funnel so the unlock always runs. Build the key from the updated document's identifier so different documents never wait on each other.

What happens when a RUAL blueprint crashes while holding a lock?

The lock's expiry frees the key after the given number of seconds, so a crashed holder cannot block other callers forever. Size the expiry to the worst realistic run of the critical section.