Created
July 29, 2013 08:31
-
-
Save chengui/6102942 to your computer and use it in GitHub Desktop.
the pid lockfile from lockfile package
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" Lockfile behaviour implemented via Unix PID files. | |
""" | |
from __future__ import absolute_import | |
import os | |
import sys | |
import errno | |
import time | |
class LockTimeout(Exception): | |
pass | |
class LockFailed(Exception): | |
pass | |
class AlreadyLocked(Exception): | |
pass | |
class NotLocked(Exception): | |
pass | |
class NotMyLock(Exception): | |
pass | |
class PIDLockFile(object): | |
""" 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. | |
self.path = path | |
self.pid = os.getpid() | |
self.timeout = 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 or 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, exc: | |
if exc.errno == errno.EEXIST: | |
# The lock creation failed. Maybe sleep a bit. | |
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 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 = 0644 | |
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() | |
line = "%(pid)d\n" % vars() | |
pidfile.write(line) | |
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, exc: | |
if exc.errno == errno.ENOENT: | |
pass | |
else: | |
raise |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment