pidlockfile.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. # -*- coding: utf-8 -*-
  2. # daemon/pidlockfile.py
  3. # Part of python-daemon, an implementation of PEP 3143.
  4. #
  5. # Copyright © 2008–2009 Ben Finney <ben+python@benfinney.id.au>
  6. #
  7. # This is free software: you may copy, modify, and/or distribute this work
  8. # under the terms of the Python Software Foundation License, version 2 or
  9. # later as published by the Python Software Foundation.
  10. # No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
  11. """ Lockfile behaviour implemented via Unix PID files.
  12. """
  13. import os
  14. import errno
  15. from lockfile import (
  16. LinkFileLock,
  17. AlreadyLocked, LockFailed,
  18. NotLocked, NotMyLock,
  19. )
  20. class PIDFileError(Exception):
  21. """ Abstract base class for errors specific to PID files. """
  22. class PIDFileParseError(ValueError, PIDFileError):
  23. """ Raised when parsing contents of PID file fails. """
  24. class PIDLockFile(LinkFileLock, object):
  25. """ Lockfile implemented as a Unix PID file.
  26. The PID file is named by the attribute `path`. When locked,
  27. the file will be created with a single line of text,
  28. containing the process ID (PID) of the process that acquired
  29. the lock.
  30. The lock is acquired and maintained as per `LinkFileLock`.
  31. """
  32. def read_pid(self):
  33. """ Get the PID from the lock file.
  34. """
  35. result = read_pid_from_pidfile(self.path)
  36. return result
  37. def acquire(self, *args, **kwargs):
  38. """ Acquire the lock.
  39. Locks the PID file then creates the PID file for this
  40. lock. The `timeout` parameter is used as for the
  41. `LinkFileLock` class.
  42. """
  43. super(PIDLockFile, self).acquire(*args, **kwargs)
  44. try:
  45. write_pid_to_pidfile(self.path)
  46. except OSError, exc:
  47. error = LockFailed("%(exc)s" % vars())
  48. raise error
  49. def release(self):
  50. """ Release the lock.
  51. Removes the PID file then releases the lock, or raises an
  52. error if the current process does not hold the lock.
  53. """
  54. if self.i_am_locking():
  55. remove_existing_pidfile(self.path)
  56. super(PIDLockFile, self).release()
  57. def break_lock(self):
  58. """ Break an existing lock.
  59. If the lock is held, breaks the lock and removes the PID
  60. file.
  61. """
  62. super(PIDLockFile, self).break_lock()
  63. remove_existing_pidfile(self.path)
  64. class TimeoutPIDLockFile(PIDLockFile):
  65. """ Lockfile with default timeout, implemented as a Unix PID file.
  66. This uses the ``PIDLockFile`` implementation, with the
  67. following changes:
  68. * The `acquire_timeout` parameter to the initialiser will be
  69. used as the default `timeout` parameter for the `acquire`
  70. method.
  71. """
  72. def __init__(self, path, acquire_timeout=None, *args, **kwargs):
  73. """ Set up the parameters of a DaemonRunnerLock. """
  74. self.acquire_timeout = acquire_timeout
  75. super(TimeoutPIDLockFile, self).__init__(path, *args, **kwargs)
  76. def acquire(self, timeout=None, *args, **kwargs):
  77. """ Acquire the lock. """
  78. if timeout is None:
  79. timeout = self.acquire_timeout
  80. super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
  81. def read_pid_from_pidfile(pidfile_path):
  82. """ Read the PID recorded in the named PID file.
  83. Read and return the numeric PID recorded as text in the named
  84. PID file. If the PID file does not exist, return ``None``. If
  85. the content is not a valid PID, raise ``PIDFileParseError``.
  86. """
  87. pid = None
  88. pidfile = None
  89. try:
  90. pidfile = open(pidfile_path, 'r')
  91. except IOError, exc:
  92. if exc.errno == errno.ENOENT:
  93. pass
  94. else:
  95. raise
  96. if pidfile:
  97. # According to the FHS 2.3 section on PID files in ‘/var/run’:
  98. #
  99. # The file must consist of the process identifier in
  100. # ASCII-encoded decimal, followed by a newline character. …
  101. #
  102. # Programs that read PID files should be somewhat flexible
  103. # in what they accept; i.e., they should ignore extra
  104. # whitespace, leading zeroes, absence of the trailing
  105. # newline, or additional lines in the PID file.
  106. line = pidfile.readline().strip()
  107. try:
  108. pid = int(line)
  109. except ValueError:
  110. raise PIDFileParseError(
  111. "PID file %(pidfile_path)r contents invalid" % vars())
  112. pidfile.close()
  113. return pid
  114. def write_pid_to_pidfile(pidfile_path):
  115. """ Write the PID in the named PID file.
  116. Get the numeric process ID (“PID”) of the current process
  117. and write it to the named file as a line of text.
  118. """
  119. open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
  120. open_mode = (
  121. ((os.R_OK | os.W_OK) << 6) |
  122. ((os.R_OK) << 3) |
  123. ((os.R_OK)))
  124. pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
  125. pidfile = os.fdopen(pidfile_fd, 'w')
  126. # According to the FHS 2.3 section on PID files in ‘/var/run’:
  127. #
  128. # The file must consist of the process identifier in
  129. # ASCII-encoded decimal, followed by a newline character. For
  130. # example, if crond was process number 25, /var/run/crond.pid
  131. # would contain three characters: two, five, and newline.
  132. pid = os.getpid()
  133. line = "%(pid)d\n" % vars()
  134. pidfile.write(line)
  135. pidfile.close()
  136. def remove_existing_pidfile(pidfile_path):
  137. """ Remove the named PID file if it exists.
  138. Remove the named PID file. Ignore the condition if the file
  139. does not exist, since that only means we are already in the
  140. desired state.
  141. """
  142. try:
  143. os.remove(pidfile_path)
  144. except OSError, exc:
  145. if exc.errno == errno.ENOENT:
  146. pass
  147. else:
  148. raise