mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
Remove Lockfile.
This commit is contained in:
parent
46ec707544
commit
51d7a97385
1
news/lockfile.vendor
Normal file
1
news/lockfile.vendor
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Remove Lockfile as a vendored dependency.
|
|
@ -64,7 +64,6 @@ if DEBUNDLED:
|
||||||
vendored("distlib")
|
vendored("distlib")
|
||||||
vendored("distro")
|
vendored("distro")
|
||||||
vendored("html5lib")
|
vendored("html5lib")
|
||||||
vendored("lockfile")
|
|
||||||
vendored("six")
|
vendored("six")
|
||||||
vendored("six.moves")
|
vendored("six.moves")
|
||||||
vendored("six.moves.urllib")
|
vendored("six.moves.urllib")
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
from lockfile import *
|
|
|
@ -1,21 +0,0 @@
|
||||||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
|
||||||
|
|
||||||
Copyright (c) 2007 Skip Montanaro.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to
|
|
||||||
deal in the Software without restriction, including without limitation the
|
|
||||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
IN THE SOFTWARE.
|
|
|
@ -1,347 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
lockfile.py - Platform-independent advisory file locks.
|
|
||||||
|
|
||||||
Requires Python 2.5 unless you apply 2.4.diff
|
|
||||||
Locking is done on a per-thread basis instead of a per-process basis.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
>>> lock = LockFile('somefile')
|
|
||||||
>>> try:
|
|
||||||
... lock.acquire()
|
|
||||||
... except AlreadyLocked:
|
|
||||||
... print 'somefile', 'is locked already.'
|
|
||||||
... except LockFailed:
|
|
||||||
... print 'somefile', 'can\\'t be locked.'
|
|
||||||
... else:
|
|
||||||
... print 'got lock'
|
|
||||||
got lock
|
|
||||||
>>> print lock.is_locked()
|
|
||||||
True
|
|
||||||
>>> lock.release()
|
|
||||||
|
|
||||||
>>> lock = LockFile('somefile')
|
|
||||||
>>> print lock.is_locked()
|
|
||||||
False
|
|
||||||
>>> with lock:
|
|
||||||
... print lock.is_locked()
|
|
||||||
True
|
|
||||||
>>> print lock.is_locked()
|
|
||||||
False
|
|
||||||
|
|
||||||
>>> lock = LockFile('somefile')
|
|
||||||
>>> # It is okay to lock twice from the same thread...
|
|
||||||
>>> with lock:
|
|
||||||
... lock.acquire()
|
|
||||||
...
|
|
||||||
>>> # Though no counter is kept, so you can't unlock multiple times...
|
|
||||||
>>> print lock.is_locked()
|
|
||||||
False
|
|
||||||
|
|
||||||
Exceptions:
|
|
||||||
|
|
||||||
Error - base class for other exceptions
|
|
||||||
LockError - base class for all locking exceptions
|
|
||||||
AlreadyLocked - Another thread or process already holds the lock
|
|
||||||
LockFailed - Lock failed for some other reason
|
|
||||||
UnlockError - base class for all unlocking exceptions
|
|
||||||
AlreadyUnlocked - File was not locked.
|
|
||||||
NotMyLock - File was locked but not by the current thread/process
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import threading
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
# Work with PEP8 and non-PEP8 versions of threading module.
|
|
||||||
if not hasattr(threading, "current_thread"):
|
|
||||||
threading.current_thread = threading.currentThread
|
|
||||||
if not hasattr(threading.Thread, "get_name"):
|
|
||||||
threading.Thread.get_name = threading.Thread.getName
|
|
||||||
|
|
||||||
__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
|
|
||||||
'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
|
|
||||||
'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock',
|
|
||||||
'LockBase', 'locked']
|
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
"""
|
|
||||||
Base class for other exceptions.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise Error
|
|
||||||
... except Exception:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LockError(Error):
|
|
||||||
"""
|
|
||||||
Base class for error arising from attempts to acquire the lock.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise LockError
|
|
||||||
... except Error:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LockTimeout(LockError):
|
|
||||||
"""Raised when lock creation fails within a user-defined period of time.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise LockTimeout
|
|
||||||
... except LockError:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AlreadyLocked(LockError):
|
|
||||||
"""Some other thread/process is locking the file.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise AlreadyLocked
|
|
||||||
... except LockError:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LockFailed(LockError):
|
|
||||||
"""Lock file creation failed for some other reason.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise LockFailed
|
|
||||||
... except LockError:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UnlockError(Error):
|
|
||||||
"""
|
|
||||||
Base class for errors arising from attempts to release the lock.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise UnlockError
|
|
||||||
... except Error:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NotLocked(UnlockError):
|
|
||||||
"""Raised when an attempt is made to unlock an unlocked file.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise NotLocked
|
|
||||||
... except UnlockError:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NotMyLock(UnlockError):
|
|
||||||
"""Raised when an attempt is made to unlock a file someone else locked.
|
|
||||||
|
|
||||||
>>> try:
|
|
||||||
... raise NotMyLock
|
|
||||||
... except UnlockError:
|
|
||||||
... pass
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class _SharedBase(object):
|
|
||||||
def __init__(self, path):
|
|
||||||
self.path = path
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
"""
|
|
||||||
Acquire the lock.
|
|
||||||
|
|
||||||
* If timeout is omitted (or None), wait forever trying to lock the
|
|
||||||
file.
|
|
||||||
|
|
||||||
* If timeout > 0, try to acquire the lock for that many seconds. If
|
|
||||||
the lock period expires and the file is still locked, raise
|
|
||||||
LockTimeout.
|
|
||||||
|
|
||||||
* If timeout <= 0, raise AlreadyLocked immediately if the file is
|
|
||||||
already locked.
|
|
||||||
"""
|
|
||||||
raise NotImplemented("implement in subclass")
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
"""
|
|
||||||
Release the lock.
|
|
||||||
|
|
||||||
If the file is not locked, raise NotLocked.
|
|
||||||
"""
|
|
||||||
raise NotImplemented("implement in subclass")
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
"""
|
|
||||||
Context manager support.
|
|
||||||
"""
|
|
||||||
self.acquire()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *_exc):
|
|
||||||
"""
|
|
||||||
Context manager support.
|
|
||||||
"""
|
|
||||||
self.release()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s: %r>" % (self.__class__.__name__, self.path)
|
|
||||||
|
|
||||||
|
|
||||||
class LockBase(_SharedBase):
|
|
||||||
"""Base class for platform-specific lock classes."""
|
|
||||||
def __init__(self, path, threaded=True, timeout=None):
|
|
||||||
"""
|
|
||||||
>>> lock = LockBase('somefile')
|
|
||||||
>>> lock = LockBase('somefile', threaded=False)
|
|
||||||
"""
|
|
||||||
super(LockBase, self).__init__(path)
|
|
||||||
self.lock_file = os.path.abspath(path) + ".lock"
|
|
||||||
self.hostname = socket.gethostname()
|
|
||||||
self.pid = os.getpid()
|
|
||||||
if threaded:
|
|
||||||
t = threading.current_thread()
|
|
||||||
# Thread objects in Python 2.4 and earlier do not have ident
|
|
||||||
# attrs. Worm around that.
|
|
||||||
ident = getattr(t, "ident", hash(t))
|
|
||||||
self.tname = "-%x" % (ident & 0xffffffff)
|
|
||||||
else:
|
|
||||||
self.tname = ""
|
|
||||||
dirname = os.path.dirname(self.lock_file)
|
|
||||||
|
|
||||||
# unique name is mostly about the current process, but must
|
|
||||||
# also contain the path -- otherwise, two adjacent locked
|
|
||||||
# files conflict (one file gets locked, creating lock-file and
|
|
||||||
# unique file, the other one gets locked, creating lock-file
|
|
||||||
# and overwriting the already existing lock-file, then one
|
|
||||||
# gets unlocked, deleting both lock-file and unique file,
|
|
||||||
# finally the last lock errors out upon releasing.
|
|
||||||
self.unique_name = os.path.join(dirname,
|
|
||||||
"%s%s.%s%s" % (self.hostname,
|
|
||||||
self.tname,
|
|
||||||
self.pid,
|
|
||||||
hash(self.path)))
|
|
||||||
self.timeout = timeout
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
"""
|
|
||||||
Tell whether or not the file is locked.
|
|
||||||
"""
|
|
||||||
raise NotImplemented("implement in subclass")
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
"""
|
|
||||||
Return True if this object is locking the file.
|
|
||||||
"""
|
|
||||||
raise NotImplemented("implement in subclass")
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
"""
|
|
||||||
Remove a lock. Useful if a locking thread failed to unlock.
|
|
||||||
"""
|
|
||||||
raise NotImplemented("implement in subclass")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name,
|
|
||||||
self.path)
|
|
||||||
|
|
||||||
|
|
||||||
def _fl_helper(cls, mod, *args, **kwds):
|
|
||||||
warnings.warn("Import from %s module instead of lockfile package" % mod,
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
# This is a bit funky, but it's only for awhile. The way the unit tests
|
|
||||||
# are constructed this function winds up as an unbound method, so it
|
|
||||||
# actually takes three args, not two. We want to toss out self.
|
|
||||||
if not isinstance(args[0], str):
|
|
||||||
# We are testing, avoid the first arg
|
|
||||||
args = args[1:]
|
|
||||||
if len(args) == 1 and not kwds:
|
|
||||||
kwds["threaded"] = True
|
|
||||||
return cls(*args, **kwds)
|
|
||||||
|
|
||||||
|
|
||||||
def LinkFileLock(*args, **kwds):
|
|
||||||
"""Factory function provided for backwards compatibility.
|
|
||||||
|
|
||||||
Do not use in new code. Instead, import LinkLockFile from the
|
|
||||||
lockfile.linklockfile module.
|
|
||||||
"""
|
|
||||||
from . import linklockfile
|
|
||||||
return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile",
|
|
||||||
*args, **kwds)
|
|
||||||
|
|
||||||
|
|
||||||
def MkdirFileLock(*args, **kwds):
|
|
||||||
"""Factory function provided for backwards compatibility.
|
|
||||||
|
|
||||||
Do not use in new code. Instead, import MkdirLockFile from the
|
|
||||||
lockfile.mkdirlockfile module.
|
|
||||||
"""
|
|
||||||
from . import mkdirlockfile
|
|
||||||
return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile",
|
|
||||||
*args, **kwds)
|
|
||||||
|
|
||||||
|
|
||||||
def SQLiteFileLock(*args, **kwds):
|
|
||||||
"""Factory function provided for backwards compatibility.
|
|
||||||
|
|
||||||
Do not use in new code. Instead, import SQLiteLockFile from the
|
|
||||||
lockfile.mkdirlockfile module.
|
|
||||||
"""
|
|
||||||
from . import sqlitelockfile
|
|
||||||
return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile",
|
|
||||||
*args, **kwds)
|
|
||||||
|
|
||||||
|
|
||||||
def locked(path, timeout=None):
|
|
||||||
"""Decorator which enables locks for decorated function.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- path: path for lockfile.
|
|
||||||
- timeout (optional): Timeout for acquiring lock.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
@locked('/var/run/myname', timeout=0)
|
|
||||||
def myname(...):
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
def decor(func):
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
lock = FileLock(path, timeout=timeout)
|
|
||||||
lock.acquire()
|
|
||||||
try:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
lock.release()
|
|
||||||
return wrapper
|
|
||||||
return decor
|
|
||||||
|
|
||||||
|
|
||||||
if hasattr(os, "link"):
|
|
||||||
from . import linklockfile as _llf
|
|
||||||
LockFile = _llf.LinkLockFile
|
|
||||||
else:
|
|
||||||
from . import mkdirlockfile as _mlf
|
|
||||||
LockFile = _mlf.MkdirLockFile
|
|
||||||
|
|
||||||
FileLock = LockFile
|
|
|
@ -1,73 +0,0 @@
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
|
|
||||||
from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
|
|
||||||
AlreadyLocked)
|
|
||||||
|
|
||||||
|
|
||||||
class LinkLockFile(LockBase):
|
|
||||||
"""Lock access to a file using atomic property of link(2).
|
|
||||||
|
|
||||||
>>> lock = LinkLockFile('somefile')
|
|
||||||
>>> lock = LinkLockFile('somefile', threaded=False)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
try:
|
|
||||||
open(self.unique_name, "wb").close()
|
|
||||||
except IOError:
|
|
||||||
raise LockFailed("failed to create %s" % self.unique_name)
|
|
||||||
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
end_time = time.time()
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
end_time += timeout
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# Try and create a hard link to it.
|
|
||||||
try:
|
|
||||||
os.link(self.unique_name, self.lock_file)
|
|
||||||
except OSError:
|
|
||||||
# Link creation failed. Maybe we've double-locked?
|
|
||||||
nlinks = os.stat(self.unique_name).st_nlink
|
|
||||||
if nlinks == 2:
|
|
||||||
# The original link plus the one I created == 2. We're
|
|
||||||
# good to go.
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# Otherwise the lock creation failed.
|
|
||||||
if timeout is not None and time.time() > end_time:
|
|
||||||
os.unlink(self.unique_name)
|
|
||||||
if timeout > 0:
|
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
|
||||||
" lock for %s" %
|
|
||||||
self.path)
|
|
||||||
else:
|
|
||||||
raise AlreadyLocked("%s is already locked" %
|
|
||||||
self.path)
|
|
||||||
time.sleep(timeout is not None and timeout / 10 or 0.1)
|
|
||||||
else:
|
|
||||||
# Link creation succeeded. We're good to go.
|
|
||||||
return
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if not self.is_locked():
|
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
|
||||||
elif not os.path.exists(self.unique_name):
|
|
||||||
raise NotMyLock("%s is locked, but not by me" % self.path)
|
|
||||||
os.unlink(self.unique_name)
|
|
||||||
os.unlink(self.lock_file)
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
return os.path.exists(self.lock_file)
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
return (self.is_locked() and
|
|
||||||
os.path.exists(self.unique_name) and
|
|
||||||
os.stat(self.unique_name).st_nlink == 2)
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
if os.path.exists(self.lock_file):
|
|
||||||
os.unlink(self.lock_file)
|
|
|
@ -1,84 +0,0 @@
|
||||||
from __future__ import absolute_import, division
|
|
||||||
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import errno
|
|
||||||
|
|
||||||
from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
|
|
||||||
AlreadyLocked)
|
|
||||||
|
|
||||||
|
|
||||||
class MkdirLockFile(LockBase):
|
|
||||||
"""Lock file by creating a directory."""
|
|
||||||
def __init__(self, path, threaded=True, timeout=None):
|
|
||||||
"""
|
|
||||||
>>> lock = MkdirLockFile('somefile')
|
|
||||||
>>> lock = MkdirLockFile('somefile', threaded=False)
|
|
||||||
"""
|
|
||||||
LockBase.__init__(self, path, threaded, timeout)
|
|
||||||
# Lock file itself is a directory. Place the unique file name into
|
|
||||||
# it.
|
|
||||||
self.unique_name = os.path.join(self.lock_file,
|
|
||||||
"%s.%s%s" % (self.hostname,
|
|
||||||
self.tname,
|
|
||||||
self.pid))
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
end_time = time.time()
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
end_time += timeout
|
|
||||||
|
|
||||||
if timeout is None:
|
|
||||||
wait = 0.1
|
|
||||||
else:
|
|
||||||
wait = max(0, timeout / 10)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
os.mkdir(self.lock_file)
|
|
||||||
except OSError:
|
|
||||||
err = sys.exc_info()[1]
|
|
||||||
if err.errno == errno.EEXIST:
|
|
||||||
# Already locked.
|
|
||||||
if os.path.exists(self.unique_name):
|
|
||||||
# Already locked by me.
|
|
||||||
return
|
|
||||||
if timeout is not None and time.time() > end_time:
|
|
||||||
if timeout > 0:
|
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
|
||||||
" lock for %s" %
|
|
||||||
self.path)
|
|
||||||
else:
|
|
||||||
# Someone else has the lock.
|
|
||||||
raise AlreadyLocked("%s is already locked" %
|
|
||||||
self.path)
|
|
||||||
time.sleep(wait)
|
|
||||||
else:
|
|
||||||
# Couldn't create the lock for some other reason
|
|
||||||
raise LockFailed("failed to create %s" % self.lock_file)
|
|
||||||
else:
|
|
||||||
open(self.unique_name, "wb").close()
|
|
||||||
return
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if not self.is_locked():
|
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
|
||||||
elif not os.path.exists(self.unique_name):
|
|
||||||
raise NotMyLock("%s is locked, but not by me" % self.path)
|
|
||||||
os.unlink(self.unique_name)
|
|
||||||
os.rmdir(self.lock_file)
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
return os.path.exists(self.lock_file)
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
return (self.is_locked() and
|
|
||||||
os.path.exists(self.unique_name))
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
if os.path.exists(self.lock_file):
|
|
||||||
for name in os.listdir(self.lock_file):
|
|
||||||
os.unlink(os.path.join(self.lock_file, name))
|
|
||||||
os.rmdir(self.lock_file)
|
|
|
@ -1,190 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# pidlockfile.py
|
|
||||||
#
|
|
||||||
# Copyright © 2008–2009 Ben Finney <ben+python@benfinney.id.au>
|
|
||||||
#
|
|
||||||
# This is free software: you may copy, modify, and/or distribute this work
|
|
||||||
# under the terms of the Python Software Foundation License, version 2 or
|
|
||||||
# later as published by the Python Software Foundation.
|
|
||||||
# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
|
|
||||||
|
|
||||||
""" Lockfile behaviour implemented via Unix PID files.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import errno
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock,
|
|
||||||
LockTimeout)
|
|
||||||
|
|
||||||
|
|
||||||
class PIDLockFile(LockBase):
|
|
||||||
""" Lockfile implemented as a Unix PID file.
|
|
||||||
|
|
||||||
The lock file is a normal file named by the attribute `path`.
|
|
||||||
A lock's PID file contains a single line of text, containing
|
|
||||||
the process ID (PID) of the process that acquired the lock.
|
|
||||||
|
|
||||||
>>> lock = PIDLockFile('somefile')
|
|
||||||
>>> lock = PIDLockFile('somefile')
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, path, threaded=False, timeout=None):
|
|
||||||
# pid lockfiles don't support threaded operation, so always force
|
|
||||||
# False as the threaded arg.
|
|
||||||
LockBase.__init__(self, path, False, timeout)
|
|
||||||
self.unique_name = self.path
|
|
||||||
|
|
||||||
def read_pid(self):
|
|
||||||
""" Get the PID from the lock file.
|
|
||||||
"""
|
|
||||||
return read_pid_from_pidfile(self.path)
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
""" Test if the lock is currently held.
|
|
||||||
|
|
||||||
The lock is held if the PID file for this lock exists.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return os.path.exists(self.path)
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
""" Test if the lock is held by the current process.
|
|
||||||
|
|
||||||
Returns ``True`` if the current process ID matches the
|
|
||||||
number stored in the PID file.
|
|
||||||
"""
|
|
||||||
return self.is_locked() and os.getpid() == self.read_pid()
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
""" Acquire the lock.
|
|
||||||
|
|
||||||
Creates the PID file for this lock, or raises an error if
|
|
||||||
the lock could not be acquired.
|
|
||||||
"""
|
|
||||||
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
end_time = time.time()
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
end_time += timeout
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
write_pid_to_pidfile(self.path)
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.errno == errno.EEXIST:
|
|
||||||
# The lock creation failed. Maybe sleep a bit.
|
|
||||||
if time.time() > end_time:
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
|
||||||
" lock for %s" %
|
|
||||||
self.path)
|
|
||||||
else:
|
|
||||||
raise AlreadyLocked("%s is already locked" %
|
|
||||||
self.path)
|
|
||||||
time.sleep(timeout is not None and timeout / 10 or 0.1)
|
|
||||||
else:
|
|
||||||
raise LockFailed("failed to create %s" % self.path)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
""" Release the lock.
|
|
||||||
|
|
||||||
Removes the PID file to release the lock, or raises an
|
|
||||||
error if the current process does not hold the lock.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not self.is_locked():
|
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
|
||||||
if not self.i_am_locking():
|
|
||||||
raise NotMyLock("%s is locked, but not by me" % self.path)
|
|
||||||
remove_existing_pidfile(self.path)
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
""" Break an existing lock.
|
|
||||||
|
|
||||||
Removes the PID file if it already exists, otherwise does
|
|
||||||
nothing.
|
|
||||||
|
|
||||||
"""
|
|
||||||
remove_existing_pidfile(self.path)
|
|
||||||
|
|
||||||
|
|
||||||
def read_pid_from_pidfile(pidfile_path):
|
|
||||||
""" Read the PID recorded in the named PID file.
|
|
||||||
|
|
||||||
Read and return the numeric PID recorded as text in the named
|
|
||||||
PID file. If the PID file cannot be read, or if the content is
|
|
||||||
not a valid PID, return ``None``.
|
|
||||||
|
|
||||||
"""
|
|
||||||
pid = None
|
|
||||||
try:
|
|
||||||
pidfile = open(pidfile_path, 'r')
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# According to the FHS 2.3 section on PID files in /var/run:
|
|
||||||
#
|
|
||||||
# The file must consist of the process identifier in
|
|
||||||
# ASCII-encoded decimal, followed by a newline character.
|
|
||||||
#
|
|
||||||
# Programs that read PID files should be somewhat flexible
|
|
||||||
# in what they accept; i.e., they should ignore extra
|
|
||||||
# whitespace, leading zeroes, absence of the trailing
|
|
||||||
# newline, or additional lines in the PID file.
|
|
||||||
|
|
||||||
line = pidfile.readline().strip()
|
|
||||||
try:
|
|
||||||
pid = int(line)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
pidfile.close()
|
|
||||||
|
|
||||||
return pid
|
|
||||||
|
|
||||||
|
|
||||||
def write_pid_to_pidfile(pidfile_path):
|
|
||||||
""" Write the PID in the named PID file.
|
|
||||||
|
|
||||||
Get the numeric process ID (“PID”) of the current process
|
|
||||||
and write it to the named file as a line of text.
|
|
||||||
|
|
||||||
"""
|
|
||||||
open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
|
||||||
open_mode = 0o644
|
|
||||||
pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
|
|
||||||
pidfile = os.fdopen(pidfile_fd, 'w')
|
|
||||||
|
|
||||||
# According to the FHS 2.3 section on PID files in /var/run:
|
|
||||||
#
|
|
||||||
# The file must consist of the process identifier in
|
|
||||||
# ASCII-encoded decimal, followed by a newline character. For
|
|
||||||
# example, if crond was process number 25, /var/run/crond.pid
|
|
||||||
# would contain three characters: two, five, and newline.
|
|
||||||
|
|
||||||
pid = os.getpid()
|
|
||||||
pidfile.write("%s\n" % pid)
|
|
||||||
pidfile.close()
|
|
||||||
|
|
||||||
|
|
||||||
def remove_existing_pidfile(pidfile_path):
|
|
||||||
""" Remove the named PID file if it exists.
|
|
||||||
|
|
||||||
Removing a PID file that doesn't already exist puts us in the
|
|
||||||
desired state, so we ignore the condition if the file does not
|
|
||||||
exist.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
os.remove(pidfile_path)
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.errno == errno.ENOENT:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise
|
|
|
@ -1,156 +0,0 @@
|
||||||
from __future__ import absolute_import, division
|
|
||||||
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
|
|
||||||
try:
|
|
||||||
unicode
|
|
||||||
except NameError:
|
|
||||||
unicode = str
|
|
||||||
|
|
||||||
from . import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
|
|
||||||
|
|
||||||
|
|
||||||
class SQLiteLockFile(LockBase):
|
|
||||||
"Demonstrate SQL-based locking."
|
|
||||||
|
|
||||||
testdb = None
|
|
||||||
|
|
||||||
def __init__(self, path, threaded=True, timeout=None):
|
|
||||||
"""
|
|
||||||
>>> lock = SQLiteLockFile('somefile')
|
|
||||||
>>> lock = SQLiteLockFile('somefile', threaded=False)
|
|
||||||
"""
|
|
||||||
LockBase.__init__(self, path, threaded, timeout)
|
|
||||||
self.lock_file = unicode(self.lock_file)
|
|
||||||
self.unique_name = unicode(self.unique_name)
|
|
||||||
|
|
||||||
if SQLiteLockFile.testdb is None:
|
|
||||||
import tempfile
|
|
||||||
_fd, testdb = tempfile.mkstemp()
|
|
||||||
os.close(_fd)
|
|
||||||
os.unlink(testdb)
|
|
||||||
del _fd, tempfile
|
|
||||||
SQLiteLockFile.testdb = testdb
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
self.connection = sqlite3.connect(SQLiteLockFile.testdb)
|
|
||||||
|
|
||||||
c = self.connection.cursor()
|
|
||||||
try:
|
|
||||||
c.execute("create table locks"
|
|
||||||
"("
|
|
||||||
" lock_file varchar(32),"
|
|
||||||
" unique_name varchar(32)"
|
|
||||||
")")
|
|
||||||
except sqlite3.OperationalError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.connection.commit()
|
|
||||||
import atexit
|
|
||||||
atexit.register(os.unlink, SQLiteLockFile.testdb)
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
end_time = time.time()
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
end_time += timeout
|
|
||||||
|
|
||||||
if timeout is None:
|
|
||||||
wait = 0.1
|
|
||||||
elif timeout <= 0:
|
|
||||||
wait = 0
|
|
||||||
else:
|
|
||||||
wait = timeout / 10
|
|
||||||
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if not self.is_locked():
|
|
||||||
# Not locked. Try to lock it.
|
|
||||||
cursor.execute("insert into locks"
|
|
||||||
" (lock_file, unique_name)"
|
|
||||||
" values"
|
|
||||||
" (?, ?)",
|
|
||||||
(self.lock_file, self.unique_name))
|
|
||||||
self.connection.commit()
|
|
||||||
|
|
||||||
# Check to see if we are the only lock holder.
|
|
||||||
cursor.execute("select * from locks"
|
|
||||||
" where unique_name = ?",
|
|
||||||
(self.unique_name,))
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
if len(rows) > 1:
|
|
||||||
# Nope. Someone else got there. Remove our lock.
|
|
||||||
cursor.execute("delete from locks"
|
|
||||||
" where unique_name = ?",
|
|
||||||
(self.unique_name,))
|
|
||||||
self.connection.commit()
|
|
||||||
else:
|
|
||||||
# Yup. We're done, so go home.
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# Check to see if we are the only lock holder.
|
|
||||||
cursor.execute("select * from locks"
|
|
||||||
" where unique_name = ?",
|
|
||||||
(self.unique_name,))
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
if len(rows) == 1:
|
|
||||||
# We're the locker, so go home.
|
|
||||||
return
|
|
||||||
|
|
||||||
# Maybe we should wait a bit longer.
|
|
||||||
if timeout is not None and time.time() > end_time:
|
|
||||||
if timeout > 0:
|
|
||||||
# No more waiting.
|
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
|
||||||
" lock for %s" %
|
|
||||||
self.path)
|
|
||||||
else:
|
|
||||||
# Someone else has the lock and we are impatient..
|
|
||||||
raise AlreadyLocked("%s is already locked" % self.path)
|
|
||||||
|
|
||||||
# Well, okay. We'll give it a bit longer.
|
|
||||||
time.sleep(wait)
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if not self.is_locked():
|
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
|
||||||
if not self.i_am_locking():
|
|
||||||
raise NotMyLock("%s is locked, but not by me (by %s)" %
|
|
||||||
(self.unique_name, self._who_is_locking()))
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute("delete from locks"
|
|
||||||
" where unique_name = ?",
|
|
||||||
(self.unique_name,))
|
|
||||||
self.connection.commit()
|
|
||||||
|
|
||||||
def _who_is_locking(self):
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute("select unique_name from locks"
|
|
||||||
" where lock_file = ?",
|
|
||||||
(self.lock_file,))
|
|
||||||
return cursor.fetchone()[0]
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute("select * from locks"
|
|
||||||
" where lock_file = ?",
|
|
||||||
(self.lock_file,))
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
return not not rows
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute("select * from locks"
|
|
||||||
" where lock_file = ?"
|
|
||||||
" and unique_name = ?",
|
|
||||||
(self.lock_file, self.unique_name))
|
|
||||||
return not not cursor.fetchall()
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
cursor = self.connection.cursor()
|
|
||||||
cursor.execute("delete from locks"
|
|
||||||
" where lock_file = ?",
|
|
||||||
(self.lock_file,))
|
|
||||||
self.connection.commit()
|
|
|
@ -1,70 +0,0 @@
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
from . import (LockBase, NotLocked, NotMyLock, LockTimeout,
|
|
||||||
AlreadyLocked)
|
|
||||||
|
|
||||||
|
|
||||||
class SymlinkLockFile(LockBase):
|
|
||||||
"""Lock access to a file using symlink(2)."""
|
|
||||||
|
|
||||||
def __init__(self, path, threaded=True, timeout=None):
|
|
||||||
# super(SymlinkLockFile).__init(...)
|
|
||||||
LockBase.__init__(self, path, threaded, timeout)
|
|
||||||
# split it back!
|
|
||||||
self.unique_name = os.path.split(self.unique_name)[1]
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
# Hopefully unnecessary for symlink.
|
|
||||||
# try:
|
|
||||||
# open(self.unique_name, "wb").close()
|
|
||||||
# except IOError:
|
|
||||||
# raise LockFailed("failed to create %s" % self.unique_name)
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
end_time = time.time()
|
|
||||||
if timeout is not None and timeout > 0:
|
|
||||||
end_time += timeout
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# Try and create a symbolic link to it.
|
|
||||||
try:
|
|
||||||
os.symlink(self.unique_name, self.lock_file)
|
|
||||||
except OSError:
|
|
||||||
# Link creation failed. Maybe we've double-locked?
|
|
||||||
if self.i_am_locking():
|
|
||||||
# Linked to out unique name. Proceed.
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# Otherwise the lock creation failed.
|
|
||||||
if timeout is not None and time.time() > end_time:
|
|
||||||
if timeout > 0:
|
|
||||||
raise LockTimeout("Timeout waiting to acquire"
|
|
||||||
" lock for %s" %
|
|
||||||
self.path)
|
|
||||||
else:
|
|
||||||
raise AlreadyLocked("%s is already locked" %
|
|
||||||
self.path)
|
|
||||||
time.sleep(timeout / 10 if timeout is not None else 0.1)
|
|
||||||
else:
|
|
||||||
# Link creation succeeded. We're good to go.
|
|
||||||
return
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if not self.is_locked():
|
|
||||||
raise NotLocked("%s is not locked" % self.path)
|
|
||||||
elif not self.i_am_locking():
|
|
||||||
raise NotMyLock("%s is locked, but not by me" % self.path)
|
|
||||||
os.unlink(self.lock_file)
|
|
||||||
|
|
||||||
def is_locked(self):
|
|
||||||
return os.path.islink(self.lock_file)
|
|
||||||
|
|
||||||
def i_am_locking(self):
|
|
||||||
return (os.path.islink(self.lock_file)
|
|
||||||
and os.readlink(self.lock_file) == self.unique_name)
|
|
||||||
|
|
||||||
def break_lock(self):
|
|
||||||
if os.path.islink(self.lock_file): # exists && link
|
|
||||||
os.unlink(self.lock_file)
|
|
|
@ -6,7 +6,6 @@ distlib==0.2.9.post0
|
||||||
distro==1.4.0
|
distro==1.4.0
|
||||||
html5lib==1.0.1
|
html5lib==1.0.1
|
||||||
ipaddress==1.0.22 # Only needed on 2.6 and 2.7
|
ipaddress==1.0.22 # Only needed on 2.6 and 2.7
|
||||||
lockfile==0.12.2
|
|
||||||
msgpack==0.6.1
|
msgpack==0.6.1
|
||||||
packaging==19.0
|
packaging==19.0
|
||||||
pep517==0.5.0
|
pep517==0.5.0
|
||||||
|
|
Loading…
Reference in a new issue