mit neuen venv und exe-Files
This commit is contained in:
909
venv3_12/Lib/site-packages/gevent/hub.py
Normal file
909
venv3_12/Lib/site-packages/gevent/hub.py
Normal file
@@ -0,0 +1,909 @@
|
||||
# Copyright (c) 2009-2015 Denis Bilenko. See LICENSE for details.
|
||||
"""
|
||||
Event-loop hub.
|
||||
"""
|
||||
from __future__ import absolute_import, print_function
|
||||
# XXX: FIXME: Refactor to make this smaller
|
||||
# pylint:disable=too-many-lines
|
||||
from functools import partial as _functools_partial
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
||||
from greenlet import greenlet as RawGreenlet
|
||||
from greenlet import getcurrent
|
||||
from greenlet import GreenletExit
|
||||
from greenlet import error as GreenletError
|
||||
|
||||
__all__ = [
|
||||
'getcurrent',
|
||||
'GreenletExit',
|
||||
'spawn_raw',
|
||||
'sleep',
|
||||
'kill',
|
||||
'signal',
|
||||
'reinit',
|
||||
'get_hub',
|
||||
'Hub',
|
||||
'Waiter',
|
||||
]
|
||||
|
||||
from gevent._config import config as GEVENT_CONFIG
|
||||
from gevent._compat import thread_mod_name
|
||||
from gevent._compat import reraise
|
||||
from gevent._util import readproperty
|
||||
from gevent._util import Lazy
|
||||
from gevent._util import gmctime
|
||||
from gevent._ident import IdentRegistry
|
||||
|
||||
from gevent._hub_local import get_hub
|
||||
from gevent._hub_local import get_loop
|
||||
from gevent._hub_local import set_hub
|
||||
from gevent._hub_local import set_loop
|
||||
from gevent._hub_local import get_hub_if_exists as _get_hub
|
||||
from gevent._hub_local import get_hub_noargs as _get_hub_noargs
|
||||
from gevent._hub_local import set_default_hub_class
|
||||
|
||||
from gevent._greenlet_primitives import TrackedRawGreenlet
|
||||
from gevent._hub_primitives import WaitOperationsGreenlet
|
||||
|
||||
# Export
|
||||
from gevent import _hub_primitives
|
||||
wait = _hub_primitives.wait_on_objects
|
||||
iwait = _hub_primitives.iwait_on_objects
|
||||
|
||||
|
||||
from gevent.exceptions import LoopExit
|
||||
from gevent.exceptions import HubDestroyed
|
||||
|
||||
from gevent._waiter import Waiter
|
||||
|
||||
|
||||
# Need the real get_ident. We're imported early enough (by gevent/__init__.py)
|
||||
# that we can be sure nothing is monkey patched yet.
|
||||
get_thread_ident = __import__(thread_mod_name).get_ident
|
||||
MAIN_THREAD_IDENT = get_thread_ident() # XXX: Assuming import is done on the main thread.
|
||||
|
||||
|
||||
|
||||
def spawn_raw(function, *args, **kwargs):
|
||||
"""
|
||||
Create a new :class:`greenlet.greenlet` object and schedule it to
|
||||
run ``function(*args, **kwargs)``.
|
||||
|
||||
This returns a raw :class:`~greenlet.greenlet` which does not have all the useful
|
||||
methods that :class:`gevent.Greenlet` has. Typically, applications
|
||||
should prefer :func:`~gevent.spawn`, but this method may
|
||||
occasionally be useful as an optimization if there are many
|
||||
greenlets involved.
|
||||
|
||||
.. versionchanged:: 1.1a3
|
||||
Verify that ``function`` is callable, raising a TypeError if not. Previously,
|
||||
the spawned greenlet would have failed the first time it was switched to.
|
||||
|
||||
.. versionchanged:: 1.1b1
|
||||
If *function* is not callable, immediately raise a :exc:`TypeError`
|
||||
instead of spawning a greenlet that will raise an uncaught TypeError.
|
||||
|
||||
.. versionchanged:: 1.1rc2
|
||||
Accept keyword arguments for ``function`` as previously (incorrectly)
|
||||
documented. Note that this may incur an additional expense.
|
||||
|
||||
.. versionchanged:: 1.3a2
|
||||
Populate the ``spawning_greenlet`` and ``spawn_tree_locals``
|
||||
attributes of the returned greenlet.
|
||||
|
||||
.. versionchanged:: 1.3b1
|
||||
*Only* populate ``spawning_greenlet`` and ``spawn_tree_locals``
|
||||
if ``GEVENT_TRACK_GREENLET_TREE`` is enabled (the default). If not enabled,
|
||||
those attributes will not be set.
|
||||
|
||||
.. versionchanged:: 1.5a3
|
||||
The returned greenlet always has a *loop* attribute matching the
|
||||
current hub's loop. This helps it work better with more gevent APIs.
|
||||
"""
|
||||
if not callable(function):
|
||||
raise TypeError("function must be callable")
|
||||
|
||||
# The hub is always the parent.
|
||||
hub = _get_hub_noargs()
|
||||
loop = hub.loop
|
||||
|
||||
factory = TrackedRawGreenlet if GEVENT_CONFIG.track_greenlet_tree else RawGreenlet
|
||||
|
||||
# The callback class object that we use to run this doesn't
|
||||
# accept kwargs (and those objects are heavily used, as well as being
|
||||
# implemented twice in core.ppyx and corecffi.py) so do it with a partial
|
||||
if kwargs:
|
||||
function = _functools_partial(function, *args, **kwargs)
|
||||
g = factory(function, hub)
|
||||
loop.run_callback(g.switch)
|
||||
else:
|
||||
g = factory(function, hub)
|
||||
loop.run_callback(g.switch, *args)
|
||||
g.loop = hub.loop
|
||||
return g
|
||||
|
||||
|
||||
def sleep(seconds=0, ref=True):
|
||||
"""
|
||||
Put the current greenlet to sleep for at least *seconds*.
|
||||
|
||||
*seconds* may be specified as an integer, or a float if fractional
|
||||
seconds are desired.
|
||||
|
||||
.. tip:: In the current implementation, a value of 0 (the default)
|
||||
means to yield execution to any other runnable greenlets, but
|
||||
this greenlet may be scheduled again before the event loop
|
||||
cycles (in an extreme case, a greenlet that repeatedly sleeps
|
||||
with 0 can prevent greenlets that are ready to do I/O from
|
||||
being scheduled for some (small) period of time); a value greater than
|
||||
0, on the other hand, will delay running this greenlet until
|
||||
the next iteration of the loop.
|
||||
|
||||
If *ref* is False, the greenlet running ``sleep()`` will not prevent :func:`gevent.wait`
|
||||
from exiting.
|
||||
|
||||
.. versionchanged:: 1.3a1
|
||||
Sleeping with a value of 0 will now be bounded to approximately block the
|
||||
loop for no longer than :func:`gevent.getswitchinterval`.
|
||||
|
||||
.. seealso:: :func:`idle`
|
||||
"""
|
||||
hub = _get_hub_noargs()
|
||||
loop = hub.loop
|
||||
if seconds <= 0:
|
||||
waiter = Waiter(hub)
|
||||
loop.run_callback(waiter.switch, None)
|
||||
waiter.get()
|
||||
else:
|
||||
with loop.timer(seconds, ref=ref) as t:
|
||||
# Sleeping is expected to be an "absolute" measure with
|
||||
# respect to time.time(), not a relative measure, so it's
|
||||
# important to update the loop's notion of now before we start
|
||||
loop.update_now()
|
||||
hub.wait(t)
|
||||
|
||||
|
||||
def idle(priority=0):
|
||||
"""
|
||||
Cause the calling greenlet to wait until the event loop is idle.
|
||||
|
||||
Idle is defined as having no other events of the same or higher
|
||||
*priority* pending. That is, as long as sockets, timeouts or even
|
||||
signals of the same or higher priority are being processed, the loop
|
||||
is not idle.
|
||||
|
||||
.. seealso:: :func:`sleep`
|
||||
"""
|
||||
hub = _get_hub_noargs()
|
||||
with hub.loop.idle() as watcher:
|
||||
if priority:
|
||||
watcher.priority = priority
|
||||
hub.wait(watcher)
|
||||
|
||||
|
||||
def kill(greenlet, exception=GreenletExit):
|
||||
"""
|
||||
Kill greenlet asynchronously. The current greenlet is not unscheduled.
|
||||
|
||||
.. note::
|
||||
|
||||
The method :meth:`Greenlet.kill` method does the same and
|
||||
more (and the same caveats listed there apply here). However, the MAIN
|
||||
greenlet - the one that exists initially - does not have a
|
||||
``kill()`` method, and neither do any created with :func:`spawn_raw`,
|
||||
so you have to use this function.
|
||||
|
||||
.. caution:: Use care when killing greenlets. If they are not prepared for
|
||||
exceptions, this could result in corrupted state.
|
||||
|
||||
.. versionchanged:: 1.1a2
|
||||
If the ``greenlet`` has a :meth:`kill <Greenlet.kill>` method, calls it. This prevents a
|
||||
greenlet from being switched to for the first time after it's been
|
||||
killed but not yet executed.
|
||||
"""
|
||||
if not greenlet.dead:
|
||||
if hasattr(greenlet, 'kill'):
|
||||
# dealing with gevent.greenlet.Greenlet. Use it, especially
|
||||
# to avoid allowing one to be switched to for the first time
|
||||
# after it's been killed
|
||||
greenlet.kill(exception=exception, block=False)
|
||||
else:
|
||||
_get_hub_noargs().loop.run_callback(greenlet.throw, exception)
|
||||
|
||||
|
||||
class signal(object):
|
||||
"""
|
||||
signal_handler(signalnum, handler, *args, **kwargs) -> object
|
||||
|
||||
Call the *handler* with the *args* and *kwargs* when the process
|
||||
receives the signal *signalnum*.
|
||||
|
||||
The *handler* will be run in a new greenlet when the signal is
|
||||
delivered.
|
||||
|
||||
This returns an object with the useful method ``cancel``, which,
|
||||
when called, will prevent future deliveries of *signalnum* from
|
||||
calling *handler*. It's best to keep the returned object alive
|
||||
until you call ``cancel``.
|
||||
|
||||
.. note::
|
||||
|
||||
This may not operate correctly with ``SIGCHLD`` if libev child
|
||||
watchers are used (as they are by default with
|
||||
`gevent.os.fork`). See :mod:`gevent.signal` for a more
|
||||
general purpose solution.
|
||||
|
||||
.. versionchanged:: 1.2a1
|
||||
|
||||
The ``handler`` argument is required to
|
||||
be callable at construction time.
|
||||
|
||||
.. versionchanged:: 20.5.1
|
||||
The ``cancel`` method now properly cleans up all native resources,
|
||||
and drops references to all the arguments of this function.
|
||||
"""
|
||||
# This is documented as a function, not a class,
|
||||
# so we're free to change implementation details.
|
||||
|
||||
greenlet_class = None
|
||||
|
||||
def __init__(self, signalnum, handler, *args, **kwargs):
|
||||
if not callable(handler):
|
||||
raise TypeError("signal handler must be callable.")
|
||||
|
||||
self.hub = _get_hub_noargs()
|
||||
self.watcher = self.hub.loop.signal(signalnum, ref=False)
|
||||
self.handler = handler
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
if self.greenlet_class is None:
|
||||
from gevent import Greenlet
|
||||
type(self).greenlet_class = Greenlet
|
||||
self.greenlet_class = Greenlet
|
||||
|
||||
self.watcher.start(self._start)
|
||||
|
||||
ref = property(
|
||||
lambda self: self.watcher.ref,
|
||||
lambda self, nv: setattr(self.watcher, 'ref', nv)
|
||||
)
|
||||
|
||||
def cancel(self):
|
||||
if self.watcher is not None:
|
||||
self.watcher.stop()
|
||||
# Must close the watcher at a deterministic time, otherwise
|
||||
# when CFFI reclaims the memory, the native loop might still
|
||||
# have some reference to it; if anything tries to touch it
|
||||
# we can wind up writing to memory that is no longer valid,
|
||||
# leading to a wide variety of crashes.
|
||||
self.watcher.close()
|
||||
self.watcher = None
|
||||
self.handler = None
|
||||
self.args = None
|
||||
self.kwargs = None
|
||||
self.hub = None
|
||||
self.greenlet_class = None
|
||||
|
||||
def _start(self):
|
||||
# TODO: Maybe this should just be Greenlet.spawn()?
|
||||
try:
|
||||
greenlet = self.greenlet_class(self.handle)
|
||||
greenlet.switch()
|
||||
except: # pylint:disable=bare-except
|
||||
self.hub.handle_error(None, *sys._exc_info()) # pylint:disable=no-member
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
self.handler(*self.args, **self.kwargs)
|
||||
except: # pylint:disable=bare-except
|
||||
self.hub.handle_error(None, *sys.exc_info())
|
||||
|
||||
|
||||
def reinit(hub=None):
|
||||
"""
|
||||
reinit() -> None
|
||||
|
||||
Prepare the gevent hub to run in a new (forked) process.
|
||||
|
||||
This should be called *immediately* after :func:`os.fork` in the
|
||||
child process. This is done automatically by
|
||||
:func:`gevent.os.fork` or if the :mod:`os` module has been
|
||||
monkey-patched. If this function is not called in a forked
|
||||
process, symptoms may include hanging of functions like
|
||||
:func:`socket.getaddrinfo`, and the hub's threadpool is unlikely
|
||||
to work.
|
||||
|
||||
.. note:: Registered fork watchers may or may not run before
|
||||
this function (and thus ``gevent.os.fork``) return. If they have
|
||||
not run, they will run "soon", after an iteration of the event loop.
|
||||
You can force this by inserting a few small (but non-zero) calls to :func:`sleep`
|
||||
after fork returns. (As of gevent 1.1 and before, fork watchers will
|
||||
not have run, but this may change in the future.)
|
||||
|
||||
.. note:: This function may be removed in a future major release
|
||||
if the fork process can be more smoothly managed.
|
||||
|
||||
.. warning:: See remarks in :func:`gevent.os.fork` about greenlets
|
||||
and event loop watchers in the child process.
|
||||
"""
|
||||
# Note the signature line in the docstring: hub is not a public param.
|
||||
|
||||
# The loop reinit function in turn calls libev's ev_loop_fork
|
||||
# function.
|
||||
hub = _get_hub() if hub is None else hub
|
||||
if hub is None:
|
||||
return
|
||||
|
||||
# Note that we reinit the existing loop, not destroy it.
|
||||
# See https://github.com/gevent/gevent/issues/200.
|
||||
hub.loop.reinit()
|
||||
# libev's fork watchers are slow to fire because the only fire
|
||||
# at the beginning of a loop; due to our use of callbacks that
|
||||
# run at the end of the loop, that may be too late. The
|
||||
# threadpool and resolvers depend on the fork handlers being
|
||||
# run (specifically, the threadpool will fail in the forked
|
||||
# child if there were any threads in it, which there will be
|
||||
# if the resolver_thread was in use (the default) before the
|
||||
# fork.)
|
||||
#
|
||||
# If the forked process wants to use the threadpool or
|
||||
# resolver immediately (in a queued callback), it would hang.
|
||||
#
|
||||
# The below is a workaround. Fortunately, all of these
|
||||
# methods are idempotent and can be called multiple times
|
||||
# following a fork if the suddenly started working, or were
|
||||
# already working on some platforms. Other threadpools and fork handlers
|
||||
# will be called at an arbitrary time later ('soon')
|
||||
for obj in (hub._threadpool, hub._resolver, hub.periodic_monitoring_thread):
|
||||
getattr(obj, '_on_fork', lambda: None)()
|
||||
|
||||
# TODO: We'd like to sleep for a non-zero amount of time to force the loop to make a
|
||||
# pass around before returning to this greenlet. That will allow any
|
||||
# user-provided fork watchers to run. (Two calls are necessary.) HOWEVER, if
|
||||
# we do this, certain tests that heavily mix threads and forking,
|
||||
# like 2.7/test_threading:test_reinit_tls_after_fork, fail. It's not immediately clear
|
||||
# why.
|
||||
#sleep(0.00001)
|
||||
#sleep(0.00001)
|
||||
|
||||
|
||||
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 type is HubDestroyed:
|
||||
# We must continue propagating this for it to properly
|
||||
# exit.
|
||||
reraise(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 destroy_loop is None:
|
||||
destroy_loop = not self.loop.default
|
||||
|
||||
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 (because we will have destroyed
|
||||
# the C datastructures in the middle of the C function that's
|
||||
# using them; the best we can hope for is a segfault).
|
||||
try:
|
||||
self.throw(HubDestroyed(destroy_loop))
|
||||
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:
|
||||
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,
|
||||
idle_task_timeout=GEVENT_CONFIG.threadpool_idle_task_timeout
|
||||
)
|
||||
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.
|
||||
""")
|
||||
|
||||
|
||||
set_default_hub_class(Hub)
|
||||
|
||||
|
||||
|
||||
class linkproxy(object):
|
||||
__slots__ = ['callback', 'obj']
|
||||
|
||||
def __init__(self, callback, obj):
|
||||
self.callback = callback
|
||||
self.obj = obj
|
||||
|
||||
def __call__(self, *args):
|
||||
callback = self.callback
|
||||
obj = self.obj
|
||||
self.callback = None
|
||||
self.obj = None
|
||||
callback(obj)
|
||||
Reference in New Issue
Block a user