Skip to content
Snippets Groups Projects
Commit cf57a3a7 authored by Alan De Smet's avatar Alan De Smet
Browse files

Cleaning up exclusivelockfile

parent 57c22b34
No related branches found
No related tags found
No related merge requests found
......@@ -6,20 +6,56 @@ import logging
class ExclusiveLockFile:
""" Lock a file by name or handle, returning an open file handle
Intended to be used in a "with" block as a context manager.
Can be used in a "with" block as a context manager.
>>> import tempfile
>>> with tempfile.TemporaryDirectory() as dir:
... lockfile = dir+"/lock"
... open(lockfile,"w").close()
... with ExclusiveLockFile(lockfile) as f:
... # Can assume exclusive control of lockfile
... # Can use f as a file object
... pass
... f = open(lockfile,"w")
... with ExclusiveLockFile(f):
... # Can assume exclusive control of lockfile
... pass
>>> from exclusivelockfile import ExclusiveLockFile
>>> from tempfile import mkdtemp
>>> tmpdir = mkdtemp()
>>> filename = tmpdir+"/lock"
>>>
>>> # You can't lock a file until it exists
>>>
>>> ExclusiveLockFile(filename) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
FileNotFoundError: [Errno 2] No such file or directory: '.../lock'
>>>
>>> # You can lock a file-like object that support f.name and fcntl.lockf(f)
>>>
>>> f = open(filename,"w")
>>> with ExclusiveLockFile(f):
... # Can assume exclusive control of filename
... pass
>>> f.close()
>>>
>>> # You can lock an existing file by name (str, bytes, or Path-like object).
>>>
>>> with ExclusiveLockFile(filename) as f:
... # Can assume exclusive control of filename
... # Can use f as a file object
... pass
>>>
>>> # Also supports manual use; you MUST call unlock in this case!
>>>
>>> l = ExclusiveLockFile(filename)
>>> l.lock()
>>> # Can assume exclusive control of filename
>>> l.unlock()
>>>
>>> # Error cases
>>> l.unlock() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
RuntimeError: Attempting to unlock .../lock, but I don't have that lock
>>> l.lock()
>>> l.lock() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
RuntimeError: Attempting to lock .../lock, but I already have that lock
>>>
>>> import os
>>> os.unlink(filename)
>>> os.rmdir(tmpdir)
"""
def __init__(self, filename_or_file):
......@@ -31,56 +67,7 @@ class ExclusiveLockFile:
file-like objects will only work to the extent that
fcntl.lockf works on them.
>>> from exclusivelockfile import ExclusiveLockFile
>>> from tempfile import mkdtemp
>>> tmpdir = mkdtemp()
>>> filename = tmpdir+"/lock"
>>>
>>> # You can't lock a file until it exists
>>>
>>> ExclusiveLockFile(filename) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
FileNotFoundError: [Errno 2] No such file or directory: '.../lock'
>>>
>>> # You can lock a file-like object that support f.name and fcntl.lockf(f)
>>>
>>> f = open(filename,"w")
>>> with ExclusiveLockFile(f):
... # Can assume exclusive control of filename
... pass
>>> f.close()
>>>
>>> # You can lock an existing file by name (str, bytes, or Path-like object).
>>>
>>> with ExclusiveLockFile(filename) as f:
... # Can assume exclusive control of filename
... # Can use f as a file object
... pass
>>>
>>> # Also supports manual use; you MUST call unlock in this case!
>>>
>>> l = ExclusiveLockFile(filename)
>>> l.lock()
>>> # Can assume exclusive control of filename
>>> l.unlock()
>>>
>>> # Error cases
>>> l.unlock() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
RuntimeError: Attempting to unlock .../lock, but I don't have that lock
>>> l.lock()
>>> l.lock() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
RuntimeError: Attempting to lock .../lock, but I already have that lock
>>>
>>> import os
>>> os.unlink(filename)
>>> os.rmdir(tmpdir)
Extended examples are in help(exclusivelockfile.ExclusiveLockFile)
"""
try:
# Ensure it's Path-like
......@@ -88,7 +75,7 @@ class ExclusiveLockFile:
self.file = None
self.filename = filename_or_file
except:
# Hopefully it's file-like
# Not Path-like? I hope it's file-like
self.file = filename_or_file
self.filename = None
......@@ -202,7 +189,7 @@ def file_matches_filename(file, filename):
return False
class AtomicCreateIfMissing:
""" Context manager to atomically crate a file
""" Context manager to atomically create a file
>>> from exclusivelockfile import AtomicCreateIfMissing
>>> from tempfile import mkdtemp
......@@ -264,62 +251,3 @@ class AtomicCreateIfMissing:
os.rename(self.lockfilename, self.filename)
self.lock.unlock()
if False:
class ExclusiveLockFileOLD:
""" Lock a temporary file created for the purpose.
Intended to be used in a "with" block as a context manager.
The file will be created if it doesn't exist. It will be deleted when the
block exits! The filename should not be a directory.
If multiple processes try to lock the same file, the first to succeed will
delete it when done. That means that a process starting after that point
will create _new_ file and lock that, not sharing the previous one! This
makes it only suitable for VERY specialized purposes (like ensuring only
one process downloads a file, and later processes will immediately bail out
if the downloaded file is already preent).
Be warned that this is implemented using fcntl.lockf which is an ADVISORY
lock; only code using fcntl.lockf or similar will resepect it.
WILL create missing directories if necessary, will NOT
remove them on completion.
>>> import tempfile
>>> with tempfile.TemporaryDirectory() as dir:
... with ExclusiveLockFile(dir+"/lock"):
... # In this block, the file f.name is exclusively locked
... # using fcntl.lockf. (Only code using fcntl.lockf or
... # equivalent will respect this!)
... pass
"""
def __init__(self, filename):
self.filename = filename
def lock(self):
dir = os.path.dirname(self.filename)
if len(dir) and not os.path.exists(dir):
logging.debug("directory {} does not exist to hold lockfile {}; creating".format(dir, self.filename))
os.makedirs(dir)
logging.debug("Attempting to lock {0}".format(self.filename))
self.f = open(self.filename, "w")
fcntl.lockf(self.f, fcntl.LOCK_EX)
logging.debug("Locked {0}".format(self.filename))
def unlock(self):
fcntl.lockf(self.f, fcntl.LOCK_UN)
self.f.close()
try:
os.unlink(self.filename)
except FileNotFoundError:
# another lock erased me; we're okay with that
pass
logging.debug("unlocked {0}".format(self.filename))
def __enter__(self):
self.lock()
def __exit__(self, type, value, traceback):
self.unlock()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment