Source code for fussy.cronlock

"""Provide a cron lock for preventing cron-bombs and the like"""
import os, logging, fcntl, errno, signal, tempfile
from optparse import OptionParser
log = logging.getLogger( __name__ )

[docs]class Busy( IOError ): """Raised if the lock is held by another cronlock"""
[docs]class Timeout( RuntimeError ): """Raised if the timeout signal triggers"""
[docs]class Flock( object ): """A context manager that just flocks a file""" file = None def __init__( self, filename ): self.filename = filename def __str__( self ): return '%s( %r )'%( self.__class__.__name__, self.filename ) def __enter__( self ): if self.file: raise Busy( """Attempted to lock the %s twice"""%( self )) fh = open( self.filename, 'a+' ) try: fcntl.flock( fh, fcntl.LOCK_EX|fcntl.LOCK_NB ) # can raise IOError except IOError, err: if err.errno == errno.EWOULDBLOCK: err = Busy( *err.args ) err.errno = errno.EWOULDBLOCK raise err fh.seek( 0 ) # rewind to start... self.file = fh return fh def __exit__( self, *args ): try: fh = self.__dict__.pop( 'file' ) except KeyError, err: pass else: fcntl.flock( fh, fcntl.LOCK_UN ) fh.close() def write( self, *args ): with self: self.file.write( *args ) self.file.flush() def read( self, *args ): with self: self.file.read( *args )
[docs]class Lock( object ): """A Context manager that provides cron-style locking"""
[docs] def __init__( self, lockfile, quiet_fail=False ): """Create a lock file name -- used to construct the lock-file name directory -- directory in which to construct the lock-file """ self.flock = Flock( lockfile ) self.quiet_fail = quiet_fail
@property def pid( self ): return os.getpid() def __enter__( self ): self.flock.__enter__() self.flock.file.write( str( self.pid )) self.flock.file.flush() def __exit__( self, *args ): self.flock.__exit__(*args) def on_timeout( self, *args, **named ): raise Timeout( "Maximum run-time exceeded, aborting" )
[docs] def set_timeout( self, duration ): """Set a signal to fire after duration and raise an error""" signal.signal( signal.SIGALRM, self.on_timeout ) signal.alarm( duration )
[docs]def with_lock( name, directory=None, timeout=None ): """Decorator that runs a function with Lock instance acquired * name -- basename of the file to create * directory -- if specified, the directory in which to store files, defaults to tempfile.gettempdir() * timeout -- if specified, the number of seconds to allow before raising a Timeout error """ if directory is None: directory = tempfile.gettempdir() filename = os.path.join( directory, name ) lock = Lock( filename ) def wrap_func( function ): """Wraps the function execution with our lock""" def wrapped_with_lock( *args, **named ): with lock: if timeout: lock.timeout_after( timeout ) return function( *args, **named ) wrapped_with_lock.__name__ = function.__name__ wrapped_with_lock.__doc__ = function.__doc__ wrapped_with_lock.__dict__ = function.__dict__ wrapped_with_lock.lock = lock return wrapped_with_lock return wrap_func

Project Versions