Viewing file: _pidfile.py (6.83 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# -*- test-case-name: twisted.application.runner.test.test_pidfile -*- # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details.
""" PID file. """
import errno from os import getpid, kill, name as SYSTEM_NAME
from zope.interface import Interface, implementer
from twisted.logger import Logger
class IPIDFile(Interface): """ Manages a file that remembers a process ID. """
def read(): """ Read the process ID stored in this PID file.
@return: The contained process ID. @rtype: L{int}
@raise NoPIDFound: If this PID file does not exist. @raise EnvironmentError: If this PID file cannot be read. @raise ValueError: If this PID file's content is invalid. """
def writeRunningPID(): """ Store the PID of the current process in this PID file.
@raise EnvironmentError: If this PID file cannot be written. """
def remove(): """ Remove this PID file.
@raise EnvironmentError: If this PID file cannot be removed. """
def isRunning(): """ Determine whether there is a running process corresponding to the PID in this PID file.
@return: True if this PID file contains a PID and a process with that PID is currently running; false otherwise. @rtype: L{bool}
@raise EnvironmentError: If this PID file cannot be read. @raise InvalidPIDFileError: If this PID file's content is invalid. @raise StalePIDFileError: If this PID file's content refers to a PID for which there is no corresponding running process. """
def __enter__(): """ Enter a context using this PIDFile.
Writes the PID file with the PID of the running process.
@raise AlreadyRunningError: A process corresponding to the PID in this PID file is already running. """
def __exit__(excType, excValue, traceback): """ Exit a context using this PIDFile.
Removes the PID file. """
@implementer(IPIDFile) class PIDFile(object): """ Concrete implementation of L{IPIDFile} based on C{IFilePath}.
This implementation is presently not supported on non-POSIX platforms. Specifically, calling L{PIDFile.isRunning} will raise L{NotImplementedError}. """
_log = Logger()
@staticmethod def _format(pid): """ Format a PID file's content.
@param pid: A process ID. @type pid: int
@return: Formatted PID file contents. @rtype: L{bytes} """ return u"{}\n".format(int(pid)).encode("utf-8")
def __init__(self, filePath): """ @param filePath: The path to the PID file on disk. @type filePath: L{IFilePath} """ self.filePath = filePath
def read(self): pidString = b"" try: with self.filePath.open() as fh: for pidString in fh: break except OSError as e: if e.errno == errno.ENOENT: # No such file raise NoPIDFound("PID file does not exist") raise
try: return int(pidString) except ValueError: raise InvalidPIDFileError( "non-integer PID value in PID file: {!r}".format(pidString) )
def _write(self, pid): """ Store a PID in this PID file.
@param pid: A PID to store. @type pid: L{int}
@raise EnvironmentError: If this PID file cannot be written. """ self.filePath.setContent(self._format(pid=pid))
def writeRunningPID(self): self._write(getpid())
def remove(self): self.filePath.remove()
def isRunning(self): try: pid = self.read() except NoPIDFound: return False
if SYSTEM_NAME == "posix": return self._pidIsRunningPOSIX(pid) else: raise NotImplementedError( "isRunning is not implemented on {}".format(SYSTEM_NAME) )
@staticmethod def _pidIsRunningPOSIX(pid): """ POSIX implementation for running process check.
Determine whether there is a running process corresponding to the given PID.
@return: True if the given PID is currently running; false otherwise. @rtype: L{bool}
@raise EnvironmentError: If this PID file cannot be read. @raise InvalidPIDFileError: If this PID file's content is invalid. @raise StalePIDFileError: If this PID file's content refers to a PID for which there is no corresponding running process. """ try: kill(pid, 0) except OSError as e: if e.errno == errno.ESRCH: # No such process raise StalePIDFileError( "PID file refers to non-existing process" ) elif e.errno == errno.EPERM: # Not permitted to kill return True else: raise else: return True
def __enter__(self): try: if self.isRunning(): raise AlreadyRunningError() except StalePIDFileError: self._log.info("Replacing stale PID file: {log_source}") self.writeRunningPID() return self
def __exit__(self, excType, excValue, traceback): self.remove()
@implementer(IPIDFile) class NonePIDFile(object): """ PID file implementation that does nothing.
This is meant to be used as a "active None" object in place of a PID file when no PID file is desired. """
def __init__(self): pass
def read(self): raise NoPIDFound("PID file does not exist")
def _write(self, pid): """ Store a PID in this PID file.
@param pid: A PID to store. @type pid: L{int}
@raise EnvironmentError: If this PID file cannot be written.
@note: This implementation always raises an L{EnvironmentError}. """ raise OSError(errno.EPERM, "Operation not permitted")
def writeRunningPID(self): self._write(0)
def remove(self): raise OSError(errno.ENOENT, "No such file or directory")
def isRunning(self): return False
def __enter__(self): return self
def __exit__(self, excType, excValue, traceback): pass
nonePIDFile = NonePIDFile()
class AlreadyRunningError(Exception): """ Process is already running. """
class InvalidPIDFileError(Exception): """ PID file contents are invalid. """
class StalePIDFileError(Exception): """ PID file contents are valid, but there is no process with the referenced PID. """
class NoPIDFound(Exception): """ No PID found in PID file. """
|