Fasteners
Fasteners is a Python package that provides useful cross-platform synchronization primitives. It extends Python's standard library by offering inter-process exclusive locks, inter-process reader-writer locks, and thread-based reader-writer locks. The library aims to simplify concurrent programming across processes and threads. The current version is 0.20, with releases occurring periodically, often tied to Python version support updates.
Common errors
-
ModuleNotFoundError: No module named 'fasteners'
cause The 'fasteners' package is not installed in the Python environment being used.fixInstall the package using pip: `pip install fasteners` -
AttributeError: 'builtin_function_or_method' object has no attribute 'acquire'
cause This error occurs when trying to call the `acquire` method on the `InterProcessLock` or `InterProcessReaderWriterLock` class itself, instead of an instantiated object of that class.fixInstantiate the lock class by adding parentheses: `lock = fasteners.InterProcessLock('path/to/lock.file')` instead of `lock = fasteners.InterProcessLock` -
fasteners InterProcessLock reentrant deadlock
cause The `fasteners.InterProcessLock` is not reentrant, meaning a process cannot acquire a lock it already holds. Attempting to do so will result in a deadlock or crash.fixDesign code to avoid recursive acquisition of `InterProcessLock`. If reentrancy is required, consider using Python's standard library `threading.RLock` for thread-level reentrant locks, or refactor the process-level logic. -
fasteners InterProcessReaderWriterLock upgrade deadlock
cause The `fasteners.InterProcessReaderWriterLock` does not support upgrading a read lock to a write lock (or downgrading a write lock to a read lock) while holding the initial lock. This can lead to deadlocks or crashes.fixExplicitly release the first lock (e.g., read lock) before attempting to acquire the second lock (e.g., write lock). Ensure distinct acquisition patterns to prevent concurrent upgrades/downgrades. -
fasteners.InterProcessLock acquire timeout
cause The `acquire()` method of `InterProcessLock` or `InterProcessReaderWriterLock` returns `False` if the lock cannot be acquired within the specified timeout, rather than raising an exception, which can lead to unexpected behavior if not handled.fixAlways check the boolean return value of `acquire(timeout=...)` to determine if the lock was successfully obtained, and handle the case where it returns `False`. Example: `if lock.acquire(timeout=10): ... else: handle_timeout()`
Warnings
- breaking Python version support is periodically dropped. Version 0.20 removed support for Python 3.8, 3.9, and PyPy 3.9. Previous versions (0.19, 0.18) also dropped support for older Python releases.
- gotcha `InterProcessLock` instances are *not reentrant*. Attempting to acquire a lock already held by the same process can lead to deadlocks or crashes upon release.
- gotcha `InterProcessReaderWriterLock` instances are *not upgradable*. You cannot acquire a read lock while holding a write lock, or vice versa, without risking deadlocks or crashes.
- gotcha `InterProcessLock` and `InterProcessReaderWriterLock` provide guarantees *only between processes*, not between threads within a single process. They are not inherently thread-safe for intra-process concurrency.
- gotcha When using `InterProcessLock` or `InterProcessReaderWriterLock`, the `path` argument to the constructor is critical. Different processes must use the exact same string path to refer to the same underlying lock. Mismatched paths will result in independent locks.
Install
-
pip install fasteners
Imports
- InterProcessLock
from fasteners import InterProcessLock
- InterProcessReaderWriterLock
from fasteners import InterProcessReaderWriterLock
- ReaderWriterLock
from fasteners import ReaderWriterLock
- interprocess_locked
@fasteners.interprocess_locked
from fasteners import interprocess_locked
- locked
@fasteners.locked
from fasteners import locked
Quickstart
import time
import fasteners
import os
# For inter-process locks, a file path is required.
# Use a temporary file path for demonstration.
lock_file_path = os.path.join(os.getcwd(), 'my_app.lock')
@fasteners.interprocess_locked(lock_file_path)
def protected_function(worker_id):
print(f"Worker {worker_id}: Acquired lock. Performing exclusive task...")
time.sleep(2) # Simulate work
print(f"Worker {worker_id}: Released lock.")
if __name__ == "__main__":
print(f"Attempting to run protected_function for worker 1...")
protected_function(1)
print(f"Attempting to run protected_function for worker 2...")
protected_function(2)
# Example with context manager for more control
print("\n--- Using context manager ---")
my_lock = fasteners.InterProcessLock(lock_file_path + '.ctx')
with my_lock:
print("Main process: Acquired lock via context manager.")
time.sleep(1)
print("Main process: Released lock via context manager.")
# Clean up lock files (optional, as fasteners typically handles this on exit)
if os.path.exists(lock_file_path):
os.remove(lock_file_path)
if os.path.exists(lock_file_path + '.ctx'):
os.remove(lock_file_path + '.ctx')