Locking model¶
Every operation acquires a single database-wide write lock with two layers:
- In-process:
threading.RLock(reentrant, so an active transaction can nest operations freely). - Cross-process:
portalockerRLockontinystore.lock(OS-level file locking, so independent processes can safely share one database).
Configuring the wait¶
Configure the wait with Database(path, lock_timeout=...); a timeout raises
LockTimeoutError. The lock file's existence is never treated as the lock —
only a successful acquire() matters.
from tinystore import Database, LockTimeoutError
try:
db = Database("./app_data", lock_timeout=5.0)
except LockTimeoutError:
... # another writer held the lock for > 5s during open
What this means¶
- Single writer. One database-wide write lock means no concurrent writes within a process or across processes. Readers also acquire the lock, so a long-running transaction blocks all other operations.
- Crash-safe. Because the lock is an OS-level file lock, a crashed process releases it automatically — no "stale lock file" problem.
Don't rely on the lock file's existence¶
The tinystore.lock file may exist even when no process holds the lock. Always go
through db.lock(...) or the implicit locking in each operation. See the
Transactions guide for how locking interacts with atomicity.