| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- # -*- coding: utf-8 -*-
- # daemon/pidlockfile.py
- # Part of python-daemon, an implementation of PEP 3143.
- #
- # 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 errno
- from lockfile import (
- LinkFileLock,
- AlreadyLocked, LockFailed,
- NotLocked, NotMyLock,
- )
- class PIDFileError(Exception):
- """ Abstract base class for errors specific to PID files. """
- class PIDFileParseError(ValueError, PIDFileError):
- """ Raised when parsing contents of PID file fails. """
- class PIDLockFile(LinkFileLock, object):
- """ Lockfile implemented as a Unix PID file.
- The PID file is named by the attribute `path`. When locked,
- the file will be created with a single line of text,
- containing the process ID (PID) of the process that acquired
- the lock.
- The lock is acquired and maintained as per `LinkFileLock`.
- """
- def read_pid(self):
- """ Get the PID from the lock file.
- """
- result = read_pid_from_pidfile(self.path)
- return result
- def acquire(self, *args, **kwargs):
- """ Acquire the lock.
- Locks the PID file then creates the PID file for this
- lock. The `timeout` parameter is used as for the
- `LinkFileLock` class.
- """
- super(PIDLockFile, self).acquire(*args, **kwargs)
- try:
- write_pid_to_pidfile(self.path)
- except OSError, exc:
- error = LockFailed("%(exc)s" % vars())
- raise error
- def release(self):
- """ Release the lock.
- Removes the PID file then releases the lock, or raises an
- error if the current process does not hold the lock.
- """
- if self.i_am_locking():
- remove_existing_pidfile(self.path)
- super(PIDLockFile, self).release()
- def break_lock(self):
- """ Break an existing lock.
- If the lock is held, breaks the lock and removes the PID
- file.
- """
- super(PIDLockFile, self).break_lock()
- remove_existing_pidfile(self.path)
- class TimeoutPIDLockFile(PIDLockFile):
- """ Lockfile with default timeout, implemented as a Unix PID file.
- This uses the ``PIDLockFile`` implementation, with the
- following changes:
- * The `acquire_timeout` parameter to the initialiser will be
- used as the default `timeout` parameter for the `acquire`
- method.
- """
- def __init__(self, path, acquire_timeout=None, *args, **kwargs):
- """ Set up the parameters of a DaemonRunnerLock. """
- self.acquire_timeout = acquire_timeout
- super(TimeoutPIDLockFile, self).__init__(path, *args, **kwargs)
- def acquire(self, timeout=None, *args, **kwargs):
- """ Acquire the lock. """
- if timeout is None:
- timeout = self.acquire_timeout
- super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
- 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 does not exist, return ``None``. If
- the content is not a valid PID, raise ``PIDFileParseError``.
- """
- pid = None
- pidfile = None
- try:
- pidfile = open(pidfile_path, 'r')
- except IOError, exc:
- if exc.errno == errno.ENOENT:
- pass
- else:
- raise
- if pidfile:
- # 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:
- raise PIDFileParseError(
- "PID file %(pidfile_path)r contents invalid" % vars())
- 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 = (
- ((os.R_OK | os.W_OK) << 6) |
- ((os.R_OK) << 3) |
- ((os.R_OK)))
- 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.
- Remove the named PID file. Ignore the condition if the file
- does not exist, since that only means we are already in the
- desired state.
- """
- try:
- os.remove(pidfile_path)
- except OSError, exc:
- if exc.errno == errno.ENOENT:
- pass
- else:
- raise
|