ExZarr.Storage.FileLock (ExZarr v1.1.0)
View SourceFile locking utilities for cross-process coordination on filesystem storage.
Provides advisory file locking to prevent concurrent modifications to the same chunk file by different processes or even different BEAM instances.
Features
- Advisory locking via
:file.openwith:exclusivemode - Automatic lock release on process termination
- Timeout support for lock acquisition
- Compatible with NFS and other network filesystems (best-effort)
Lock Types
- Write locks: Use
:exclusivemode, prevents all other access - Read locks: Advisory only, multiple readers can open simultaneously
Limitations
- Advisory locks only (not enforced by OS)
- Network filesystem compatibility varies
- Lock files may persist if process crashes before cleanup
Usage
# Acquire write lock
{:ok, lock} = ExZarr.Storage.FileLock.acquire_write("/path/to/chunk", timeout: 5000)
# Write data...
# Release lock
:ok = ExZarr.Storage.FileLock.release(lock)
# Or use with_lock for automatic cleanup
ExZarr.Storage.FileLock.with_write_lock("/path/to/file", fn ->
# Write operations here
:ok
end)
Summary
Functions
Acquires a read lock on a file.
Acquires an exclusive write lock on a file.
Checks if a write lock exists for a file.
Releases a lock.
Removes a stale lock file.
Executes a function while holding a read lock.
Executes a function while holding a write lock.
Types
Functions
@spec acquire_read( String.t(), keyword() ) :: {:ok, lock()} | {:error, lock_error()}
Acquires a read lock on a file.
For read locks, we use a less restrictive approach since multiple readers are allowed. This is advisory only.
Options
:timeout- Maximum time to wait for lock in milliseconds (default: 5000)
Returns
{:ok, lock}if lock acquired{:error, reason}for errors
@spec acquire_write( String.t(), keyword() ) :: {:ok, lock()} | {:error, lock_error()}
Acquires an exclusive write lock on a file.
Creates a lock file with .lock extension. The lock is automatically
released when the process terminates or when explicitly released.
Options
:timeout- Maximum time to wait for lock in milliseconds (default: 5000):retries- Number of retry attempts (default: based on timeout)
Returns
{:ok, lock}if lock acquired{:error, :timeout}if lock couldn't be acquired{:error, reason}for other errors
Checks if a write lock exists for a file.
Returns true if a lock file exists, false otherwise.
Note: This is a best-effort check and subject to race conditions.
@spec release(lock()) :: :ok
Releases a lock.
Closes the lock file handle and removes the lock file if it's a write lock.
Removes a stale lock file.
Use with caution - only call this if you're certain the lock is stale (e.g., from a crashed process).
@spec with_read_lock(String.t(), keyword(), (-> result)) :: {:ok, result} | {:error, lock_error()} when result: term()
Executes a function while holding a read lock.
Similar to with_write_lock/3 but for read operations.
@spec with_write_lock(String.t(), keyword(), (-> result)) :: {:ok, result} | {:error, lock_error()} when result: term()
Executes a function while holding a write lock.
Automatically acquires the lock before executing and releases it after. Ensures cleanup even if the function raises an error.
Example
ExZarr.Storage.FileLock.with_write_lock("/path/to/file", timeout: 10_000, fn ->
# Write operations here
File.write!("/path/to/file", data)
end)