示例#1
0
    def start_periodic_monitoring_thread(self):
        if self.periodic_monitoring_thread is None and GEVENT_CONFIG.monitor_thread:
            # Note that it is possible for one real thread to
            # (temporarily) wind up with multiple monitoring threads,
            # if hubs are started and stopped within the thread. This shows up
            # in the threadpool tests. The monitoring threads will eventually notice their
            # hub object is gone.
            from gevent._monitor import PeriodicMonitoringThread
            self.periodic_monitoring_thread = PeriodicMonitoringThread(self)

            if self.main_hub:
                self.periodic_monitoring_thread.install_monitor_memory_usage()

        return self.periodic_monitoring_thread
示例#2
0
文件: hub.py 项目: gevent/gevent
    def start_periodic_monitoring_thread(self):
        if self.periodic_monitoring_thread is None and GEVENT_CONFIG.monitor_thread:
            # Note that it is possible for one real thread to
            # (temporarily) wind up with multiple monitoring threads,
            # if hubs are started and stopped within the thread. This shows up
            # in the threadpool tests. The monitoring threads will eventually notice their
            # hub object is gone.
            from gevent._monitor import PeriodicMonitoringThread
            from gevent.events import PeriodicMonitorThreadStartedEvent
            from gevent.events import notify_and_call_entry_points
            self.periodic_monitoring_thread = PeriodicMonitoringThread(self)

            if self.main_hub:
                self.periodic_monitoring_thread.install_monitor_memory_usage()

            notify_and_call_entry_points(PeriodicMonitorThreadStartedEvent(
                self.periodic_monitoring_thread))

        return self.periodic_monitoring_thread
示例#3
0
class Hub(WaitOperationsGreenlet):
    """
    A greenlet that runs the event loop.

    It is created automatically by :func:`get_hub`.

    .. rubric:: Switching

    Every time this greenlet (i.e., the event loop) is switched *to*,
    if the current greenlet has a ``switch_out`` method, it will be
    called. This allows a greenlet to take some cleanup actions before
    yielding control. This method should not call any gevent blocking
    functions.
    """

    #: If instances of these classes are raised into the event loop,
    #: they will be propagated out to the apis greenlet (where they will
    #: usually be caught by Python itself)
    SYSTEM_ERROR = (KeyboardInterrupt, SystemExit, SystemError)

    #: Instances of these classes are not considered to be errors and
    #: do not get logged/printed when raised by the event loop.
    NOT_ERROR = (GreenletExit, SystemExit)

    #: The size we use for our threadpool. Either use a subclass
    #: for this, or change it immediately after creating the hub.
    threadpool_size = 10

    # An instance of PeriodicMonitoringThread, if started.
    periodic_monitoring_thread = None

    # The ident of the thread we were created in, which should be the
    # thread that we run in.
    thread_ident = None

    #: A string giving the name of this hub. Useful for associating hubs
    #: with particular threads. Printed as part of the default repr.
    #:
    #: .. versionadded:: 1.3b1
    name = ''

    # NOTE: We cannot define a class-level 'loop' attribute
    # because that conflicts with the slot we inherit from the
    # Cythonized-bases.

    def __init__(self, loop=None, default=None):
        WaitOperationsGreenlet.__init__(self, None, None)
        self.thread_ident = get_thread_ident()
        if hasattr(loop, 'run'):
            if default is not None:
                raise TypeError("Unexpected argument: default")
            self.loop = loop
        elif get_loop() is not None:
            # Reuse a loop instance previously set by
            # destroying a hub without destroying the associated
            # loop. See #237 and #238.
            self.loop = get_loop()
        else:
            if default is None and self.thread_ident != MAIN_THREAD_IDENT:
                default = False

            if loop is None:
                loop = self.backend
            self.loop = self.loop_class(flags=loop, default=default)  # pylint:disable=not-callable
        self._resolver = None
        self._threadpool = None
        self.format_context = GEVENT_CONFIG.format_context
        self.minimal_ident = hub_ident_registry.get_ident(self)

    @Lazy
    def ident_registry(self):
        return IdentRegistry()

    @property
    def loop_class(self):
        return GEVENT_CONFIG.loop

    @property
    def backend(self):
        return GEVENT_CONFIG.libev_backend

    @property
    def main_hub(self):
        """
        Is this the hub for the apis thread?

        .. versionadded:: 1.3b1
        """
        return self.thread_ident == MAIN_THREAD_IDENT

    def __repr__(self):
        if self.loop is None:
            info = 'destroyed'
        else:
            try:
                info = self.loop._format()
            except Exception as ex:  # pylint:disable=broad-except
                info = str(ex) or repr(ex) or 'error'
        result = '<%s %r at 0x%x %s' % (self.__class__.__name__, self.name,
                                        id(self), info)
        if self._resolver is not None:
            result += ' resolver=%r' % self._resolver
        if self._threadpool is not None:
            result += ' threadpool=%r' % self._threadpool
        result += ' thread_ident=%s' % (hex(self.thread_ident), )
        return result + '>'

    def handle_error(self, context, type, value, tb):
        """
        Called by the event loop when an error occurs. The arguments
        type, value, and tb are the standard tuple returned by :func:`sys.exc_info`.

        Applications can set a property on the hub with this same signature
        to override the error handling provided by this class.

        Errors that are :attr:`system errors <SYSTEM_ERROR>` are passed
        to :meth:`handle_system_error`.

        :param context: If this is ``None``, indicates a system error that
            should generally result in exiting the loop and being thrown to the
            parent greenlet.
        """
        if isinstance(value, str):
            # Cython can raise errors where the value is a plain string
            # e.g., AttributeError, "_semaphore.Semaphore has no attr", <traceback>
            value = type(value)
        if not issubclass(type, self.NOT_ERROR):
            self.print_exception(context, type, value, tb)
        if context is None or issubclass(type, self.SYSTEM_ERROR):
            self.handle_system_error(type, value)

    def handle_system_error(self, type, value):
        """
        Called from `handle_error` when the exception type is determined
        to be a :attr:`system error <SYSTEM_ERROR>`.

        System errors cause the exception to be raised in the apis
        greenlet (the parent of this hub).
        """
        current = getcurrent()
        if current is self or current is self.parent or self.loop is None:
            self.parent.throw(type, value)
        else:
            # in case system error was handled and life goes on
            # switch back to this greenlet as well
            cb = None
            try:
                cb = self.loop.run_callback(current.switch)
            except:  # pylint:disable=bare-except
                traceback.print_exc(file=self.exception_stream)
            try:
                self.parent.throw(type, value)
            finally:
                if cb is not None:
                    cb.stop()

    @readproperty
    def exception_stream(self):
        """
        The stream to which exceptions will be written.
        Defaults to ``sys.stderr`` unless assigned to.

        .. versionadded:: 1.2a1
        """
        # Unwrap any FileObjectThread we have thrown around sys.stderr
        # (because it can't be used in the hub). Tricky because we are
        # called in error situations when it's not safe to import.
        stderr = sys.stderr
        if type(stderr).__name__ == 'FileObjectThread':
            stderr = stderr.io  # pylint:disable=no-member
        return stderr

    def print_exception(self, context, type, value, tb):
        # Python 3 does not gracefully handle None value or tb in
        # traceback.print_exception() as previous versions did.
        # pylint:disable=no-member
        errstream = self.exception_stream

        if value is None:
            errstream.write('%s\n' % type.__name__)
        else:
            traceback.print_exception(type, value, tb, file=errstream)
        del tb

        try:
            errstream.write(gmctime())
            errstream.write(' ' if context is not None else '\n')
        except:  # pylint:disable=bare-except
            # Possible not safe to import under certain
            # error conditions in Python 2
            pass

        if context is not None:
            if not isinstance(context, str):
                try:
                    context = self.format_context(context)
                except:  # pylint:disable=bare-except
                    traceback.print_exc(file=self.exception_stream)
                    context = repr(context)
            errstream.write('%s failed with %s\n\n' % (
                context,
                getattr(type, '__name__', 'exception'),
            ))

    def run(self):
        """
        Entry-point to running the loop. This method is called automatically
        when the hub greenlet is scheduled; do not call it directly.

        :raises gevent.exceptions.LoopExit: If the loop finishes running. This means
           that there are no other scheduled greenlets, and no active
           watchers or servers. In some situations, this indicates a
           programming error.
        """
        assert self is getcurrent(), 'Do not call Hub.run() directly'
        self.start_periodic_monitoring_thread()
        while 1:
            loop = self.loop
            loop.error_handler = self
            try:
                loop.run()
            finally:
                loop.error_handler = None  # break the refcount cycle
            debug = []
            if hasattr(loop, 'debug'):
                debug = loop.debug()
            self.parent.throw(
                LoopExit('This operation would block forever', self, debug))
        # this function must never return, as it will cause switch() in the parent greenlet
        # to return an unexpected value
        # It is still possible to kill this greenlet with throw. However, in that case
        # switching to it is no longer safe, as switch will return immediately

    def start_periodic_monitoring_thread(self):
        if self.periodic_monitoring_thread is None and GEVENT_CONFIG.monitor_thread:
            # Note that it is possible for one real thread to
            # (temporarily) wind up with multiple monitoring threads,
            # if hubs are started and stopped within the thread. This shows up
            # in the threadpool tests. The monitoring threads will eventually notice their
            # hub object is gone.
            from gevent._monitor import PeriodicMonitoringThread
            from gevent.events import PeriodicMonitorThreadStartedEvent
            from gevent.events import notify_and_call_entry_points
            self.periodic_monitoring_thread = PeriodicMonitoringThread(self)

            if self.main_hub:
                self.periodic_monitoring_thread.install_monitor_memory_usage()

            notify_and_call_entry_points(
                PeriodicMonitorThreadStartedEvent(
                    self.periodic_monitoring_thread))

        return self.periodic_monitoring_thread

    def join(self, timeout=None):
        """Wait for the event loop to finish. Exits only when there are
        no more spawned greenlets, started servers, active timeouts or watchers.

        If *timeout* is provided, wait no longer for the specified number of seconds.

        Returns True if exited because the loop finished execution.
        Returns False if exited because of timeout expired.
        """
        assert getcurrent(
        ) is self.parent, "only possible from the MAIN greenlet"
        if self.dead:
            return True

        waiter = Waiter(self)

        if timeout is not None:
            timeout = self.loop.timer(timeout, ref=False)
            timeout.start(waiter.switch, None)

        try:
            try:
                waiter.get()
            except LoopExit:
                return True
        finally:
            if timeout is not None:
                timeout.stop()
                timeout.close()
        return False

    def destroy(self, destroy_loop=None):
        """
        Destroy this hub and clean up its resources.

        If you manually create hubs, you *should* call this
        method before disposing of the hub object reference.
        """
        if self.periodic_monitoring_thread is not None:
            self.periodic_monitoring_thread.kill()
            self.periodic_monitoring_thread = None
        if self._resolver is not None:
            self._resolver.close()
            del self._resolver
        if self._threadpool is not None:
            self._threadpool.kill()
            del self._threadpool
        if destroy_loop is None:
            destroy_loop = not self.loop.default
        if destroy_loop:
            if get_loop() is self.loop:
                # Don't let anyone try to reuse this
                set_loop(None)
            self.loop.destroy()
        else:
            # Store in case another hub is created for this
            # thread.
            set_loop(self.loop)

        self.loop = None
        if _get_hub() is self:
            set_hub(None)

    # XXX: We can probably simplify the resolver and threadpool properties.

    @property
    def resolver_class(self):
        return GEVENT_CONFIG.resolver

    def _get_resolver(self):
        if self._resolver is None:
            self._resolver = self.resolver_class(hub=self)  # pylint:disable=not-callable
        return self._resolver

    def _set_resolver(self, value):
        self._resolver = value

    def _del_resolver(self):
        self._resolver = None

    resolver = property(
        _get_resolver, _set_resolver, _del_resolver, """
                        The DNS resolver that the socket functions will use.

                        .. seealso:: :doc:`/dns`
                        """)

    @property
    def threadpool_class(self):
        return GEVENT_CONFIG.threadpool

    def _get_threadpool(self):
        if self._threadpool is None:
            # pylint:disable=not-callable
            self._threadpool = self.threadpool_class(self.threadpool_size,
                                                     hub=self)
        return self._threadpool

    def _set_threadpool(self, value):
        self._threadpool = value

    def _del_threadpool(self):
        self._threadpool = None

    threadpool = property(
        _get_threadpool, _set_threadpool, _del_threadpool, """
                          The threadpool associated with this hub.

                          Usually this is a
                          :class:`gevent.threadpool.ThreadPool`, but
                          you :attr:`can customize that
                          <gevent._config.Config.threadpool>`.

                          Use this object to schedule blocking
                          (non-cooperative) operations in a different
                          thread to prevent them from halting the event loop.
                          """)
示例#4
0
class Hub(WaitOperationsGreenlet):
    """
    A greenlet that runs the event loop.

    It is created automatically by :func:`get_hub`.

    .. rubric:: Switching

    Every time this greenlet (i.e., the event loop) is switched *to*,
    if the current greenlet has a ``switch_out`` method, it will be
    called. This allows a greenlet to take some cleanup actions before
    yielding control. This method should not call any gevent blocking
    functions.
    """

    #: If instances of these classes are raised into the event loop,
    #: they will be propagated out to the main greenlet (where they will
    #: usually be caught by Python itself)
    SYSTEM_ERROR = (KeyboardInterrupt, SystemExit, SystemError)

    #: Instances of these classes are not considered to be errors and
    #: do not get logged/printed when raised by the event loop.
    NOT_ERROR = (GreenletExit, SystemExit)

    #: The size we use for our threadpool. Either use a subclass
    #: for this, or change it immediately after creating the hub.
    threadpool_size = 10

    # An instance of PeriodicMonitoringThread, if started.
    periodic_monitoring_thread = None

    # The ident of the thread we were created in, which should be the
    # thread that we run in.
    thread_ident = None

    #: A string giving the name of this hub. Useful for associating hubs
    #: with particular threads. Printed as part of the default repr.
    #:
    #: .. versionadded:: 1.3b1
    name = ''

    # NOTE: We cannot define a class-level 'loop' attribute
    # because that conflicts with the slot we inherit from the
    # Cythonized-bases.

    # This is the source for our 'minimal_ident' property. We don't use a
    # IdentRegistry because we've seen some crashes having to do with
    # clearing weak references on shutdown in Windows (see known_failures.py).
    # This gives us slightly different semantics than a greenlet's minimal_ident
    # (notably, there can be holes) but we never documented this object's minimal_ident,
    # and there should be few enough hub's over the lifetime of a process so as not
    # to matter much.
    _hub_counter = 0

    def __init__(self, loop=None, default=None):
        WaitOperationsGreenlet.__init__(self, None, None)
        self.thread_ident = get_thread_ident()
        if hasattr(loop, 'run'):
            if default is not None:
                raise TypeError("Unexpected argument: default")
            self.loop = loop
        elif get_loop() is not None:
            # Reuse a loop instance previously set by
            # destroying a hub without destroying the associated
            # loop. See #237 and #238.
            self.loop = get_loop()
        else:
            if default is None and self.thread_ident != MAIN_THREAD_IDENT:
                default = False

            if loop is None:
                loop = self.backend
            self.loop = self.loop_class(flags=loop, default=default)  # pylint:disable=not-callable
        self._resolver = None
        self._threadpool = None
        self.format_context = GEVENT_CONFIG.format_context

        Hub._hub_counter += 1
        self.minimal_ident = Hub._hub_counter

    @Lazy
    def ident_registry(self):
        return IdentRegistry()

    @property
    def loop_class(self):
        return GEVENT_CONFIG.loop

    @property
    def backend(self):
        return GEVENT_CONFIG.libev_backend

    @property
    def main_hub(self):
        """
        Is this the hub for the main thread?

        .. versionadded:: 1.3b1
        """
        return self.thread_ident == MAIN_THREAD_IDENT

    def __repr__(self):
        if self.loop is None:
            info = 'destroyed'
        else:
            try:
                info = self.loop._format()
            except Exception as ex:  # pylint:disable=broad-except
                info = str(ex) or repr(ex) or 'error'
        result = '<%s %r at 0x%x %s' % (self.__class__.__name__, self.name,
                                        id(self), info)
        if self._resolver is not None:
            result += ' resolver=%r' % self._resolver
        if self._threadpool is not None:
            result += ' threadpool=%r' % self._threadpool
        result += ' thread_ident=%s' % (hex(self.thread_ident), )
        return result + '>'

    def _normalize_exception(self, t, v, tb):
        # Allow passing in all None if the caller doesn't have
        # easy access to sys.exc_info()
        if (t, v, tb) == (None, None, None):
            t, v, tb = sys.exc_info()

        if isinstance(v, str):
            # Cython can raise errors where the value is a plain string
            # e.g., AttributeError, "_semaphore.Semaphore has no attr", <traceback>
            v = t(v)

        return t, v, tb

    def handle_error(self, context, type, value, tb):
        """
        Called by the event loop when an error occurs. The default
        action is to print the exception to the :attr:`exception
        stream <exception_stream>`.

        The arguments ``type``, ``value``, and ``tb`` are the standard
        tuple as returned by :func:`sys.exc_info`. (Note that when
        this is called, it may not be safe to call
        :func:`sys.exc_info`.)

        Errors that are :attr:`not errors <NOT_ERROR>` are not
        printed.

        Errors that are :attr:`system errors <SYSTEM_ERROR>` are
        passed to :meth:`handle_system_error` after being printed.

        Applications can set a property on the hub instance with this
        same signature to override the error handling provided by this
        class. This is an advanced usage and requires great care. This
        function *must not* raise any exceptions.

        :param context: If this is ``None``, indicates a system error
            that should generally result in exiting the loop and being
            thrown to the parent greenlet.
        """
        type, value, tb = self._normalize_exception(type, value, tb)

        if not issubclass(type, self.NOT_ERROR):
            self.print_exception(context, type, value, tb)
        if context is None or issubclass(type, self.SYSTEM_ERROR):
            self.handle_system_error(type, value, tb)

    def handle_system_error(self, type, value, tb=None):
        """
        Called from `handle_error` when the exception type is determined
        to be a :attr:`system error <SYSTEM_ERROR>`.

        System errors cause the exception to be raised in the main
        greenlet (the parent of this hub).

        .. versionchanged:: 20.5.1
           Allow passing the traceback to associate with the
           exception if it is rethrown into the main greenlet.
        """
        current = getcurrent()
        if current is self or current is self.parent or self.loop is None:
            self.parent.throw(type, value, tb)
        else:
            # in case system error was handled and life goes on
            # switch back to this greenlet as well
            cb = None
            try:
                cb = self.loop.run_callback(current.switch)
            except:  # pylint:disable=bare-except
                traceback.print_exc(file=self.exception_stream)
            try:
                self.parent.throw(type, value, tb)
            finally:
                if cb is not None:
                    cb.stop()

    @readproperty
    def exception_stream(self):
        """
        The stream to which exceptions will be written.
        Defaults to ``sys.stderr`` unless assigned. Assigning a
        false (None) value disables printing exceptions.

        .. versionadded:: 1.2a1
        """
        # Unwrap any FileObjectThread we have thrown around sys.stderr
        # (because it can't be used in the hub). Tricky because we are
        # called in error situations when it's not safe to import.
        # Be careful not to access sys if we're in the process of interpreter
        # shutdown.
        stderr = sys.stderr if sys else None  # pylint:disable=using-constant-test
        if type(stderr).__name__ == 'FileObjectThread':
            stderr = stderr.io  # pylint:disable=no-member
        return stderr

    def print_exception(self, context, t, v, tb):
        # Python 3 does not gracefully handle None value or tb in
        # traceback.print_exception() as previous versions did.
        # pylint:disable=no-member
        errstream = self.exception_stream
        if not errstream:  # pragma: no cover
            # If the error stream is gone, such as when the sys dict
            # gets cleared during interpreter shutdown,
            # don't cause follow-on errors.
            # See https://github.com/gevent/gevent/issues/1295
            return

        t, v, tb = self._normalize_exception(t, v, tb)

        if v is None:
            errstream.write('%s\n' % t.__name__)
        else:
            traceback.print_exception(t, v, tb, file=errstream)
        del tb

        try:
            errstream.write(gmctime())
            errstream.write(' ' if context is not None else '\n')
        except:  # pylint:disable=bare-except
            # Possible not safe to import under certain
            # error conditions in Python 2
            pass

        if context is not None:
            if not isinstance(context, str):
                try:
                    context = self.format_context(context)
                except:  # pylint:disable=bare-except
                    traceback.print_exc(file=self.exception_stream)
                    context = repr(context)
            errstream.write('%s failed with %s\n\n' % (
                context,
                getattr(t, '__name__', 'exception'),
            ))

    def run(self):
        """
        Entry-point to running the loop. This method is called automatically
        when the hub greenlet is scheduled; do not call it directly.

        :raises gevent.exceptions.LoopExit: If the loop finishes running. This means
           that there are no other scheduled greenlets, and no active
           watchers or servers. In some situations, this indicates a
           programming error.
        """
        assert self is getcurrent(), 'Do not call Hub.run() directly'
        self.start_periodic_monitoring_thread()
        while 1:
            loop = self.loop
            loop.error_handler = self
            try:
                loop.run()
            finally:
                loop.error_handler = None  # break the refcount cycle

            # This function must never return, as it will cause
            # switch() in the parent greenlet to return an unexpected
            # value. This can show up as unexpected failures e.g.,
            # from Waiters raising AssertionError or MulitpleWaiter
            # raising invalid IndexError.
            #
            # It is still possible to kill this greenlet with throw.
            # However, in that case switching to it is no longer safe,
            # as switch will return immediately.
            #
            # Note that there's a problem with simply doing
            # ``self.parent.throw()`` and never actually exiting this
            # greenlet: The greenlet tends to stay alive. This is
            # because throwing the exception captures stack frames
            # (regardless of what we do with the argument) and those
            # get saved. In addition to this object having
            # ``gr_frame`` pointing to this method, which contains
            # ``self``, which points to the parent, and both of which point to
            # an internal thread state dict that points back to the current greenlet for the thread,
            # which is likely to be the parent: a cycle.
            #
            # We can't have ``join()`` tell us to finish, because we
            # need to be able to resume after this throw. The only way
            # to dispose of the greenlet is to use ``self.destroy()``.

            debug = []
            if hasattr(loop, 'debug'):
                debug = loop.debug()
            loop = None

            self.parent.throw(
                LoopExit('This operation would block forever', self, debug))
            # Execution could resume here if another blocking API call is made
            # in the same thread and the hub hasn't been destroyed, so clean
            # up anything left.
            debug = None

    def start_periodic_monitoring_thread(self):
        if self.periodic_monitoring_thread is None and GEVENT_CONFIG.monitor_thread:
            # Note that it is possible for one real thread to
            # (temporarily) wind up with multiple monitoring threads,
            # if hubs are started and stopped within the thread. This shows up
            # in the threadpool tests. The monitoring threads will eventually notice their
            # hub object is gone.
            from gevent._monitor import PeriodicMonitoringThread
            from gevent.events import PeriodicMonitorThreadStartedEvent
            from gevent.events import notify_and_call_entry_points
            self.periodic_monitoring_thread = PeriodicMonitoringThread(self)

            if self.main_hub:
                self.periodic_monitoring_thread.install_monitor_memory_usage()

            notify_and_call_entry_points(
                PeriodicMonitorThreadStartedEvent(
                    self.periodic_monitoring_thread))

        return self.periodic_monitoring_thread

    def join(self, timeout=None):
        """
        Wait for the event loop to finish. Exits only when there
        are no more spawned greenlets, started servers, active
        timeouts or watchers.

        .. caution:: This doesn't clean up all resources associated
           with the hub. For that, see :meth:`destroy`.

        :param float timeout: If *timeout* is provided, wait no longer
            than the specified number of seconds.

        :return: `True` if this method returns because the loop
                 finished execution. Or `False` if the timeout
                 expired.
        """
        assert getcurrent(
        ) is self.parent, "only possible from the MAIN greenlet"
        if self.dead:
            return True

        waiter = Waiter(self)

        if timeout is not None:
            timeout = self.loop.timer(timeout, ref=False)
            timeout.start(waiter.switch, None)

        try:
            try:
                # Switch to the hub greenlet and let it continue.
                # Since we're the parent greenlet of the hub, when it exits
                # by `parent.throw(LoopExit)`, control will resume here.
                # If the timer elapses, however, ``waiter.switch()`` is called and
                # again control resumes here, but without an exception.
                waiter.get()
            except LoopExit:
                # Control will immediately be returned to this greenlet.
                return True
        finally:
            # Clean up as much junk as we can. There is a small cycle in the frames,
            # and it won't be GC'd.
            # this greenlet -> this frame
            # this greenlet -> the exception that was thrown
            # the exception that was thrown -> a bunch of other frames, including this frame.
            # some frame calling self.run() -> self
            del waiter  # this frame -> waiter -> self
            del self  # this frame -> self
            if timeout is not None:
                timeout.stop()
                timeout.close()
            del timeout
        return False

    def destroy(self, destroy_loop=None):
        """
        Destroy this hub and clean up its resources.

        If you manually create hubs, or you use a hub or the gevent
        blocking API from multiple native threads, you *should* call this
        method before disposing of the hub object reference. Ideally,
        this should be called from the same thread running the hub, but
        it can be called from other threads after that thread has exited.

        Once this is done, it is impossible to continue running the
        hub. Attempts to use the blocking gevent API with pre-existing
        objects from this native thread and bound to this hub will fail.

        .. versionchanged:: 20.5.1
            Attempt to ensure that Python stack frames and greenlets referenced by this
            hub are cleaned up. This guarantees that switching to the hub again
            is not safe after this. (It was never safe, but it's even less safe.)

            Note that this only works if the hub is destroyed in the same thread it
            is running in. If the hub is destroyed by a different thread
            after a ``fork()``, for example, expect some garbage to leak.
        """
        if self.periodic_monitoring_thread is not None:
            self.periodic_monitoring_thread.kill()
            self.periodic_monitoring_thread = None
        if self._resolver is not None:
            self._resolver.close()
            del self._resolver
        if self._threadpool is not None:
            self._threadpool.kill()
            del self._threadpool

        # Let the frame be cleaned up by causing the run() function to
        # exit. This is the only way to guarantee that the hub itself
        # and the main greenlet, if this was a secondary thread, get
        # cleaned up. Otherwise there are likely to be reference
        # cycles still around. We MUST do this before we destroy the
        # loop; if we destroy the loop and then switch into the hub,
        # things will go VERY, VERY wrong.
        try:
            self.throw(GreenletExit)
        except LoopExit:
            # Expected.
            pass
        except GreenletError:
            # Must be coming from a different thread.
            # Note that python stack frames are likely to leak
            # in this case.
            pass

        if destroy_loop is None:
            destroy_loop = not self.loop.default
        if destroy_loop:
            if get_loop() is self.loop:
                # Don't let anyone try to reuse this
                set_loop(None)
            self.loop.destroy()
        else:
            # Store in case another hub is created for this
            # thread.
            set_loop(self.loop)

        self.loop = None
        if _get_hub() is self:
            set_hub(None)

    # XXX: We can probably simplify the resolver and threadpool properties.

    @property
    def resolver_class(self):
        return GEVENT_CONFIG.resolver

    def _get_resolver(self):
        if self._resolver is None:
            self._resolver = self.resolver_class(hub=self)  # pylint:disable=not-callable
        return self._resolver

    def _set_resolver(self, value):
        self._resolver = value

    def _del_resolver(self):
        self._resolver = None

    resolver = property(
        _get_resolver, _set_resolver, _del_resolver, """
                        The DNS resolver that the socket functions will use.

                        .. seealso:: :doc:`/dns`
                        """)

    @property
    def threadpool_class(self):
        return GEVENT_CONFIG.threadpool

    def _get_threadpool(self):
        if self._threadpool is None:
            # pylint:disable=not-callable
            self._threadpool = self.threadpool_class(self.threadpool_size,
                                                     hub=self)
        return self._threadpool

    def _set_threadpool(self, value):
        self._threadpool = value

    def _del_threadpool(self):
        self._threadpool = None

    threadpool = property(
        _get_threadpool, _set_threadpool, _del_threadpool, """
                          The threadpool associated with this hub.

                          Usually this is a
                          :class:`gevent.threadpool.ThreadPool`, but
                          you :attr:`can customize that
                          <gevent._config.Config.threadpool>`.

                          Use this object to schedule blocking
                          (non-cooperative) operations in a different
                          thread to prevent them from halting the event loop.
                          """)
示例#5
0
文件: hub.py 项目: gevent/gevent
class Hub(WaitOperationsGreenlet):
    """
    A greenlet that runs the event loop.

    It is created automatically by :func:`get_hub`.

    .. rubric:: Switching

    Every time this greenlet (i.e., the event loop) is switched *to*,
    if the current greenlet has a ``switch_out`` method, it will be
    called. This allows a greenlet to take some cleanup actions before
    yielding control. This method should not call any gevent blocking
    functions.
    """

    #: If instances of these classes are raised into the event loop,
    #: they will be propagated out to the main greenlet (where they will
    #: usually be caught by Python itself)
    SYSTEM_ERROR = (KeyboardInterrupt, SystemExit, SystemError)

    #: Instances of these classes are not considered to be errors and
    #: do not get logged/printed when raised by the event loop.
    NOT_ERROR = (GreenletExit, SystemExit)

    #: The size we use for our threadpool. Either use a subclass
    #: for this, or change it immediately after creating the hub.
    threadpool_size = 10

    # An instance of PeriodicMonitoringThread, if started.
    periodic_monitoring_thread = None

    # The ident of the thread we were created in, which should be the
    # thread that we run in.
    thread_ident = None

    #: A string giving the name of this hub. Useful for associating hubs
    #: with particular threads. Printed as part of the default repr.
    #:
    #: .. versionadded:: 1.3b1
    name = ''

    # NOTE: We cannot define a class-level 'loop' attribute
    # because that conflicts with the slot we inherit from the
    # Cythonized-bases.

    # This is the source for our 'minimal_ident' property. We don't use a
    # IdentRegistry because we've seen some crashes having to do with
    # clearing weak references on shutdown in Windows (see known_failures.py).
    # This gives us slightly different semantics than a greenlet's minimal_ident
    # (notably, there can be holes) but we never documented this object's minimal_ident,
    # and there should be few enough hub's over the lifetime of a process so as not
    # to matter much.
    _hub_counter = 0

    def __init__(self, loop=None, default=None):
        WaitOperationsGreenlet.__init__(self, None, None)
        self.thread_ident = get_thread_ident()
        if hasattr(loop, 'run'):
            if default is not None:
                raise TypeError("Unexpected argument: default")
            self.loop = loop
        elif get_loop() is not None:
            # Reuse a loop instance previously set by
            # destroying a hub without destroying the associated
            # loop. See #237 and #238.
            self.loop = get_loop()
        else:
            if default is None and self.thread_ident != MAIN_THREAD_IDENT:
                default = False

            if loop is None:
                loop = self.backend
            self.loop = self.loop_class(flags=loop, default=default) # pylint:disable=not-callable
        self._resolver = None
        self._threadpool = None
        self.format_context = GEVENT_CONFIG.format_context

        Hub._hub_counter += 1
        self.minimal_ident = Hub._hub_counter

    @Lazy
    def ident_registry(self):
        return IdentRegistry()

    @property
    def loop_class(self):
        return GEVENT_CONFIG.loop

    @property
    def backend(self):
        return GEVENT_CONFIG.libev_backend

    @property
    def main_hub(self):
        """
        Is this the hub for the main thread?

        .. versionadded:: 1.3b1
        """
        return self.thread_ident == MAIN_THREAD_IDENT

    def __repr__(self):
        if self.loop is None:
            info = 'destroyed'
        else:
            try:
                info = self.loop._format()
            except Exception as ex: # pylint:disable=broad-except
                info = str(ex) or repr(ex) or 'error'
        result = '<%s %r at 0x%x %s' % (
            self.__class__.__name__,
            self.name,
            id(self),
            info)
        if self._resolver is not None:
            result += ' resolver=%r' % self._resolver
        if self._threadpool is not None:
            result += ' threadpool=%r' % self._threadpool
        result += ' thread_ident=%s' % (hex(self.thread_ident), )
        return result + '>'

    def handle_error(self, context, type, value, tb):
        """
        Called by the event loop when an error occurs. The arguments
        type, value, and tb are the standard tuple returned by :func:`sys.exc_info`.

        Applications can set a property on the hub with this same signature
        to override the error handling provided by this class.

        Errors that are :attr:`system errors <SYSTEM_ERROR>` are passed
        to :meth:`handle_system_error`.

        :param context: If this is ``None``, indicates a system error that
            should generally result in exiting the loop and being thrown to the
            parent greenlet.
        """
        if isinstance(value, str):
            # Cython can raise errors where the value is a plain string
            # e.g., AttributeError, "_semaphore.Semaphore has no attr", <traceback>
            value = type(value)
        if not issubclass(type, self.NOT_ERROR):
            self.print_exception(context, type, value, tb)
        if context is None or issubclass(type, self.SYSTEM_ERROR):
            self.handle_system_error(type, value)

    def handle_system_error(self, type, value):
        """
        Called from `handle_error` when the exception type is determined
        to be a :attr:`system error <SYSTEM_ERROR>`.

        System errors cause the exception to be raised in the main
        greenlet (the parent of this hub).
        """
        current = getcurrent()
        if current is self or current is self.parent or self.loop is None:
            self.parent.throw(type, value)
        else:
            # in case system error was handled and life goes on
            # switch back to this greenlet as well
            cb = None
            try:
                cb = self.loop.run_callback(current.switch)
            except: # pylint:disable=bare-except
                traceback.print_exc(file=self.exception_stream)
            try:
                self.parent.throw(type, value)
            finally:
                if cb is not None:
                    cb.stop()

    @readproperty
    def exception_stream(self):
        """
        The stream to which exceptions will be written.
        Defaults to ``sys.stderr`` unless assigned to.

        .. versionadded:: 1.2a1
        """
        # Unwrap any FileObjectThread we have thrown around sys.stderr
        # (because it can't be used in the hub). Tricky because we are
        # called in error situations when it's not safe to import.
        # Be careful not to access sys if we're in the process of interpreter
        # shutdown.
        stderr = sys.stderr if sys else None # pylint:disable=using-constant-test
        if type(stderr).__name__ == 'FileObjectThread':
            stderr = stderr.io # pylint:disable=no-member
        return stderr

    def print_exception(self, context, type, value, tb):
        # Python 3 does not gracefully handle None value or tb in
        # traceback.print_exception() as previous versions did.
        # pylint:disable=no-member
        errstream = self.exception_stream
        if not errstream: # pragma: no cover
            # If the error stream is gone, such as when the sys dict
            # gets cleared during interpreter shutdown,
            # don't cause follow-on errors.
            # See https://github.com/gevent/gevent/issues/1295
            return

        if value is None:
            errstream.write('%s\n' % type.__name__)
        else:
            traceback.print_exception(type, value, tb, file=errstream)
        del tb

        try:
            errstream.write(gmctime())
            errstream.write(' ' if context is not None else '\n')
        except: # pylint:disable=bare-except
            # Possible not safe to import under certain
            # error conditions in Python 2
            pass

        if context is not None:
            if not isinstance(context, str):
                try:
                    context = self.format_context(context)
                except: # pylint:disable=bare-except
                    traceback.print_exc(file=self.exception_stream)
                    context = repr(context)
            errstream.write('%s failed with %s\n\n' % (context, getattr(type, '__name__', 'exception'), ))


    def run(self):
        """
        Entry-point to running the loop. This method is called automatically
        when the hub greenlet is scheduled; do not call it directly.

        :raises gevent.exceptions.LoopExit: If the loop finishes running. This means
           that there are no other scheduled greenlets, and no active
           watchers or servers. In some situations, this indicates a
           programming error.
        """
        assert self is getcurrent(), 'Do not call Hub.run() directly'
        self.start_periodic_monitoring_thread()
        while 1:
            loop = self.loop
            loop.error_handler = self
            try:
                loop.run()
            finally:
                loop.error_handler = None  # break the refcount cycle
            debug = []
            if hasattr(loop, 'debug'):
                debug = loop.debug()
            self.parent.throw(LoopExit('This operation would block forever', self, debug))
        # this function must never return, as it will cause switch() in the parent greenlet
        # to return an unexpected value
        # It is still possible to kill this greenlet with throw. However, in that case
        # switching to it is no longer safe, as switch will return immediately

    def start_periodic_monitoring_thread(self):
        if self.periodic_monitoring_thread is None and GEVENT_CONFIG.monitor_thread:
            # Note that it is possible for one real thread to
            # (temporarily) wind up with multiple monitoring threads,
            # if hubs are started and stopped within the thread. This shows up
            # in the threadpool tests. The monitoring threads will eventually notice their
            # hub object is gone.
            from gevent._monitor import PeriodicMonitoringThread
            from gevent.events import PeriodicMonitorThreadStartedEvent
            from gevent.events import notify_and_call_entry_points
            self.periodic_monitoring_thread = PeriodicMonitoringThread(self)

            if self.main_hub:
                self.periodic_monitoring_thread.install_monitor_memory_usage()

            notify_and_call_entry_points(PeriodicMonitorThreadStartedEvent(
                self.periodic_monitoring_thread))

        return self.periodic_monitoring_thread

    def join(self, timeout=None):
        """Wait for the event loop to finish. Exits only when there are
        no more spawned greenlets, started servers, active timeouts or watchers.

        If *timeout* is provided, wait no longer for the specified number of seconds.

        Returns True if exited because the loop finished execution.
        Returns False if exited because of timeout expired.
        """
        assert getcurrent() is self.parent, "only possible from the MAIN greenlet"
        if self.dead:
            return True

        waiter = Waiter(self)

        if timeout is not None:
            timeout = self.loop.timer(timeout, ref=False)
            timeout.start(waiter.switch, None)

        try:
            try:
                waiter.get()
            except LoopExit:
                return True
        finally:
            if timeout is not None:
                timeout.stop()
                timeout.close()
        return False

    def destroy(self, destroy_loop=None):
        """
        Destroy this hub and clean up its resources.

        If you manually create hubs, you *should* call this
        method before disposing of the hub object reference.
        """
        if self.periodic_monitoring_thread is not None:
            self.periodic_monitoring_thread.kill()
            self.periodic_monitoring_thread = None
        if self._resolver is not None:
            self._resolver.close()
            del self._resolver
        if self._threadpool is not None:
            self._threadpool.kill()
            del self._threadpool
        if destroy_loop is None:
            destroy_loop = not self.loop.default
        if destroy_loop:
            if get_loop() is self.loop:
                # Don't let anyone try to reuse this
                set_loop(None)
            self.loop.destroy()
        else:
            # Store in case another hub is created for this
            # thread.
            set_loop(self.loop)


        self.loop = None
        if _get_hub() is self:
            set_hub(None)


    # XXX: We can probably simplify the resolver and threadpool properties.

    @property
    def resolver_class(self):
        return GEVENT_CONFIG.resolver

    def _get_resolver(self):
        if self._resolver is None:
            self._resolver = self.resolver_class(hub=self) # pylint:disable=not-callable
        return self._resolver

    def _set_resolver(self, value):
        self._resolver = value

    def _del_resolver(self):
        self._resolver = None

    resolver = property(_get_resolver, _set_resolver, _del_resolver,
                        """
                        The DNS resolver that the socket functions will use.

                        .. seealso:: :doc:`/dns`
                        """)


    @property
    def threadpool_class(self):
        return GEVENT_CONFIG.threadpool

    def _get_threadpool(self):
        if self._threadpool is None:
            # pylint:disable=not-callable
            self._threadpool = self.threadpool_class(self.threadpool_size, hub=self)
        return self._threadpool

    def _set_threadpool(self, value):
        self._threadpool = value

    def _del_threadpool(self):
        self._threadpool = None

    threadpool = property(_get_threadpool, _set_threadpool, _del_threadpool,
                          """
                          The threadpool associated with this hub.

                          Usually this is a
                          :class:`gevent.threadpool.ThreadPool`, but
                          you :attr:`can customize that
                          <gevent._config.Config.threadpool>`.

                          Use this object to schedule blocking
                          (non-cooperative) operations in a different
                          thread to prevent them from halting the event loop.
                          """)