49 lines
1.8 KiB
Python
49 lines
1.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import errno
|
|
import fcntl
|
|
import os
|
|
import time
|
|
from contextlib import contextmanager
|
|
|
|
@contextmanager
|
|
def lock(lock_file, fail_callback=None):
|
|
# Open the lock file in append mode first to ensure its existence but not modify any data if it already exists
|
|
with open(lock_file, 'a'):
|
|
pass
|
|
# Open the lock file in read + write mode without truncation
|
|
with open(lock_file, 'r+') as f:
|
|
while True:
|
|
try:
|
|
# Try to obtain exclusive lock in non-blocking mode
|
|
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
break
|
|
except OSError as e:
|
|
# If lock is already locked by another process
|
|
if e.errno == errno.EAGAIN:
|
|
if fail_callback:
|
|
# Call the callback function with contents of the lock file (PID of the process holding the lock)
|
|
fail_callback(f.read())
|
|
# Remove the callback function so it's not called in every loop
|
|
fail_callback = None
|
|
# Set the position for future truncation
|
|
f.seek(0)
|
|
# Wait for the lock to be freed
|
|
time.sleep(0.1)
|
|
else:
|
|
raise
|
|
# If the lock was obtained, truncate the file and write PID of the process holding the lock
|
|
f.truncate()
|
|
f.write(str(os.getpid()))
|
|
f.flush()
|
|
yield f
|
|
|
|
# Function decorator
|
|
def locked(lock_file, fail_callback=None):
|
|
def decorator(target):
|
|
def wrapper(*args, **kwargs):
|
|
with lock(lock_file, fail_callback):
|
|
return target(*args, **kwargs)
|
|
return wrapper
|
|
return decorator
|