[Subversion] / PEAK / src / peak / running / lockfiles.py  

View of /PEAK/src/peak/running/lockfiles.py

Parent Directory | Revision Log
Revision: 1123 - (download) (as text)
Sun May 18 16:20:35 2003 UTC (21 years ago) by pje
File size: 10661 byte(s)
Standardized file-based URL syntaxes (e.g logfiles and lockfiles) to
follow RFC 1738/2396, and Python 'urllib'.  This shouldn't affect much
besides the canonical forms of the URLs.  Added 'pkgfile:some.pkg/filepath'
URL syntax for ease of referring to files near modules.  (A convenience
intended mainly for referencing ZConfig schemas.)
"""Lockfiles for inter-process communication

    These are used for synchronization between processes, unlike
    thread.LockType locks.  The common use is non-blocking lock attempts.
    For convenience and in order to reduce confusion with the (somewhat odd)
    thread lock interface, these locks have a different interface.

    attempt()   try to obtain the lock, return boolean success
    obtain()    wait to obtain the lock, returns None
    release()   release an obtained lock, returns None
    locked()    returns True if any thread IN THIS PROCESS
                has obtained the lock, else False

    In general, you should assume that only the 'LockFile' class will be
    available.  Only use a more-specific lockfile type if you have need
    of compatibility with non-PEAK software that uses that special type
    of lockfile.  (In practice, this means that the only time you'll ever
    use anything other than the generic 'LockFile' class is if you need
    to work with something on a Unix-like platform that uses the equivalent
    of 'FLockFile', as 'SHLockFile' is the default implementation of 'LockFile'
    on Unix-like platforms.)

    Currently, only Unix-ish and Windows platforms supported; if your platform
    isn't supported, not even the 'LockFile' class will be available from this
    module.  For Windows, the 'msvcrt' module must be available (it is in the
    standard Python 2.2.1 binary distribution for Windows).

    This module also exports a 'NullLockFile' class, for use when locking is
    not needed, but an object with a locking interface is nonetheless required.
    'NullLockFile' can also be used as a substitute for a thread lock, if you
    prefer this locking interface over the standard Python one.
"""

__all__ = ['LockFile', 'NullLockFile']

import os, errno, time
from peak.util.threads import allocate_lock
from peak.api import naming, protocols
from protocols import Interface


class ILock(Interface):

    def attempt():
        """try to obtain the lock, return boolean success"""

    def obtain():
        """wait to obtain the lock, returns None"""

    def release():
        """release an obtained lock, returns None"""

    def locked():
        """returns True if any thread IN THIS PROCESS
        has obtained the lock, else False"""



























class LockFileBase:
    """Common base for lockfiles"""

    protocols.advise(
        instancesProvide=[ILock]
    )

    def __init__(self, fn):
        self.fn = os.path.abspath(fn)
        self._lock = allocate_lock()
        self._locked = False

    def attempt(self):
        if self._lock.acquire(False):
            r = False
            try:
                r = self.do_acquire(False)
            finally:
                if not r and self._lock.locked():
                    self._lock.release()
            return r
        else:
            return False

    def obtain(self):
        self._lock.acquire()
        r = False
        try:
            r = self.do_acquire(True)
        finally:
            if not r and self._lock.locked():
                self._lock.release()
        if not r:
            raise RuntimeError, "lock obtain shouldn't fail!"

    def release(self):
        self.do_release()
        self._locked = False
        self._lock.release()


    def locked(self):
        return self._locked

### Posix-y lockfiles ###

try:
    import posix
    from posix import O_EXCL, O_CREAT, O_RDWR
except ImportError:
    posix=None

try:
    import fcntl
except ImportError:
    fcntl = None

try:
    import msvcrt
except ImportError:
    msvcrt = None


def pid_exists(pid):
    """Is there a process with PID pid?"""
    if pid < 0:
        return False

    exist = False
    try:
        os.kill(pid, 0)
        exist = 1
    except OSError, x:
        if x.errno != errno.ESRCH:
            raise

    return exist





def check_lock(fn):

    """Check the validity of an existing lock file

    Reads the PID out of the lock and check if that process exists"""

    try:
        f = open(fn, 'r')
        pid = int(f.read().strip())
        f.close()
        return pid_exists(pid)
    except:
        raise
        return 1 # be conservative



def make_tempfile(fn, pid):

    tfn = os.path.join(os.path.dirname(fn), 'shlock%d' % pid)

    errcount = 1000
    while 1:
        try:
            fd = posix.open(tfn, O_EXCL | O_CREAT | O_RDWR, 0600)
            posix.write(fd, '%d\n' % pid)
            posix.close(fd)

            return tfn
        except OSError, x:
            if (errcount > 0) and (x.errno == errno.EEXIST):
                os.unlink(tfn)
                errcount = errcount - 1
            else:
                raise






class SHLockFile(LockFileBase):
    """HoneyDanBer/NNTP/shlock(1)-style locking

    Two bigs wins to this algorithm:

      o Locks do not survive crashes of either the system or the
        application by any appreciable period of time.

      o No clean up to do if the system or application crashes.

    Loses:

      o In the off chance that another process comes along with
        the same pid, we can get a false positive for lock validity.

      o Not compatible with NFS or any shared filesystem
        (due to disjoint PID spaces)

      o Waiting for lock must be implemented by polling"""

    def do_acquire(self, waitflag):
        if waitflag:
            sleep = 1
            locked = self.do_acquire(False)

            while not locked:
                time.sleep(sleep)
                sleep = min(sleep + 1, 15)
                locked = self.do_acquire(False)

            return locked

        else:
            tfn = make_tempfile(self.fn, os.getpid())
            while 1:
                try:
                    os.link(tfn,self.fn)
                    os.unlink(tfn)
                    self._locked = True
                    return True

                except OSError, x:
                    if x.errno == errno.EEXIST:
                        if check_lock(self.fn):
                            os.unlink(tfn)
                            self._locked = False
                            return False
                        else:
                            # nuke invalid lock file, and try to lock again
                            os.unlink(self.fn)
                    else:
                        os.unlink(tfn)
                        raise


    def do_release(self):
        os.unlink(self.fn)

























class FLockFile(LockFileBase):
    """
    flock(3)-based locks.

    Wins:

      o Locks do not survive crashes of either the system or the
        application.

      o Waiting for a lock is handled by the kernel and doesn't require
        polling

      o Potentially compatible with NFS or other shared filesystem
        _if_ you trust their lockd (or equivalent) implemenation.
        Note that this is a *big* if!

      o No false positives on stale locks

    Loses:

      o Leaves lockfiles around, since unlink would cause a race.
    """

    def do_acquire(self, waitflag=False):
        locked = False

        if waitflag:
            blockflag = 0
        else:
            blockflag = fcntl.LOCK_NB

        self.fd = posix.open(self.fn, O_CREAT | O_RDWR, 0600)
        try:
            fcntl.flock(self.fd, fcntl.LOCK_EX|blockflag)
            # locked it
            try:
                posix.ftruncate(self.fd, 0)
                posix.write(self.fd, `os.getpid()` + '\n')
                locked = True
            except:
                self.do_release()
                raise
        except IOError, x:
            if x.errno == errno.EWOULDBLOCK:
                # failed to lock
                posix.close(self.fd)
                del self.fd
            else:
                raise

        self._locked = locked

        return locked

    def do_release(self):
        posix.ftruncate(self.fd, 0)
        fcntl.flock(self.fd, fcntl.LOCK_UN)
        posix.close(self.fd)
        del self.fd























class WinFLockFile(LockFileBase):

    """Like FLockFile, but for Windows"""

    def do_acquire(self, waitflag=False):

        if waitflag:
            sleep = 1
            locked = self.do_acquire(False)

            while not locked:
                time.sleep(sleep)
                sleep = min(sleep + 1, 15)
                locked = self.do_acquire(False)
            return locked

        locked = False

        self.f = open(self.fn, 'a')
        try:
            msvcrt.locking(self.f.fileno(), msvcrt.LK_NBLCK, 1)
            try:
                self.f.write(`os.getpid()` + '\n')  # informational only
                self.f.seek(0)  # lock is at offset 0, so go back there
                locked = True
            except:
                self.do_release()
                raise

        except IOError, x:
            if x.errno == errno.EACCES:
                self.f.close()
                del self.f
            else:
                raise

        self._locked = locked
        return locked



    def do_release(self):
        msvcrt.locking(self.f.fileno(), msvcrt.LK_UNLCK, 1)
        self.f.close()
        del self.f



class NullLockFile(LockFileBase):

    """Pseudo-LockFile (locks only for threads in this process)"""

    def __init__(self):
        self._lock = allocate_lock()
        self._locked = False

    def do_acquire(self, waitflag=False):
        return True

    def do_release(self):
        pass





















# Default is shlock(1)-style if available

if posix:
    __all__.extend(['SHLockFile','FLockFile'])
    LockFile = SHLockFile
    del WinFLockFile

else:

    # don't need them and they won't work...
    del make_tempfile, check_lock, pid_exists

    if msvcrt:
        __all__.append('WinFLockFile')
        LockFile = WinFLockFile

    else:
        # Waaaaaaa!, as Jim F. would say...
        __all__.remove('LockFile')






















from peak.naming.factories.openable import FileURL

class lockfileURL(FileURL):

    supportedSchemes = (
        'lockfile', 'shlockfile', 'flockfile', 'winflockfile',
        'nulllockfile'
    )

    def retrieve(self, refInfo, name, context, attrs=None):

        filename = self.getFilename()

        if self.scheme == 'lockfile':
            return LockFile(filename)

        elif self.scheme == 'nulllockfile':
            return NullLockFile()

        elif self.scheme == 'shlockfile':
            return SHLockFile(filename)

        elif self.scheme == 'flockfile':
            return FLockFile(filename)

        elif self.scheme == 'winflockfile':
            return WinFLockFile(filename)















cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help