303 lines
6.8 KiB
Python
303 lines
6.8 KiB
Python
# -*- 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.
|
|
"""
|