Skip to content

Instantly share code, notes, and snippets.

@danmackinlay
Created August 27, 2009 02:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danmackinlay/176027 to your computer and use it in GitHub Desktop.
Save danmackinlay/176027 to your computer and use it in GitHub Desktop.
django- or FS-based contextmanager-friendly locks for python
"""
various ways of acquiring cross-process locks on a string including a django one
http://www.evanfosmark.com/2009/01/cross-platform-file-locking-support-in-python/
needs test coverage, but won't get it here since I've just removed it from the project.
`with` statement requires python 2.5 or later; these may work as non-with statement doohickeys
django locks require version 1.0 or later to get the "force_insert" argument
"""
import os
import errno
class BadName(Exception):
"""
raise when the lock string requested is invalid for the locking method
"""
pass
class LockException(Exception):
pass
class FileLock(object):
"""
A file locking mechanism that has context-manager support so
you can use it in a with statement. This should be relatively cross
compatible as it doesn't rely on msvcrt or fcntl for the locking.
"""
def __init__(self, name):
from django.conf import settings
lock_path = settings.TICKET_LOCK_PATH
"""
Prepare the file locker. Specify the file to lock. Do some
sanity checks to make sure you ain't messing with the wrong
bits of the FS.
this should surely be farmed off to an OS-specific library.
"""
if name.find("/")>-1: raise BadName("%s is not a safe name to lock on the FS")
if name.find("\\")>-1: raise BadName("%s is not a safe name to lock on the FS")
if name.find("..")>-1: raise BadName("%s is not a safe name to lock on the FS")
# if name.find("|")>-1: raise BadName("%s a bad name, since '|' is reserved as a separator")
self.is_locked = False
self.lockfile = os.path.join(lock_path, os.getcwd(), "%s" % name)
self.name = name
def acquire(self):
"""
Acquire the lock, if possible
"""
try:
self.fd = os.open(self.lockfile, os.O_CREAT|os.O_EXCL|os.O_RDWR)
self.is_locked = True
except OSError as e:
raise FileLockException("That was locked. stoppit.")
def release(self):
"""
Get rid of the lock by deleting the lockfile.
When working in a `with` statement, this gets automatically
called at the end.
"""
if self.is_locked:
os.close(self.fd)
os.unlink(self.lockfile)
self.is_locked = False
def __enter__(self):
"""
Activated when used in the with statement.
Should automatically acquire a lock to be used in the with block.
"""
if not self.is_locked:
self.acquire()
return self
def __exit__(self, type, value, traceback):
"""
Activated at the end of the with statement.
It automatically releases the lock if it isn't locked.
"""
if self.is_locked:
self.release()
def __del__(self):
"""
Make sure that the FileLock instance doesn't leave a lockfile
lying around.
"""
self.release()
class DummyLock(object):
"""
convenience dummy lock that always allows you the lock
"""
def __init__(self, *args, **kwargs):
pass
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
pass
class DBLock(FileLock):
"""
this lock uses the
"""
def __init__(self, name):
self.is_locked = False
self.name = name
def acquire(self):
"""
Acquire the lock, if possible, by locking in the DB. This relies on a minimal DB lock class that looks like this
class TicketLock(models.Model):
'''
minimal lock model
'''
name = models.CharField(primary_key=True, unique=True, max_length=128, blank=False)
created = models.DateTimeField(auto_now=1, editable=False)
"""
from ticket.models import TicketLock
try:
lock = TicketLock(name=self.name)
self.lock.save(force_insert=True)
self.lock = lock
self.is_locked = True
except Exception as e:
#Horrible hack. but db errors have no shared base:
if e.__class__.__name__ == 'IntegrityError':
raise LockException("%s was locked." % self.name)
else:
raise
def release(self):
"""
Get rid of the lock by deleting the lockfile.
When working in a `with` statement, this gets automatically
called at the end.
"""
if self.is_locked:
self.lock.delete()
self.is_locked = False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment