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.
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
from fasteners import interprocess_locked
- 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')