_pidfile.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. # -*- test-case-name: twisted.application.runner.test.test_pidfile -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. PID file.
  6. """
  7. import errno
  8. from os import getpid, kill, name as SYSTEM_NAME
  9. from zope.interface import Interface, implementer
  10. from twisted.logger import Logger
  11. class IPIDFile(Interface):
  12. """
  13. Manages a file that remembers a process ID.
  14. """
  15. def read():
  16. """
  17. Read the process ID stored in this PID file.
  18. @return: The contained process ID.
  19. @rtype: L{int}
  20. @raise NoPIDFound: If this PID file does not exist.
  21. @raise EnvironmentError: If this PID file cannot be read.
  22. @raise ValueError: If this PID file's content is invalid.
  23. """
  24. def writeRunningPID():
  25. """
  26. Store the PID of the current process in this PID file.
  27. @raise EnvironmentError: If this PID file cannot be written.
  28. """
  29. def remove():
  30. """
  31. Remove this PID file.
  32. @raise EnvironmentError: If this PID file cannot be removed.
  33. """
  34. def isRunning():
  35. """
  36. Determine whether there is a running process corresponding to the PID
  37. in this PID file.
  38. @return: True if this PID file contains a PID and a process with that
  39. PID is currently running; false otherwise.
  40. @rtype: L{bool}
  41. @raise EnvironmentError: If this PID file cannot be read.
  42. @raise InvalidPIDFileError: If this PID file's content is invalid.
  43. @raise StalePIDFileError: If this PID file's content refers to a PID
  44. for which there is no corresponding running process.
  45. """
  46. def __enter__():
  47. """
  48. Enter a context using this PIDFile.
  49. Writes the PID file with the PID of the running process.
  50. @raise AlreadyRunningError: A process corresponding to the PID in this
  51. PID file is already running.
  52. """
  53. def __exit__(excType, excValue, traceback):
  54. """
  55. Exit a context using this PIDFile.
  56. Removes the PID file.
  57. """
  58. @implementer(IPIDFile)
  59. class PIDFile(object):
  60. """
  61. Concrete implementation of L{IPIDFile} based on C{IFilePath}.
  62. This implementation is presently not supported on non-POSIX platforms.
  63. Specifically, calling L{PIDFile.isRunning} will raise
  64. L{NotImplementedError}.
  65. """
  66. _log = Logger()
  67. @staticmethod
  68. def _format(pid):
  69. """
  70. Format a PID file's content.
  71. @param pid: A process ID.
  72. @type pid: int
  73. @return: Formatted PID file contents.
  74. @rtype: L{bytes}
  75. """
  76. return u"{}\n".format(int(pid)).encode("utf-8")
  77. def __init__(self, filePath):
  78. """
  79. @param filePath: The path to the PID file on disk.
  80. @type filePath: L{IFilePath}
  81. """
  82. self.filePath = filePath
  83. def read(self):
  84. pidString = b""
  85. try:
  86. with self.filePath.open() as fh:
  87. for pidString in fh:
  88. break
  89. except OSError as e:
  90. if e.errno == errno.ENOENT: # No such file
  91. raise NoPIDFound("PID file does not exist")
  92. raise
  93. try:
  94. return int(pidString)
  95. except ValueError:
  96. raise InvalidPIDFileError(
  97. "non-integer PID value in PID file: {!r}".format(pidString)
  98. )
  99. def _write(self, pid):
  100. """
  101. Store a PID in this PID file.
  102. @param pid: A PID to store.
  103. @type pid: L{int}
  104. @raise EnvironmentError: If this PID file cannot be written.
  105. """
  106. self.filePath.setContent(self._format(pid=pid))
  107. def writeRunningPID(self):
  108. self._write(getpid())
  109. def remove(self):
  110. self.filePath.remove()
  111. def isRunning(self):
  112. try:
  113. pid = self.read()
  114. except NoPIDFound:
  115. return False
  116. if SYSTEM_NAME == "posix":
  117. return self._pidIsRunningPOSIX(pid)
  118. else:
  119. raise NotImplementedError(
  120. "isRunning is not implemented on {}".format(SYSTEM_NAME)
  121. )
  122. @staticmethod
  123. def _pidIsRunningPOSIX(pid):
  124. """
  125. POSIX implementation for running process check.
  126. Determine whether there is a running process corresponding to the given
  127. PID.
  128. @return: True if the given PID is currently running; false otherwise.
  129. @rtype: L{bool}
  130. @raise EnvironmentError: If this PID file cannot be read.
  131. @raise InvalidPIDFileError: If this PID file's content is invalid.
  132. @raise StalePIDFileError: If this PID file's content refers to a PID
  133. for which there is no corresponding running process.
  134. """
  135. try:
  136. kill(pid, 0)
  137. except OSError as e:
  138. if e.errno == errno.ESRCH: # No such process
  139. raise StalePIDFileError(
  140. "PID file refers to non-existing process"
  141. )
  142. elif e.errno == errno.EPERM: # Not permitted to kill
  143. return True
  144. else:
  145. raise
  146. else:
  147. return True
  148. def __enter__(self):
  149. try:
  150. if self.isRunning():
  151. raise AlreadyRunningError()
  152. except StalePIDFileError:
  153. self._log.info("Replacing stale PID file: {log_source}")
  154. self.writeRunningPID()
  155. return self
  156. def __exit__(self, excType, excValue, traceback):
  157. self.remove()
  158. @implementer(IPIDFile)
  159. class NonePIDFile(object):
  160. """
  161. PID file implementation that does nothing.
  162. This is meant to be used as a "active None" object in place of a PID file
  163. when no PID file is desired.
  164. """
  165. def __init__(self):
  166. pass
  167. def read(self):
  168. raise NoPIDFound("PID file does not exist")
  169. def _write(self, pid):
  170. """
  171. Store a PID in this PID file.
  172. @param pid: A PID to store.
  173. @type pid: L{int}
  174. @raise EnvironmentError: If this PID file cannot be written.
  175. @note: This implementation always raises an L{EnvironmentError}.
  176. """
  177. raise OSError(errno.EPERM, "Operation not permitted")
  178. def writeRunningPID(self):
  179. self._write(0)
  180. def remove(self):
  181. raise OSError(errno.ENOENT, "No such file or directory")
  182. def isRunning(self):
  183. return False
  184. def __enter__(self):
  185. return self
  186. def __exit__(self, excType, excValue, traceback):
  187. pass
  188. nonePIDFile = NonePIDFile()
  189. class AlreadyRunningError(Exception):
  190. """
  191. Process is already running.
  192. """
  193. class InvalidPIDFileError(Exception):
  194. """
  195. PID file contents are invalid.
  196. """
  197. class StalePIDFileError(Exception):
  198. """
  199. PID file contents are valid, but there is no process with the referenced
  200. PID.
  201. """
  202. class NoPIDFound(Exception):
  203. """
  204. No PID found in PID file.
  205. """