Locking and Concurrency
Document updates take a lock for you. File actions do not. This page shows which operations are safe on their own, and how to claim a lock yourself for the ones that are not.
Your blueprints run on more than one node, and the same flow can be running several times at once: two users clicking at the same moment, a queue worker next to a page request, a repeating event that overlaps its previous run. Whether that is safe depends entirely on which operation you are performing.
Document actions lock themselves
Updating a storage document is guarded for you. The update takes a lock on that document, applies the mutations, and releases it, so two concurrent updates queue up instead of overwriting each other. This is what makes counters and array mutations safe: an increment is applied to the value as it is at that moment, not to the value your flow read a second ago.
On top of that, a document carries an update_hash in its _meta. An update that supplies the hash it read is rejected with a conflict when another writer got there first, which is the check to use when you want "only write if nothing changed since I read it" rather than "apply my change on top".
File actions do not lock
File actions have no equivalent guard. Appending to a file, writing one, copying or deleting one: each simply performs the operation. Two flows appending to the same file at the same time interleave their writes, and a flow that reads a file, changes the contents and writes it back loses whatever the other flow wrote in between.
Anything that touches a shared file therefore needs an explicit lock. This is the single most common cause of "the file is missing lines" and "the counter file went backwards".
Claiming a lock
| Block | Behaviour |
|---|---|
function_claim_lock_v2 |
Tries to claim the lock once and reports on its success out-pin whether it got it. Use it when the flow can do something sensible on failure, such as replying "busy, try again". |
function_claim_lock_wait |
Waits until the lock is free, retrying every two seconds, then continues. Use it when the work must happen and the caller can wait. |
function_claim_unlock |
Releases the lock for a key. |
Both claim blocks take a key and an expiry (sec.), which defaults to 60. The expiry is a safety net: if a run dies without releasing, the lock disappears on its own instead of blocking the next run forever. Pick a key that identifies the resource you are protecting, for example the file path or the document guid, and pick an expiry longer than the slowest realistic run.
Since core 15.0.0 both claim blocks also have an auto clear pin. With it set, the lock is released when the flow ends, including when the flow fails, so you do not need a separate free lock block on every exit path.
auto clear, or make sure every path out of the flow, including the error path, reaches function_claim_unlock. The pattern for a shared file
- Claim a lock keyed on the file path.
- Read the file, change it, write it back.
- Release the lock, or let
auto cleardo it.
Keep the locked section as small as possible. Fetching an external API or resizing an image inside the lock makes every other run wait for it, and pushes you towards an expiry long enough to be dangerous.
When you need a lock
| Situation | Lock needed |
|---|---|
| Increment a counter on a document | No. Use a mutation, which is applied sequentially per document. |
| Add a value to an array field on a document | No. The array mutations are applied the same way. |
| Read a document, compute a new value from it, write it back | Yes, or supply the update_hash you read and handle the conflict. |
| Append to a shared file, or rewrite a file in place | Yes, always. |
| A repeating event whose run can outlast its interval | Yes. Claim a lock on the job name so runs cannot overlap. |
| Calling an external API that must not be called twice for the same record | Yes, keyed on the record, in combination with a marker field so a retry is idempotent. |
Next steps
Object References and Copies The in-memory half of the same problem: objects are shared until you copy them. Storages Mutations, transactional updates and the _meta fields, including update_hash. Repeating Events Scheduling work, and keeping overlapping runs from colliding.Frequently asked
Do I need a lock to update a document in RUAL?
Usually not. A document update takes a lock on that document automatically, so concurrent updates queue instead of overwriting each other. You do need one when your flow reads a document, decides something, and writes the result, unless you supply the update_hash you read and handle the conflict.
Are file actions in RUAL safe when two flows run at the same time?
No. File actions have no automatic locking, unlike document actions. Two flows appending to the same file interleave their writes. Claim a lock keyed on the file path around any read, change and write back sequence.
How do I claim a lock in a RUAL blueprint?
Use function_claim_lock_v2 to try once and branch on its success out-pin, or function_claim_lock_wait to wait until the lock is free. Both take a key and an expiry in seconds that defaults to 60. Release with function_claim_unlock, or set the auto clear pin so the lock is released when the flow ends.
