Responsible operations
A lock file is a protocol, not a Boolean
A file named writer.lock does not create safety by existing.
Safety comes from the agreement around a lock: how a process acquires it, what it records, when it may remove it, and what happens after a crash.
This matters in scheduled workspaces. Two jobs can each read the same apparently idle state, both begin rendering or editing, and then overwrite each other's output. The final files may even look valid. The missing evidence is that no second writer changed them halfway through the run.
The check-then-create trap
This sequence is not a lock:
- check whether the lock path exists;
- see that it does not;
- create it.
A second process can pass step one before the first reaches step three. Both then believe they own the workspace.
Acquisition must be one indivisible attempt. A process can, for example, create a file with exclusive-create semantics or create a dedicated lock directory and treat an already-existing path as a failed acquisition. POSIX specifies that open() with both O_CREAT and O_EXCL must make the existence check and file creation atomic with respect to other threads executing the same operation. POSIX mkdir() likewise fails with EEXIST when the named path already exists. Python exposes the corresponding O_CREAT and O_EXCL flags through os.open on platforms that provide them. The precise primitive matters less than one rule: do not separate “is it free?” from “I now own it.”
Put enough evidence inside
A useful lock record should answer:
- what resource is protected;
- which process or job acquired it;
- when acquisition occurred;
- what operation is in progress;
- whether the lock is leased, and if so when that lease expires.
A process ID alone is weak evidence. Processes end, identifiers can be reused, and a lock can outlive the machine state that created it. A timestamp alone is also weak: a long render may be healthy while an impatient cleanup job calls it stale.
Prefer a small structured record and an operation-specific timeout chosen from observed runtimes. If the environment supports an operating-system advisory lock, use that ownership signal rather than guessing from age. If it does not, make stale-lock recovery an explicit protocol instead of an automatic deletion reflex.
Fail closed, then investigate
When acquisition fails, the safe default is simple: do not write.
A read-only check can still inspect the lock, validate finished artifacts, or report that another writer is active. It should not “help” by deleting the lock merely because no progress is visible from outside.
Recovery should require multiple pieces of evidence, such as an expired lease, no matching live owner in the same execution environment, and no recent changes to protected outputs. When those checks cannot be made reliably, stop and preserve the lock for review.
Release only what you own
The writer should release the lock in a guaranteed cleanup path after its final durable write. It should also verify an ownership token before removal. Otherwise, this failure is possible:
- job A's lease expires;
- job B safely recovers and acquires a new lock at the same path;
- job A wakes up and deletes job B's lock during cleanup.
A random acquisition token stored in the lock record lets job A distinguish its lock from a successor's. Cleanup should remove the lock only when that token still matches.
A lock does not make writes transactional
One-writer discipline prevents overlap; it does not guarantee that a multi-file update is complete. A crash can still leave half a bundle on disk.
Use temporary outputs, validate them, and replace final files only after they are ready. Do not call a sequence of separate file moves an atomic bundle update: readers can still observe a mixture of old and new files unless the design adds a transaction, an atomic directory-level handoff supported by the target environment, or a completion manifest that readers verify. SQLite, for example, documents that multiple connections may hold read transactions while only one write transaction can exist at a time. That is a stronger protocol than a casual sentinel file, though application-level operation boundaries still need to be designed.
The compact checklist
Before trusting a one-writer guard, verify:
- acquisition is atomic rather than check-then-create;
- failure to acquire causes a no-write exit;
- the protected resource and operation are named;
- the lock records acquisition time and an ownership token;
- stale recovery uses more than elapsed time;
- cleanup removes only the current owner's lock;
- artifacts are written to temporary paths and validated before replacement;
- readers report lock state without mutating it;
- logs distinguish “writer active,” “lock recovered,” and “write completed.”
The lock file is the visible part. The protocol is the actual safety feature.
Boundaries
These rules do not make every filesystem behave alike. Shared and network filesystems can differ in failure modes and guarantees, and a machine-level process check cannot establish that an owner is dead in another execution environment. Choose and test the acquisition primitive on the actual storage target. When ownership cannot be established reliably, preserve the lock and fail closed rather than guessing.
Source notes
- The Open Group,
open: primary POSIX specification for the atomic existence-check-and-create behavior ofO_CREAT | O_EXCL. - Python Software Foundation,
os.open: primary API reference showing Python's low-level open interface and platform-dependent flag constants. - The Open Group,
mkdir: primary POSIX specification for directory creation and theEEXISTfailure condition. - SQLite, Transaction documentation: primary documentation supporting the distinction between concurrent read transactions and a single simultaneous write transaction.
All four sources returned HTTPS 200 during final fact-checking on 2026-08-11. This note gives conservative operational guidance, not a claim that lock files provide identical guarantees on every filesystem or network mount.