summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskip.montanaro <skip.montanaro@gmail.com>2010-02-19 12:48:51 +0000
committerskip.montanaro <skip.montanaro@gmail.com>2010-02-19 12:48:51 +0000
commita393a692c0289e047f01af25ec712ef01e3428a8 (patch)
tree50562f3223bdc81620f75e4d36f48d498ef92600
parent42da14f9b52e0b14ed69163dbf3352102c4ac8cc (diff)
downloadlockfile-a393a692c0289e047f01af25ec712ef01e3428a8.tar.gz
* Add pidlockfile (not quite working properly)
* Rearrange MANIFEST.in slightly to include test directory
-rw-r--r--MANIFEST3
-rw-r--r--MANIFEST.in5
-rw-r--r--lockfile/pidlockfile.py182
3 files changed, 188 insertions, 2 deletions
diff --git a/MANIFEST b/MANIFEST
index 312cc4d..d302eef 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -13,4 +13,7 @@ doc/lockfile.rst
lockfile/__init__.py
lockfile/linklockfile.py
lockfile/mkdirlockfile.py
+lockfile/pidlockfile.py
lockfile/sqlitelockfile.py
+test/compliancetest.py
+test/test_lockfile.py
diff --git a/MANIFEST.in b/MANIFEST.in
index ece489b..31903eb 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,4 @@
-include README RELEASE-NOTES LICENSE MANIFEST
-include lockfile setup.py ACKS 2.4.diff
+include README RELEASE-NOTES LICENSE MANIFEST setup.py ACKS 2.4.diff
recursive-include doc *.rst conf.py Makefile
+recursive-include test *.py
+recursive-include lockfile *.py
diff --git a/lockfile/pidlockfile.py b/lockfile/pidlockfile.py
new file mode 100644
index 0000000..9e4b3fd
--- /dev/null
+++ b/lockfile/pidlockfile.py
@@ -0,0 +1,182 @@
+# -*- 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.
+ """
+
+import os
+import sys
+import errno
+import time
+
+from lockfile import (
+ LockBase,
+ AlreadyLocked, LockFailed,
+ NotLocked, NotMyLock,
+ )
+
+
+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', threaded=False)
+ """
+
+ 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.
+ """
+
+ 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
+ else:
+ raise AlreadyLocked
+ time.sleep(timeout is not None and timeout/10 or 0.1)
+ else:
+ raise LockFailed
+ 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
+ if not self.i_am_locking():
+ raise NotMyLock
+ 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 = 0x644
+ 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