123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- # -*- test-case-name: twisted.internet.test.test_sigchld -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- This module is used to integrate child process termination into a
- reactor event loop. This is a challenging feature to provide because
- most platforms indicate process termination via SIGCHLD and do not
- provide a way to wait for that signal and arbitrary I/O events at the
- same time. The naive implementation involves installing a Python
- SIGCHLD handler; unfortunately this leads to other syscalls being
- interrupted (whenever SIGCHLD is received) and failing with EINTR
- (which almost no one is prepared to handle). This interruption can be
- disabled via siginterrupt(2) (or one of the equivalent mechanisms);
- however, if the SIGCHLD is delivered by the platform to a non-main
- thread (not a common occurrence, but difficult to prove impossible),
- the main thread (waiting on select() or another event notification
- API) may not wake up leading to an arbitrary delay before the child
- termination is noticed.
- The basic solution to all these issues involves enabling SA_RESTART (ie,
- disabling system call interruption) and registering a C signal handler which
- writes a byte to a pipe. The other end of the pipe is registered with the
- event loop, allowing it to wake up shortly after SIGCHLD is received. See
- L{_SIGCHLDWaker} for the implementation of the event loop side of this
- solution. The use of a pipe this way is known as the U{self-pipe
- trick<http://cr.yp.to/docs/selfpipe.html>}.
- From Python version 2.6, C{signal.siginterrupt} and C{signal.set_wakeup_fd}
- provide the necessary C signal handler which writes to the pipe to be
- registered with C{SA_RESTART}.
- """
- from __future__ import annotations
- import contextlib
- import errno
- import os
- import signal
- import socket
- from types import FrameType
- from typing import Callable, Optional, Sequence
- from zope.interface import Attribute, Interface, implementer
- from attrs import define, frozen
- from typing_extensions import Protocol, TypeAlias
- from twisted.internet.interfaces import IReadDescriptor
- from twisted.python import failure, log, util
- from twisted.python.runtime import platformType
- if platformType == "posix":
- from . import fdesc, process
- SignalHandler: TypeAlias = Callable[[int, Optional[FrameType]], None]
- def installHandler(fd: int) -> int:
- """
- Install a signal handler which will write a byte to C{fd} when
- I{SIGCHLD} is received.
- This is implemented by installing a SIGCHLD handler that does nothing,
- setting the I{SIGCHLD} handler as not allowed to interrupt system calls,
- and using L{signal.set_wakeup_fd} to do the actual writing.
- @param fd: The file descriptor to which to write when I{SIGCHLD} is
- received.
- @return: The file descriptor previously configured for this use.
- """
- if fd == -1:
- signal.signal(signal.SIGCHLD, signal.SIG_DFL)
- else:
- def noopSignalHandler(*args):
- pass
- signal.signal(signal.SIGCHLD, noopSignalHandler)
- signal.siginterrupt(signal.SIGCHLD, False)
- return signal.set_wakeup_fd(fd)
- def isDefaultHandler():
- """
- Determine whether the I{SIGCHLD} handler is the default or not.
- """
- return signal.getsignal(signal.SIGCHLD) == signal.SIG_DFL
- class SignalHandling(Protocol):
- """
- The L{SignalHandling} protocol enables customizable signal-handling
- behaviors for reactors.
- A value that conforms to L{SignalHandling} has install and uninstall hooks
- that are called by a reactor at the correct times to have the (typically)
- process-global effects necessary for dealing with signals.
- """
- def install(self) -> None:
- """
- Install the signal handlers.
- """
- def uninstall(self) -> None:
- """
- Restore signal handlers to their original state.
- """
- @frozen
- class _WithoutSignalHandling:
- """
- A L{SignalHandling} implementation that does no signal handling.
- This is the implementation of C{installSignalHandlers=False}.
- """
- def install(self) -> None:
- """
- Do not install any signal handlers.
- """
- def uninstall(self) -> None:
- """
- Do nothing because L{install} installed nothing.
- """
- @frozen
- class _WithSignalHandling:
- """
- A reactor core helper that can manage signals: it installs signal handlers
- at start time.
- """
- _sigInt: SignalHandler
- _sigBreak: SignalHandler
- _sigTerm: SignalHandler
- def install(self) -> None:
- """
- Install the signal handlers for the Twisted event loop.
- """
- if signal.getsignal(signal.SIGINT) == signal.default_int_handler:
- # only handle if there isn't already a handler, e.g. for Pdb.
- signal.signal(signal.SIGINT, self._sigInt)
- signal.signal(signal.SIGTERM, self._sigTerm)
- # Catch Ctrl-Break in windows
- SIGBREAK = getattr(signal, "SIGBREAK", None)
- if SIGBREAK is not None:
- signal.signal(SIGBREAK, self._sigBreak)
- def uninstall(self) -> None:
- """
- At the moment, do nothing (for historical reasons).
- """
- # This should really do something.
- # https://github.com/twisted/twisted/issues/11761
- @define
- class _MultiSignalHandling:
- """
- An implementation of L{SignalHandling} which propagates protocol
- method calls to a number of other implementations.
- This supports composition of multiple signal handling implementations into
- a single object so the reactor doesn't have to be concerned with how those
- implementations are factored.
- @ivar _signalHandlings: The other C{SignalHandling} implementations to
- which to propagate calls.
- @ivar _installed: If L{install} has been called but L{uninstall} has not.
- This is used to avoid double cleanup which otherwise results (at least
- during test suite runs) because twisted.internet.reactormixins doesn't
- keep track of whether a reactor has run or not but always invokes its
- cleanup logic.
- """
- _signalHandlings: Sequence[SignalHandling]
- _installed: bool = False
- def install(self) -> None:
- for d in self._signalHandlings:
- d.install()
- self._installed = True
- def uninstall(self) -> None:
- if self._installed:
- for d in self._signalHandlings:
- d.uninstall()
- self._installed = False
- @define
- class _ChildSignalHandling:
- """
- Signal handling behavior which supports I{SIGCHLD} for notification about
- changes to child process state.
- @ivar _childWaker: L{None} or a reference to the L{_SIGCHLDWaker} which is
- used to properly notice child process termination. This is L{None}
- when this handling behavior is not installed and non-C{None}
- otherwise. This is mostly an unfortunate implementation detail due to
- L{_SIGCHLDWaker} allocating file descriptors as a side-effect of its
- initializer.
- """
- _addInternalReader: Callable[[IReadDescriptor], object]
- _removeInternalReader: Callable[[IReadDescriptor], object]
- _childWaker: Optional[_SIGCHLDWaker] = None
- def install(self) -> None:
- """
- Extend the basic signal handling logic to also support handling
- SIGCHLD to know when to try to reap child processes.
- """
- # This conditional should probably not be necessary.
- # https://github.com/twisted/twisted/issues/11763
- if self._childWaker is None:
- self._childWaker = _SIGCHLDWaker()
- self._addInternalReader(self._childWaker)
- self._childWaker.install()
- # Also reap all processes right now, in case we missed any
- # signals before we installed the SIGCHLD waker/handler.
- # This should only happen if someone used spawnProcess
- # before calling reactor.run (and the process also exited
- # already).
- process.reapAllProcesses()
- def uninstall(self) -> None:
- """
- If a child waker was created and installed, uninstall it now.
- Since this disables reactor functionality and is only called when the
- reactor is stopping, it doesn't provide any directly useful
- functionality, but the cleanup of reactor-related process-global state
- that it does helps in unit tests involving multiple reactors and is
- generally just a nice thing.
- """
- assert self._childWaker is not None
- # XXX This would probably be an alright place to put all of the
- # cleanup code for all internal readers (here and in the base class,
- # anyway). See #3063 for that cleanup task.
- self._removeInternalReader(self._childWaker)
- self._childWaker.uninstall()
- self._childWaker.connectionLost(failure.Failure(Exception("uninstalled")))
- # We just spoiled the current _childWaker so throw it away. We can
- # make a new one later if need be.
- self._childWaker = None
- class _IWaker(Interface):
- """
- Interface to wake up the event loop based on the self-pipe trick.
- The U{I{self-pipe trick}<http://cr.yp.to/docs/selfpipe.html>}, used to wake
- up the main loop from another thread or a signal handler.
- This is why we have wakeUp together with doRead
- This is used by threads or signals to wake up the event loop.
- """
- disconnected = Attribute("")
- def wakeUp() -> None:
- """
- Called when the event should be wake up.
- """
- def doRead() -> None:
- """
- Read some data from my connection and discard it.
- """
- def connectionLost(reason: failure.Failure) -> None:
- """
- Called when connection was closed and the pipes.
- """
- @implementer(_IWaker)
- class _SocketWaker(log.Logger):
- """
- The I{self-pipe trick<http://cr.yp.to/docs/selfpipe.html>}, implemented
- using a pair of sockets rather than pipes (due to the lack of support in
- select() on Windows for pipes), used to wake up the main loop from
- another thread.
- """
- disconnected = 0
- def __init__(self) -> None:
- """Initialize."""
- # Following select_trigger (from asyncore)'s example;
- client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- with contextlib.closing(
- socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ) as server:
- server.bind(("127.0.0.1", 0))
- server.listen(1)
- client.connect(server.getsockname())
- reader, clientaddr = server.accept()
- client.setblocking(False)
- reader.setblocking(False)
- self.r = reader
- self.w = client
- self.fileno = self.r.fileno
- def wakeUp(self):
- """Send a byte to my connection."""
- try:
- util.untilConcludes(self.w.send, b"x")
- except OSError as e:
- if e.args[0] != errno.WSAEWOULDBLOCK:
- raise
- def doRead(self):
- """
- Read some data from my connection.
- """
- try:
- self.r.recv(8192)
- except OSError:
- pass
- def connectionLost(self, reason):
- self.r.close()
- self.w.close()
- @implementer(IReadDescriptor)
- class _FDWaker(log.Logger):
- """
- The I{self-pipe trick<http://cr.yp.to/docs/selfpipe.html>}, used to wake
- up the main loop from another thread or a signal handler.
- L{_FDWaker} is a base class for waker implementations based on
- writing to a pipe being monitored by the reactor.
- @ivar o: The file descriptor for the end of the pipe which can be
- written to wake up a reactor monitoring this waker.
- @ivar i: The file descriptor which should be monitored in order to
- be awoken by this waker.
- """
- disconnected = 0
- i: int
- o: int
- def __init__(self) -> None:
- """Initialize."""
- self.i, self.o = os.pipe()
- fdesc.setNonBlocking(self.i)
- fdesc._setCloseOnExec(self.i)
- fdesc.setNonBlocking(self.o)
- fdesc._setCloseOnExec(self.o)
- self.fileno = lambda: self.i
- def doRead(self) -> None:
- """
- Read some bytes from the pipe and discard them.
- """
- fdesc.readFromFD(self.fileno(), lambda data: None)
- def connectionLost(self, reason):
- """Close both ends of my pipe."""
- if not hasattr(self, "o"):
- return
- for fd in self.i, self.o:
- try:
- os.close(fd)
- except OSError:
- pass
- del self.i, self.o
- @implementer(_IWaker)
- class _UnixWaker(_FDWaker):
- """
- This class provides a simple interface to wake up the event loop.
- This is used by threads or signals to wake up the event loop.
- """
- def wakeUp(self):
- """Write one byte to the pipe, and flush it."""
- # We don't use fdesc.writeToFD since we need to distinguish
- # between EINTR (try again) and EAGAIN (do nothing).
- if self.o is not None:
- try:
- util.untilConcludes(os.write, self.o, b"x")
- except OSError as e:
- # XXX There is no unit test for raising the exception
- # for other errnos. See #4285.
- if e.errno != errno.EAGAIN:
- raise
- if platformType == "posix":
- _Waker = _UnixWaker
- else:
- # Primarily Windows and Jython.
- _Waker = _SocketWaker # type: ignore[misc,assignment]
- class _SIGCHLDWaker(_FDWaker):
- """
- L{_SIGCHLDWaker} can wake up a reactor whenever C{SIGCHLD} is received.
- """
- def install(self) -> None:
- """
- Install the handler necessary to make this waker active.
- """
- installHandler(self.o)
- def uninstall(self) -> None:
- """
- Remove the handler which makes this waker active.
- """
- installHandler(-1)
- def doRead(self) -> None:
- """
- Having woken up the reactor in response to receipt of
- C{SIGCHLD}, reap the process which exited.
- This is called whenever the reactor notices the waker pipe is
- writeable, which happens soon after any call to the C{wakeUp}
- method.
- """
- super().doRead()
- process.reapAllProcesses()
|