semaphore_tracker.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #
  2. # On Unix we run a server process which keeps track of unlinked
  3. # semaphores. The server ignores SIGINT and SIGTERM and reads from a
  4. # pipe. Every other process of the program has a copy of the writable
  5. # end of the pipe, so we get EOF when all other processes have exited.
  6. # Then the server process unlinks any remaining semaphore names.
  7. #
  8. # This is important because the system only supports a limited number
  9. # of named semaphores, and they will not be automatically removed till
  10. # the next reboot. Without this semaphore tracker process, "killall
  11. # python" would probably leave unlinked semaphores.
  12. #
  13. import os
  14. import signal
  15. import sys
  16. import threading
  17. import warnings
  18. import _multiprocessing
  19. from . import spawn
  20. from . import util
  21. __all__ = ['ensure_running', 'register', 'unregister']
  22. class SemaphoreTracker(object):
  23. def __init__(self):
  24. self._lock = threading.Lock()
  25. self._fd = None
  26. self._pid = None
  27. def getfd(self):
  28. self.ensure_running()
  29. return self._fd
  30. def ensure_running(self):
  31. '''Make sure that semaphore tracker process is running.
  32. This can be run from any process. Usually a child process will use
  33. the semaphore created by its parent.'''
  34. with self._lock:
  35. if self._pid is not None:
  36. # semaphore tracker was launched before, is it still running?
  37. pid, status = os.waitpid(self._pid, os.WNOHANG)
  38. if not pid:
  39. # => still alive
  40. return
  41. # => dead, launch it again
  42. os.close(self._fd)
  43. self._fd = None
  44. self._pid = None
  45. warnings.warn('semaphore_tracker: process died unexpectedly, '
  46. 'relaunching. Some semaphores might leak.')
  47. fds_to_pass = []
  48. try:
  49. fds_to_pass.append(sys.stderr.fileno())
  50. except Exception:
  51. pass
  52. cmd = 'from multiprocessing.semaphore_tracker import main;main(%d)'
  53. r, w = os.pipe()
  54. try:
  55. fds_to_pass.append(r)
  56. # process will out live us, so no need to wait on pid
  57. exe = spawn.get_executable()
  58. args = [exe] + util._args_from_interpreter_flags()
  59. args += ['-c', cmd % r]
  60. pid = util.spawnv_passfds(exe, args, fds_to_pass)
  61. except:
  62. os.close(w)
  63. raise
  64. else:
  65. self._fd = w
  66. self._pid = pid
  67. finally:
  68. os.close(r)
  69. def register(self, name):
  70. '''Register name of semaphore with semaphore tracker.'''
  71. self._send('REGISTER', name)
  72. def unregister(self, name):
  73. '''Unregister name of semaphore with semaphore tracker.'''
  74. self._send('UNREGISTER', name)
  75. def _send(self, cmd, name):
  76. self.ensure_running()
  77. msg = '{0}:{1}\n'.format(cmd, name).encode('ascii')
  78. if len(name) > 512:
  79. # posix guarantees that writes to a pipe of less than PIPE_BUF
  80. # bytes are atomic, and that PIPE_BUF >= 512
  81. raise ValueError('name too long')
  82. nbytes = os.write(self._fd, msg)
  83. assert nbytes == len(msg), "nbytes {0:n} but len(msg) {1:n}".format(
  84. nbytes, len(msg))
  85. _semaphore_tracker = SemaphoreTracker()
  86. ensure_running = _semaphore_tracker.ensure_running
  87. register = _semaphore_tracker.register
  88. unregister = _semaphore_tracker.unregister
  89. getfd = _semaphore_tracker.getfd
  90. def main(fd):
  91. '''Run semaphore tracker.'''
  92. # protect the process from ^C and "killall python" etc
  93. signal.signal(signal.SIGINT, signal.SIG_IGN)
  94. signal.signal(signal.SIGTERM, signal.SIG_IGN)
  95. for f in (sys.stdin, sys.stdout):
  96. try:
  97. f.close()
  98. except Exception:
  99. pass
  100. cache = set()
  101. try:
  102. # keep track of registered/unregistered semaphores
  103. with open(fd, 'rb') as f:
  104. for line in f:
  105. try:
  106. cmd, name = line.strip().split(b':')
  107. if cmd == b'REGISTER':
  108. cache.add(name)
  109. elif cmd == b'UNREGISTER':
  110. cache.remove(name)
  111. else:
  112. raise RuntimeError('unrecognized command %r' % cmd)
  113. except Exception:
  114. try:
  115. sys.excepthook(*sys.exc_info())
  116. except:
  117. pass
  118. finally:
  119. # all processes have terminated; cleanup any remaining semaphores
  120. if cache:
  121. try:
  122. warnings.warn('semaphore_tracker: There appear to be %d '
  123. 'leaked semaphores to clean up at shutdown' %
  124. len(cache))
  125. except Exception:
  126. pass
  127. for name in cache:
  128. # For some reason the process which created and registered this
  129. # semaphore has failed to unregister it. Presumably it has died.
  130. # We therefore unlink it.
  131. try:
  132. name = name.decode('ascii')
  133. try:
  134. _multiprocessing.sem_unlink(name)
  135. except Exception as e:
  136. warnings.warn('semaphore_tracker: %r: %s' % (name, e))
  137. finally:
  138. pass