123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- # -*- test-case-name: twisted.application.runner.test.test_pidfile -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- PID file.
- """
- from __future__ import annotations
- import errno
- from os import getpid, kill, name as SYSTEM_NAME
- from types import TracebackType
- from typing import Any, Optional, Type
- from zope.interface import Interface, implementer
- from twisted.logger import Logger
- from twisted.python.filepath import FilePath
- class IPIDFile(Interface):
- """
- Manages a file that remembers a process ID.
- """
- def read() -> int:
- """
- Read the process ID stored in this PID file.
- @return: The contained process ID.
- @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() -> None:
- """
- Store the PID of the current process in this PID file.
- @raise EnvironmentError: If this PID file cannot be written.
- """
- def remove() -> None:
- """
- Remove this PID file.
- @raise EnvironmentError: If this PID file cannot be removed.
- """
- def isRunning() -> bool:
- """
- 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.
- @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__() -> "IPIDFile":
- """
- 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: Optional[Type[BaseException]],
- excValue: Optional[BaseException],
- traceback: Optional[TracebackType],
- ) -> Optional[bool]:
- """
- Exit a context using this PIDFile.
- Removes the PID file.
- """
- @implementer(IPIDFile)
- class PIDFile:
- """
- Concrete implementation of L{IPIDFile}.
- 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: int) -> bytes:
- """
- Format a PID file's content.
- @param pid: A process ID.
- @return: Formatted PID file contents.
- """
- return f"{int(pid)}\n".encode()
- def __init__(self, filePath: FilePath[Any]) -> None:
- """
- @param filePath: The path to the PID file on disk.
- """
- self.filePath = filePath
- def read(self) -> int:
- 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(
- f"non-integer PID value in PID file: {pidString!r}"
- )
- def _write(self, pid: int) -> None:
- """
- Store a PID in this PID file.
- @param pid: A PID to store.
- @raise EnvironmentError: If this PID file cannot be written.
- """
- self.filePath.setContent(self._format(pid=pid))
- def writeRunningPID(self) -> None:
- self._write(getpid())
- def remove(self) -> None:
- self.filePath.remove()
- def isRunning(self) -> bool:
- try:
- pid = self.read()
- except NoPIDFound:
- return False
- if SYSTEM_NAME == "posix":
- return self._pidIsRunningPOSIX(pid)
- else:
- raise NotImplementedError(f"isRunning is not implemented on {SYSTEM_NAME}")
- @staticmethod
- def _pidIsRunningPOSIX(pid: int) -> bool:
- """
- POSIX implementation for running process check.
- Determine whether there is a running process corresponding to the given
- PID.
- @param pid: The PID to check.
- @return: True if the given PID is currently running; false otherwise.
- @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) -> "PIDFile":
- 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: Optional[Type[BaseException]],
- excValue: Optional[BaseException],
- traceback: Optional[TracebackType],
- ) -> None:
- self.remove()
- return None
- @implementer(IPIDFile)
- class NonePIDFile:
- """
- 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) -> None:
- pass
- def read(self) -> int:
- raise NoPIDFound("PID file does not exist")
- def _write(self, pid: int) -> None:
- """
- Store a PID in this PID file.
- @param pid: A PID to store.
- @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) -> None:
- self._write(0)
- def remove(self) -> None:
- raise OSError(errno.ENOENT, "No such file or directory")
- def isRunning(self) -> bool:
- return False
- def __enter__(self) -> "NonePIDFile":
- return self
- def __exit__(
- self,
- excType: Optional[Type[BaseException]],
- excValue: Optional[BaseException],
- traceback: Optional[TracebackType],
- ) -> None:
- return None
- nonePIDFile: IPIDFile = 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.
- """
|