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/_glibbase.py | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/internet/_glibbase.py')
-rw-r--r-- | contrib/python/Twisted/py3/twisted/internet/_glibbase.py | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/internet/_glibbase.py b/contrib/python/Twisted/py3/twisted/internet/_glibbase.py new file mode 100644 index 0000000000..4a6d1323ab --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/internet/_glibbase.py @@ -0,0 +1,369 @@ +# -*- test-case-name: twisted.internet.test -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +This module provides base support for Twisted to interact with the glib/gtk +mainloops. + +The classes in this module should not be used directly, but rather you should +import gireactor or gtk3reactor for GObject Introspection based applications, +or glib2reactor or gtk2reactor for applications using legacy static bindings. +""" + + +import sys +from typing import Any, Callable, Dict, Set + +from zope.interface import implementer + +from twisted.internet import posixbase +from twisted.internet.abstract import FileDescriptor +from twisted.internet.interfaces import IReactorFDSet, IReadDescriptor, IWriteDescriptor +from twisted.python import log +from twisted.python.monkey import MonkeyPatcher +from ._signals import _UnixWaker + + +def ensureNotImported(moduleNames, errorMessage, preventImports=[]): + """ + Check whether the given modules were imported, and if requested, ensure + they will not be importable in the future. + + @param moduleNames: A list of module names we make sure aren't imported. + @type moduleNames: C{list} of C{str} + + @param preventImports: A list of module name whose future imports should + be prevented. + @type preventImports: C{list} of C{str} + + @param errorMessage: Message to use when raising an C{ImportError}. + @type errorMessage: C{str} + + @raise ImportError: with given error message if a given module name + has already been imported. + """ + for name in moduleNames: + if sys.modules.get(name) is not None: + raise ImportError(errorMessage) + + # Disable module imports to avoid potential problems. + for name in preventImports: + sys.modules[name] = None + + +class GlibWaker(_UnixWaker): + """ + Run scheduled events after waking up. + """ + + def __init__(self, reactor): + super().__init__() + self.reactor = reactor + + def doRead(self) -> None: + super().doRead() + self.reactor._simulate() + + +def _signalGlue(): + """ + Integrate glib's wakeup file descriptor usage and our own. + + Python supports only one wakeup file descriptor at a time and both Twisted + and glib want to use it. + + This is a context manager that can be wrapped around the whole glib + reactor main loop which makes our signal handling work with glib's signal + handling. + """ + from gi import _ossighelper as signalGlue # type: ignore[import] + + patcher = MonkeyPatcher() + patcher.addPatch(signalGlue, "_wakeup_fd_is_active", True) + return patcher + + +def _loopQuitter( + idleAdd: Callable[[Callable[[], None]], None], loopQuit: Callable[[], None] +) -> Callable[[], None]: + """ + Combine the C{glib.idle_add} and C{glib.MainLoop.quit} functions into a + function suitable for crashing the reactor. + """ + return lambda: idleAdd(loopQuit) + + +@implementer(IReactorFDSet) +class GlibReactorBase(posixbase.PosixReactorBase, posixbase._PollLikeMixin): + """ + Base class for GObject event loop reactors. + + Notification for I/O events (reads and writes on file descriptors) is done + by the gobject-based event loop. File descriptors are registered with + gobject with the appropriate flags for read/write/disconnect notification. + + Time-based events, the results of C{callLater} and C{callFromThread}, are + handled differently. Rather than registering each event with gobject, a + single gobject timeout is registered for the earliest scheduled event, the + output of C{reactor.timeout()}. For example, if there are timeouts in 1, 2 + and 3.4 seconds, a single timeout is registered for 1 second in the + future. When this timeout is hit, C{_simulate} is called, which calls the + appropriate Twisted-level handlers, and a new timeout is added to gobject + by the C{_reschedule} method. + + To handle C{callFromThread} events, we use a custom waker that calls + C{_simulate} whenever it wakes up. + + @ivar _sources: A dictionary mapping L{FileDescriptor} instances to + GSource handles. + + @ivar _reads: A set of L{FileDescriptor} instances currently monitored for + reading. + + @ivar _writes: A set of L{FileDescriptor} instances currently monitored for + writing. + + @ivar _simtag: A GSource handle for the next L{simulate} call. + """ + + # Install a waker that knows it needs to call C{_simulate} in order to run + # callbacks queued from a thread: + def _wakerFactory(self) -> GlibWaker: + return GlibWaker(self) + + def __init__(self, glib_module: Any, gtk_module: Any, useGtk: bool = False) -> None: + self._simtag = None + self._reads: Set[IReadDescriptor] = set() + self._writes: Set[IWriteDescriptor] = set() + self._sources: Dict[FileDescriptor, int] = {} + self._glib = glib_module + + self._POLL_DISCONNECTED = ( + glib_module.IOCondition.HUP + | glib_module.IOCondition.ERR + | glib_module.IOCondition.NVAL + ) + self._POLL_IN = glib_module.IOCondition.IN + self._POLL_OUT = glib_module.IOCondition.OUT + + # glib's iochannel sources won't tell us about any events that we haven't + # asked for, even if those events aren't sensible inputs to the poll() + # call. + self.INFLAGS = self._POLL_IN | self._POLL_DISCONNECTED + self.OUTFLAGS = self._POLL_OUT | self._POLL_DISCONNECTED + + super().__init__() + + self._source_remove = self._glib.source_remove + self._timeout_add = self._glib.timeout_add + + self.context = self._glib.main_context_default() + self._pending = self.context.pending + self._iteration = self.context.iteration + self.loop = self._glib.MainLoop() + self._crash = _loopQuitter(self._glib.idle_add, self.loop.quit) + self._run = self.loop.run + + def _reallyStartRunning(self): + """ + Make sure the reactor's signal handlers are installed despite any + outside interference. + """ + # First, install SIGINT and friends: + super()._reallyStartRunning() + + # Next, since certain versions of gtk will clobber our signal handler, + # set all signal handlers again after the event loop has started to + # ensure they're *really* set. + # + # We don't actually know which versions of gtk do this so this might + # be obsolete. If so, that would be great and this whole method can + # go away. Someone needs to find out, though. + # + # https://github.com/twisted/twisted/issues/11762 + + def reinitSignals(): + self._signals.uninstall() + self._signals.install() + + self.callLater(0, reinitSignals) + + # The input_add function in pygtk1 checks for objects with a + # 'fileno' method and, if present, uses the result of that method + # as the input source. The pygtk2 input_add does not do this. The + # function below replicates the pygtk1 functionality. + + # In addition, pygtk maps gtk.input_add to _gobject.io_add_watch, and + # g_io_add_watch() takes different condition bitfields than + # gtk_input_add(). We use g_io_add_watch() here in case pygtk fixes this + # bug. + def input_add(self, source, condition, callback): + if hasattr(source, "fileno"): + # handle python objects + def wrapper(ignored, condition): + return callback(source, condition) + + fileno = source.fileno() + else: + fileno = source + wrapper = callback + return self._glib.io_add_watch( + fileno, + self._glib.PRIORITY_DEFAULT_IDLE, + condition, + wrapper, + ) + + def _ioEventCallback(self, source, condition): + """ + Called by event loop when an I/O event occurs. + """ + log.callWithLogger(source, self._doReadOrWrite, source, source, condition) + return True # True = don't auto-remove the source + + def _add(self, source, primary, other, primaryFlag, otherFlag): + """ + Add the given L{FileDescriptor} for monitoring either for reading or + writing. If the file is already monitored for the other operation, we + delete the previous registration and re-register it for both reading + and writing. + """ + if source in primary: + return + flags = primaryFlag + if source in other: + self._source_remove(self._sources[source]) + flags |= otherFlag + self._sources[source] = self.input_add(source, flags, self._ioEventCallback) + primary.add(source) + + def addReader(self, reader): + """ + Add a L{FileDescriptor} for monitoring of data available to read. + """ + self._add(reader, self._reads, self._writes, self.INFLAGS, self.OUTFLAGS) + + def addWriter(self, writer): + """ + Add a L{FileDescriptor} for monitoring ability to write data. + """ + self._add(writer, self._writes, self._reads, self.OUTFLAGS, self.INFLAGS) + + def getReaders(self): + """ + Retrieve the list of current L{FileDescriptor} monitored for reading. + """ + return list(self._reads) + + def getWriters(self): + """ + Retrieve the list of current L{FileDescriptor} monitored for writing. + """ + return list(self._writes) + + def removeAll(self): + """ + Remove monitoring for all registered L{FileDescriptor}s. + """ + return self._removeAll(self._reads, self._writes) + + def _remove(self, source, primary, other, flags): + """ + Remove monitoring the given L{FileDescriptor} for either reading or + writing. If it's still monitored for the other operation, we + re-register the L{FileDescriptor} for only that operation. + """ + if source not in primary: + return + self._source_remove(self._sources[source]) + primary.remove(source) + if source in other: + self._sources[source] = self.input_add(source, flags, self._ioEventCallback) + else: + self._sources.pop(source) + + def removeReader(self, reader): + """ + Stop monitoring the given L{FileDescriptor} for reading. + """ + self._remove(reader, self._reads, self._writes, self.OUTFLAGS) + + def removeWriter(self, writer): + """ + Stop monitoring the given L{FileDescriptor} for writing. + """ + self._remove(writer, self._writes, self._reads, self.INFLAGS) + + def iterate(self, delay=0): + """ + One iteration of the event loop, for trial's use. + + This is not used for actual reactor runs. + """ + self.runUntilCurrent() + while self._pending(): + self._iteration(0) + + def crash(self): + """ + Crash the reactor. + """ + posixbase.PosixReactorBase.crash(self) + self._crash() + + def stop(self): + """ + Stop the reactor. + """ + posixbase.PosixReactorBase.stop(self) + # The base implementation only sets a flag, to ensure shutting down is + # not reentrant. Unfortunately, this flag is not meaningful to the + # gobject event loop. We therefore call wakeUp() to ensure the event + # loop will call back into Twisted once this iteration is done. This + # will result in self.runUntilCurrent() being called, where the stop + # flag will trigger the actual shutdown process, eventually calling + # crash() which will do the actual gobject event loop shutdown. + self.wakeUp() + + def run(self, installSignalHandlers=True): + """ + Run the reactor. + """ + with _signalGlue(): + self.callWhenRunning(self._reschedule) + self.startRunning(installSignalHandlers=installSignalHandlers) + if self._started: + self._run() + + def callLater(self, *args, **kwargs): + """ + Schedule a C{DelayedCall}. + """ + result = posixbase.PosixReactorBase.callLater(self, *args, **kwargs) + # Make sure we'll get woken up at correct time to handle this new + # scheduled call: + self._reschedule() + return result + + def _reschedule(self): + """ + Schedule a glib timeout for C{_simulate}. + """ + if self._simtag is not None: + self._source_remove(self._simtag) + self._simtag = None + timeout = self.timeout() + if timeout is not None: + self._simtag = self._timeout_add( + int(timeout * 1000), + self._simulate, + priority=self._glib.PRIORITY_DEFAULT_IDLE, + ) + + def _simulate(self): + """ + Run timers, and then reschedule glib timeout for next scheduled event. + """ + self.runUntilCurrent() + self._reschedule() |