123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- # -*- 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.
- """
|