diff options
author | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:16:14 +0300 |
---|---|---|
committer | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:43:30 +0300 |
commit | b8cf9e88f4c5c64d9406af533d8948deb050d695 (patch) | |
tree | 218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/internet/_signals.py | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/internet/_signals.py')
-rw-r--r-- | contrib/python/Twisted/py3/twisted/internet/_signals.py | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/internet/_signals.py b/contrib/python/Twisted/py3/twisted/internet/_signals.py new file mode 100644 index 0000000000..fa878f6cba --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/internet/_signals.py @@ -0,0 +1,445 @@ +# -*- 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(): + """ + Called when the event should be wake up. + """ + + def doRead(): + """ + 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() |