_signals.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. # -*- test-case-name: twisted.internet.test.test_sigchld -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. This module is used to integrate child process termination into a
  6. reactor event loop. This is a challenging feature to provide because
  7. most platforms indicate process termination via SIGCHLD and do not
  8. provide a way to wait for that signal and arbitrary I/O events at the
  9. same time. The naive implementation involves installing a Python
  10. SIGCHLD handler; unfortunately this leads to other syscalls being
  11. interrupted (whenever SIGCHLD is received) and failing with EINTR
  12. (which almost no one is prepared to handle). This interruption can be
  13. disabled via siginterrupt(2) (or one of the equivalent mechanisms);
  14. however, if the SIGCHLD is delivered by the platform to a non-main
  15. thread (not a common occurrence, but difficult to prove impossible),
  16. the main thread (waiting on select() or another event notification
  17. API) may not wake up leading to an arbitrary delay before the child
  18. termination is noticed.
  19. The basic solution to all these issues involves enabling SA_RESTART (ie,
  20. disabling system call interruption) and registering a C signal handler which
  21. writes a byte to a pipe. The other end of the pipe is registered with the
  22. event loop, allowing it to wake up shortly after SIGCHLD is received. See
  23. L{_SIGCHLDWaker} for the implementation of the event loop side of this
  24. solution. The use of a pipe this way is known as the U{self-pipe
  25. trick<http://cr.yp.to/docs/selfpipe.html>}.
  26. From Python version 2.6, C{signal.siginterrupt} and C{signal.set_wakeup_fd}
  27. provide the necessary C signal handler which writes to the pipe to be
  28. registered with C{SA_RESTART}.
  29. """
  30. from __future__ import annotations
  31. import contextlib
  32. import errno
  33. import os
  34. import signal
  35. import socket
  36. from types import FrameType
  37. from typing import Callable, Optional, Sequence
  38. from zope.interface import Attribute, Interface, implementer
  39. from attrs import define, frozen
  40. from typing_extensions import Protocol, TypeAlias
  41. from twisted.internet.interfaces import IReadDescriptor
  42. from twisted.python import failure, log, util
  43. from twisted.python.runtime import platformType
  44. if platformType == "posix":
  45. from . import fdesc, process
  46. SignalHandler: TypeAlias = Callable[[int, Optional[FrameType]], None]
  47. def installHandler(fd: int) -> int:
  48. """
  49. Install a signal handler which will write a byte to C{fd} when
  50. I{SIGCHLD} is received.
  51. This is implemented by installing a SIGCHLD handler that does nothing,
  52. setting the I{SIGCHLD} handler as not allowed to interrupt system calls,
  53. and using L{signal.set_wakeup_fd} to do the actual writing.
  54. @param fd: The file descriptor to which to write when I{SIGCHLD} is
  55. received.
  56. @return: The file descriptor previously configured for this use.
  57. """
  58. if fd == -1:
  59. signal.signal(signal.SIGCHLD, signal.SIG_DFL)
  60. else:
  61. def noopSignalHandler(*args):
  62. pass
  63. signal.signal(signal.SIGCHLD, noopSignalHandler)
  64. signal.siginterrupt(signal.SIGCHLD, False)
  65. return signal.set_wakeup_fd(fd)
  66. def isDefaultHandler():
  67. """
  68. Determine whether the I{SIGCHLD} handler is the default or not.
  69. """
  70. return signal.getsignal(signal.SIGCHLD) == signal.SIG_DFL
  71. class SignalHandling(Protocol):
  72. """
  73. The L{SignalHandling} protocol enables customizable signal-handling
  74. behaviors for reactors.
  75. A value that conforms to L{SignalHandling} has install and uninstall hooks
  76. that are called by a reactor at the correct times to have the (typically)
  77. process-global effects necessary for dealing with signals.
  78. """
  79. def install(self) -> None:
  80. """
  81. Install the signal handlers.
  82. """
  83. def uninstall(self) -> None:
  84. """
  85. Restore signal handlers to their original state.
  86. """
  87. @frozen
  88. class _WithoutSignalHandling:
  89. """
  90. A L{SignalHandling} implementation that does no signal handling.
  91. This is the implementation of C{installSignalHandlers=False}.
  92. """
  93. def install(self) -> None:
  94. """
  95. Do not install any signal handlers.
  96. """
  97. def uninstall(self) -> None:
  98. """
  99. Do nothing because L{install} installed nothing.
  100. """
  101. @frozen
  102. class _WithSignalHandling:
  103. """
  104. A reactor core helper that can manage signals: it installs signal handlers
  105. at start time.
  106. """
  107. _sigInt: SignalHandler
  108. _sigBreak: SignalHandler
  109. _sigTerm: SignalHandler
  110. def install(self) -> None:
  111. """
  112. Install the signal handlers for the Twisted event loop.
  113. """
  114. if signal.getsignal(signal.SIGINT) == signal.default_int_handler:
  115. # only handle if there isn't already a handler, e.g. for Pdb.
  116. signal.signal(signal.SIGINT, self._sigInt)
  117. signal.signal(signal.SIGTERM, self._sigTerm)
  118. # Catch Ctrl-Break in windows
  119. SIGBREAK = getattr(signal, "SIGBREAK", None)
  120. if SIGBREAK is not None:
  121. signal.signal(SIGBREAK, self._sigBreak)
  122. def uninstall(self) -> None:
  123. """
  124. At the moment, do nothing (for historical reasons).
  125. """
  126. # This should really do something.
  127. # https://github.com/twisted/twisted/issues/11761
  128. @define
  129. class _MultiSignalHandling:
  130. """
  131. An implementation of L{SignalHandling} which propagates protocol
  132. method calls to a number of other implementations.
  133. This supports composition of multiple signal handling implementations into
  134. a single object so the reactor doesn't have to be concerned with how those
  135. implementations are factored.
  136. @ivar _signalHandlings: The other C{SignalHandling} implementations to
  137. which to propagate calls.
  138. @ivar _installed: If L{install} has been called but L{uninstall} has not.
  139. This is used to avoid double cleanup which otherwise results (at least
  140. during test suite runs) because twisted.internet.reactormixins doesn't
  141. keep track of whether a reactor has run or not but always invokes its
  142. cleanup logic.
  143. """
  144. _signalHandlings: Sequence[SignalHandling]
  145. _installed: bool = False
  146. def install(self) -> None:
  147. for d in self._signalHandlings:
  148. d.install()
  149. self._installed = True
  150. def uninstall(self) -> None:
  151. if self._installed:
  152. for d in self._signalHandlings:
  153. d.uninstall()
  154. self._installed = False
  155. @define
  156. class _ChildSignalHandling:
  157. """
  158. Signal handling behavior which supports I{SIGCHLD} for notification about
  159. changes to child process state.
  160. @ivar _childWaker: L{None} or a reference to the L{_SIGCHLDWaker} which is
  161. used to properly notice child process termination. This is L{None}
  162. when this handling behavior is not installed and non-C{None}
  163. otherwise. This is mostly an unfortunate implementation detail due to
  164. L{_SIGCHLDWaker} allocating file descriptors as a side-effect of its
  165. initializer.
  166. """
  167. _addInternalReader: Callable[[IReadDescriptor], object]
  168. _removeInternalReader: Callable[[IReadDescriptor], object]
  169. _childWaker: Optional[_SIGCHLDWaker] = None
  170. def install(self) -> None:
  171. """
  172. Extend the basic signal handling logic to also support handling
  173. SIGCHLD to know when to try to reap child processes.
  174. """
  175. # This conditional should probably not be necessary.
  176. # https://github.com/twisted/twisted/issues/11763
  177. if self._childWaker is None:
  178. self._childWaker = _SIGCHLDWaker()
  179. self._addInternalReader(self._childWaker)
  180. self._childWaker.install()
  181. # Also reap all processes right now, in case we missed any
  182. # signals before we installed the SIGCHLD waker/handler.
  183. # This should only happen if someone used spawnProcess
  184. # before calling reactor.run (and the process also exited
  185. # already).
  186. process.reapAllProcesses()
  187. def uninstall(self) -> None:
  188. """
  189. If a child waker was created and installed, uninstall it now.
  190. Since this disables reactor functionality and is only called when the
  191. reactor is stopping, it doesn't provide any directly useful
  192. functionality, but the cleanup of reactor-related process-global state
  193. that it does helps in unit tests involving multiple reactors and is
  194. generally just a nice thing.
  195. """
  196. assert self._childWaker is not None
  197. # XXX This would probably be an alright place to put all of the
  198. # cleanup code for all internal readers (here and in the base class,
  199. # anyway). See #3063 for that cleanup task.
  200. self._removeInternalReader(self._childWaker)
  201. self._childWaker.uninstall()
  202. self._childWaker.connectionLost(failure.Failure(Exception("uninstalled")))
  203. # We just spoiled the current _childWaker so throw it away. We can
  204. # make a new one later if need be.
  205. self._childWaker = None
  206. class _IWaker(Interface):
  207. """
  208. Interface to wake up the event loop based on the self-pipe trick.
  209. The U{I{self-pipe trick}<http://cr.yp.to/docs/selfpipe.html>}, used to wake
  210. up the main loop from another thread or a signal handler.
  211. This is why we have wakeUp together with doRead
  212. This is used by threads or signals to wake up the event loop.
  213. """
  214. disconnected = Attribute("")
  215. def wakeUp() -> None:
  216. """
  217. Called when the event should be wake up.
  218. """
  219. def doRead() -> None:
  220. """
  221. Read some data from my connection and discard it.
  222. """
  223. def connectionLost(reason: failure.Failure) -> None:
  224. """
  225. Called when connection was closed and the pipes.
  226. """
  227. @implementer(_IWaker)
  228. class _SocketWaker(log.Logger):
  229. """
  230. The I{self-pipe trick<http://cr.yp.to/docs/selfpipe.html>}, implemented
  231. using a pair of sockets rather than pipes (due to the lack of support in
  232. select() on Windows for pipes), used to wake up the main loop from
  233. another thread.
  234. """
  235. disconnected = 0
  236. def __init__(self) -> None:
  237. """Initialize."""
  238. # Following select_trigger (from asyncore)'s example;
  239. client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  240. client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
  241. with contextlib.closing(
  242. socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  243. ) as server:
  244. server.bind(("127.0.0.1", 0))
  245. server.listen(1)
  246. client.connect(server.getsockname())
  247. reader, clientaddr = server.accept()
  248. client.setblocking(False)
  249. reader.setblocking(False)
  250. self.r = reader
  251. self.w = client
  252. self.fileno = self.r.fileno
  253. def wakeUp(self):
  254. """Send a byte to my connection."""
  255. try:
  256. util.untilConcludes(self.w.send, b"x")
  257. except OSError as e:
  258. if e.args[0] != errno.WSAEWOULDBLOCK:
  259. raise
  260. def doRead(self):
  261. """
  262. Read some data from my connection.
  263. """
  264. try:
  265. self.r.recv(8192)
  266. except OSError:
  267. pass
  268. def connectionLost(self, reason):
  269. self.r.close()
  270. self.w.close()
  271. @implementer(IReadDescriptor)
  272. class _FDWaker(log.Logger):
  273. """
  274. The I{self-pipe trick<http://cr.yp.to/docs/selfpipe.html>}, used to wake
  275. up the main loop from another thread or a signal handler.
  276. L{_FDWaker} is a base class for waker implementations based on
  277. writing to a pipe being monitored by the reactor.
  278. @ivar o: The file descriptor for the end of the pipe which can be
  279. written to wake up a reactor monitoring this waker.
  280. @ivar i: The file descriptor which should be monitored in order to
  281. be awoken by this waker.
  282. """
  283. disconnected = 0
  284. i: int
  285. o: int
  286. def __init__(self) -> None:
  287. """Initialize."""
  288. self.i, self.o = os.pipe()
  289. fdesc.setNonBlocking(self.i)
  290. fdesc._setCloseOnExec(self.i)
  291. fdesc.setNonBlocking(self.o)
  292. fdesc._setCloseOnExec(self.o)
  293. self.fileno = lambda: self.i
  294. def doRead(self) -> None:
  295. """
  296. Read some bytes from the pipe and discard them.
  297. """
  298. fdesc.readFromFD(self.fileno(), lambda data: None)
  299. def connectionLost(self, reason):
  300. """Close both ends of my pipe."""
  301. if not hasattr(self, "o"):
  302. return
  303. for fd in self.i, self.o:
  304. try:
  305. os.close(fd)
  306. except OSError:
  307. pass
  308. del self.i, self.o
  309. @implementer(_IWaker)
  310. class _UnixWaker(_FDWaker):
  311. """
  312. This class provides a simple interface to wake up the event loop.
  313. This is used by threads or signals to wake up the event loop.
  314. """
  315. def wakeUp(self):
  316. """Write one byte to the pipe, and flush it."""
  317. # We don't use fdesc.writeToFD since we need to distinguish
  318. # between EINTR (try again) and EAGAIN (do nothing).
  319. if self.o is not None:
  320. try:
  321. util.untilConcludes(os.write, self.o, b"x")
  322. except OSError as e:
  323. # XXX There is no unit test for raising the exception
  324. # for other errnos. See #4285.
  325. if e.errno != errno.EAGAIN:
  326. raise
  327. if platformType == "posix":
  328. _Waker = _UnixWaker
  329. else:
  330. # Primarily Windows and Jython.
  331. _Waker = _SocketWaker # type: ignore[misc,assignment]
  332. class _SIGCHLDWaker(_FDWaker):
  333. """
  334. L{_SIGCHLDWaker} can wake up a reactor whenever C{SIGCHLD} is received.
  335. """
  336. def install(self) -> None:
  337. """
  338. Install the handler necessary to make this waker active.
  339. """
  340. installHandler(self.o)
  341. def uninstall(self) -> None:
  342. """
  343. Remove the handler which makes this waker active.
  344. """
  345. installHandler(-1)
  346. def doRead(self) -> None:
  347. """
  348. Having woken up the reactor in response to receipt of
  349. C{SIGCHLD}, reap the process which exited.
  350. This is called whenever the reactor notices the waker pipe is
  351. writeable, which happens soon after any call to the C{wakeUp}
  352. method.
  353. """
  354. super().doRead()
  355. process.reapAllProcesses()