mit neuen venv und exe-Files
This commit is contained in:
819
venv3_12/Lib/site-packages/gevent/threadpool.py
Normal file
819
venv3_12/Lib/site-packages/gevent/threadpool.py
Normal file
@@ -0,0 +1,819 @@
|
||||
# Copyright (c) 2012 Denis Bilenko. See LICENSE for details.
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
from greenlet import greenlet as RawGreenlet
|
||||
|
||||
from gevent import monkey
|
||||
from gevent._compat import integer_types
|
||||
from gevent.event import AsyncResult
|
||||
from gevent.exceptions import InvalidThreadUseError
|
||||
from gevent.greenlet import Greenlet
|
||||
|
||||
from gevent._hub_local import get_hub_if_exists
|
||||
from gevent.hub import _get_hub_noargs as get_hub
|
||||
from gevent.hub import getcurrent
|
||||
from gevent.hub import sleep
|
||||
from gevent.lock import Semaphore
|
||||
from gevent.pool import GroupMappingMixin
|
||||
from gevent.util import clear_stack_frames
|
||||
|
||||
from gevent._threading import Queue
|
||||
from gevent._threading import EmptyTimeout
|
||||
from gevent._threading import start_new_thread
|
||||
from gevent._threading import get_thread_ident
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ThreadPool',
|
||||
'ThreadResult',
|
||||
]
|
||||
|
||||
def _format_hub(hub):
|
||||
if hub is None:
|
||||
return '<missing>'
|
||||
return '<%s at 0x%x thread_ident=0x%x>' % (
|
||||
hub.__class__.__name__, id(hub), hub.thread_ident
|
||||
)
|
||||
|
||||
|
||||
def _get_thread_profile(_sys=sys):
|
||||
if 'threading' in _sys.modules:
|
||||
return _sys.modules['threading']._profile_hook
|
||||
|
||||
|
||||
def _get_thread_trace(_sys=sys):
|
||||
if 'threading' in _sys.modules:
|
||||
return _sys.modules['threading']._trace_hook
|
||||
|
||||
|
||||
class _WorkerGreenlet(RawGreenlet):
|
||||
# Exists to produce a more useful repr for worker pool
|
||||
# threads/greenlets, and manage the communication of the worker
|
||||
# thread with the threadpool.
|
||||
|
||||
# Inform the gevent.util.GreenletTree that this should be
|
||||
# considered the root (for printing purposes)
|
||||
greenlet_tree_is_root = True
|
||||
|
||||
_thread_ident = 0
|
||||
_exc_info = sys.exc_info
|
||||
_get_hub_if_exists = staticmethod(get_hub_if_exists)
|
||||
# We capture the hub each time through the loop in case its created
|
||||
# so we can destroy it after a fork.
|
||||
_hub_of_worker = None
|
||||
# The hub of the threadpool we're working for. Just for info.
|
||||
_hub = None
|
||||
|
||||
# A cookie passed to task_queue.get()
|
||||
_task_queue_cookie = None
|
||||
|
||||
# If not -1, how long to block waiting for a task before we
|
||||
# exit.
|
||||
_idle_task_timeout = -1
|
||||
|
||||
def __init__(self, threadpool):
|
||||
# Construct in the main thread (owner of the threadpool)
|
||||
# The parent greenlet and thread identifier will be set once the
|
||||
# new thread begins running.
|
||||
RawGreenlet.__init__(self)
|
||||
|
||||
self._hub = threadpool.hub
|
||||
# Avoid doing any imports in the background thread if it's not
|
||||
# necessary (monkey.get_original imports if not patched).
|
||||
# Background imports can hang Python 2 (gevent's thread resolver runs in the BG,
|
||||
# and resolving may have to import the idna module, which needs an import lock, so
|
||||
# resolving at module scope)
|
||||
if monkey.is_module_patched('sys'):
|
||||
stderr = monkey.get_original('sys', 'stderr')
|
||||
else:
|
||||
stderr = sys.stderr
|
||||
self._stderr = stderr
|
||||
# We can capture the task_queue; even though it can change if the threadpool
|
||||
# is re-innitted, we won't be running in that case
|
||||
self._task_queue = threadpool.task_queue # type:gevent._threading.Queue
|
||||
self._task_queue_cookie = self._task_queue.allocate_cookie()
|
||||
self._unregister_worker = threadpool._unregister_worker
|
||||
self._idle_task_timeout = threadpool._idle_task_timeout
|
||||
|
||||
threadpool._register_worker(self)
|
||||
try:
|
||||
start_new_thread(self._begin, ())
|
||||
except:
|
||||
self._unregister_worker(self)
|
||||
raise
|
||||
|
||||
def _begin(self, _get_c=getcurrent, _get_ti=get_thread_ident):
|
||||
# Pass arguments to avoid accessing globals during module shutdown.
|
||||
|
||||
# we're in the new thread (but its root greenlet). Establish invariants and get going
|
||||
# by making this the current greenlet.
|
||||
self.parent = _get_c() # pylint:disable=attribute-defined-outside-init
|
||||
self._thread_ident = _get_ti()
|
||||
# ignore the parent attribute. (We can't set parent to None.)
|
||||
self.parent.greenlet_tree_is_ignored = True
|
||||
try:
|
||||
self.switch() # goto run()
|
||||
except: # pylint:disable=bare-except
|
||||
# run() will attempt to print any exceptions, but that might
|
||||
# not work during shutdown. sys.excepthook and such may be gone,
|
||||
# so things might not get printed at all except for a cryptic
|
||||
# message. This is especially true on Python 2 (doesn't seem to be
|
||||
# an issue on Python 3).
|
||||
pass
|
||||
|
||||
def __fixup_hub_before_block(self):
|
||||
hub = self._get_hub_if_exists() # Don't create one; only set if a worker function did it
|
||||
if hub is not None:
|
||||
hub.name = 'ThreadPool Worker Hub'
|
||||
# While we block, don't let the monitoring thread, if any,
|
||||
# report us as blocked. Indeed, so long as we never
|
||||
# try to switch greenlets, don't report us as blocked---
|
||||
# the threadpool is *meant* to run blocking tasks
|
||||
if hub is not None and hub.periodic_monitoring_thread is not None:
|
||||
hub.periodic_monitoring_thread.ignore_current_greenlet_blocking()
|
||||
self._hub_of_worker = hub
|
||||
|
||||
@staticmethod
|
||||
def __print_tb(tb, stderr):
|
||||
# Extracted from traceback to avoid accessing any module
|
||||
# globals (these sometimes happen during interpreter shutdown;
|
||||
# see test__subprocess_interrupted)
|
||||
while tb is not None:
|
||||
f = tb.tb_frame
|
||||
lineno = tb.tb_lineno
|
||||
co = f.f_code
|
||||
filename = co.co_filename
|
||||
name = co.co_name
|
||||
print(' File "%s", line %d, in %s' % (filename, lineno, name),
|
||||
file=stderr)
|
||||
tb = tb.tb_next
|
||||
|
||||
def _before_run_task(self, func, args, kwargs, thread_result,
|
||||
_sys=sys,
|
||||
_get_thread_profile=_get_thread_profile,
|
||||
_get_thread_trace=_get_thread_trace):
|
||||
# pylint:disable=unused-argument
|
||||
_sys.setprofile(_get_thread_profile())
|
||||
_sys.settrace(_get_thread_trace())
|
||||
|
||||
def _after_run_task(self, func, args, kwargs, thread_result, _sys=sys):
|
||||
# pylint:disable=unused-argument
|
||||
_sys.setprofile(None)
|
||||
_sys.settrace(None)
|
||||
|
||||
def __run_task(self, func, args, kwargs, thread_result):
|
||||
self._before_run_task(func, args, kwargs, thread_result)
|
||||
try:
|
||||
thread_result.set(func(*args, **kwargs))
|
||||
except: # pylint:disable=bare-except
|
||||
thread_result.handle_error((self, func), self._exc_info())
|
||||
finally:
|
||||
self._after_run_task(func, args, kwargs, thread_result)
|
||||
del func, args, kwargs, thread_result
|
||||
|
||||
def run(self):
|
||||
# pylint:disable=too-many-branches
|
||||
task = None
|
||||
exc_info = sys.exc_info
|
||||
fixup_hub_before_block = self.__fixup_hub_before_block
|
||||
task_queue_get = self._task_queue.get
|
||||
task_queue_cookie = self._task_queue_cookie
|
||||
run_task = self.__run_task
|
||||
task_queue_done = self._task_queue.task_done
|
||||
idle_task_timeout = self._idle_task_timeout
|
||||
try: # pylint:disable=too-many-nested-blocks
|
||||
while 1: # tiny bit faster than True on Py2
|
||||
fixup_hub_before_block()
|
||||
|
||||
try:
|
||||
task = task_queue_get(task_queue_cookie, idle_task_timeout)
|
||||
except EmptyTimeout:
|
||||
# Nothing to do, exit the thread. Do not
|
||||
# go into the next block where we would call
|
||||
# queue.task_done(), because we didn't actually
|
||||
# take a task.
|
||||
return
|
||||
try:
|
||||
if task is None:
|
||||
return
|
||||
|
||||
run_task(*task)
|
||||
except:
|
||||
task = repr(task)
|
||||
raise
|
||||
finally:
|
||||
task = None if not isinstance(task, str) else task
|
||||
task_queue_done()
|
||||
except Exception as e: # pylint:disable=broad-except
|
||||
print(
|
||||
"Failed to run worker thread. Task=%r Exception=%r" % (
|
||||
task, e
|
||||
),
|
||||
file=self._stderr)
|
||||
self.__print_tb(exc_info()[-1], self._stderr)
|
||||
finally:
|
||||
# Re-check for the hub in case the task created it but then
|
||||
# failed.
|
||||
self.cleanup(self._get_hub_if_exists())
|
||||
|
||||
def cleanup(self, hub_of_worker):
|
||||
if self._hub is not None:
|
||||
self._hub = None
|
||||
self._unregister_worker(self)
|
||||
self._unregister_worker = lambda _: None
|
||||
self._task_queue = None
|
||||
self._task_queue_cookie = None
|
||||
|
||||
if hub_of_worker is not None:
|
||||
hub_of_worker.destroy(True)
|
||||
|
||||
def __repr__(self, _format_hub=_format_hub):
|
||||
return "<ThreadPoolWorker at 0x%x thread_ident=0x%x threadpool-hub=%s>" % (
|
||||
id(self),
|
||||
self._thread_ident,
|
||||
_format_hub(self._hub)
|
||||
)
|
||||
|
||||
|
||||
class ThreadPool(GroupMappingMixin):
|
||||
"""
|
||||
A pool of native worker threads.
|
||||
|
||||
This can be useful for CPU intensive functions, or those that
|
||||
otherwise will not cooperate with gevent. The best functions to execute
|
||||
in a thread pool are small functions with a single purpose; ideally they release
|
||||
the CPython GIL. Such functions are extension functions implemented in C.
|
||||
|
||||
It implements the same operations as a :class:`gevent.pool.Pool`,
|
||||
but using threads instead of greenlets.
|
||||
|
||||
.. note:: The method :meth:`apply_async` will always return a new
|
||||
greenlet, bypassing the threadpool entirely.
|
||||
|
||||
Most users will not need to create instances of this class. Instead,
|
||||
use the threadpool already associated with gevent's hub::
|
||||
|
||||
pool = gevent.get_hub().threadpool
|
||||
result = pool.spawn(lambda: "Some func").get()
|
||||
|
||||
.. important:: It is only possible to use instances of this class from
|
||||
the thread running their hub. Typically that means from the thread that
|
||||
created them. Using the pattern shown above takes care of this.
|
||||
|
||||
There is no gevent-provided way to have a single process-wide limit on the
|
||||
number of threads in various pools when doing that, however. The suggested
|
||||
way to use gevent and threadpools is to have a single gevent hub
|
||||
and its one threadpool (which is the default without doing any extra work).
|
||||
Only dispatch minimal blocking functions to the threadpool, functions that
|
||||
do not use the gevent hub.
|
||||
|
||||
The `len` of instances of this class is the number of enqueued
|
||||
(unfinished) tasks.
|
||||
|
||||
Just before a task starts running in a worker thread,
|
||||
the values of :func:`threading.setprofile` and :func:`threading.settrace`
|
||||
are consulted. Any values there are installed in that thread for the duration
|
||||
of the task (using :func:`sys.setprofile` and :func:`sys.settrace`, respectively).
|
||||
(Because worker threads are long-lived and outlast any given task, this arrangement
|
||||
lets the hook functions change between tasks, but does not let them see the
|
||||
bookkeeping done by the worker thread itself.)
|
||||
|
||||
.. caution:: Instances of this class are only true if they have
|
||||
unfinished tasks.
|
||||
|
||||
.. versionchanged:: 1.5a3
|
||||
The undocumented ``apply_e`` function, deprecated since 1.1,
|
||||
was removed.
|
||||
.. versionchanged:: 20.12.0
|
||||
Install the profile and trace functions in the worker thread while
|
||||
the worker thread is running the supplied task.
|
||||
.. versionchanged:: 22.08.0
|
||||
Add the option to let idle threads expire and be removed
|
||||
from the pool after *idle_task_timeout* seconds (-1 for no
|
||||
timeout)
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'hub',
|
||||
'_maxsize',
|
||||
# A Greenlet that runs to adjust the number of worker
|
||||
# threads.
|
||||
'manager',
|
||||
# The PID of the process we were created in.
|
||||
# Used to help detect a fork and then re-create
|
||||
# internal state.
|
||||
'pid',
|
||||
'fork_watcher',
|
||||
# A semaphore initialized with ``maxsize`` counting the
|
||||
# number of available worker threads we have. As a
|
||||
# gevent.lock.Semaphore, this is only safe to use from a single
|
||||
# native thread.
|
||||
'_available_worker_threads_greenlet_sem',
|
||||
# A set of running or pending _WorkerGreenlet objects;
|
||||
# we rely on the GIL for thread safety.
|
||||
'_worker_greenlets',
|
||||
# The task queue is itself safe to use from multiple
|
||||
# native threads.
|
||||
'task_queue',
|
||||
'_idle_task_timeout',
|
||||
)
|
||||
|
||||
_WorkerGreenlet = _WorkerGreenlet
|
||||
|
||||
def __init__(self, maxsize, hub=None, idle_task_timeout=-1):
|
||||
if hub is None:
|
||||
hub = get_hub()
|
||||
self.hub = hub
|
||||
self.pid = os.getpid()
|
||||
self.manager = None
|
||||
self.task_queue = Queue()
|
||||
self.fork_watcher = None
|
||||
self._idle_task_timeout = idle_task_timeout
|
||||
|
||||
self._worker_greenlets = set()
|
||||
self._maxsize = 0
|
||||
# Note that by starting with 1, we actually allow
|
||||
# maxsize + 1 tasks in the queue.
|
||||
self._available_worker_threads_greenlet_sem = Semaphore(1, hub)
|
||||
self._set_maxsize(maxsize)
|
||||
self.fork_watcher = hub.loop.fork(ref=False)
|
||||
|
||||
def _register_worker(self, worker):
|
||||
self._worker_greenlets.add(worker)
|
||||
|
||||
def _unregister_worker(self, worker):
|
||||
self._worker_greenlets.discard(worker)
|
||||
|
||||
def _set_maxsize(self, maxsize):
|
||||
if not isinstance(maxsize, integer_types):
|
||||
raise TypeError('maxsize must be integer: %r' % (maxsize, ))
|
||||
if maxsize < 0:
|
||||
raise ValueError('maxsize must not be negative: %r' % (maxsize, ))
|
||||
difference = maxsize - self._maxsize
|
||||
self._available_worker_threads_greenlet_sem.counter += difference
|
||||
self._maxsize = maxsize
|
||||
self.adjust()
|
||||
# make sure all currently blocking spawn() start unlocking if maxsize increased
|
||||
self._available_worker_threads_greenlet_sem._start_notify()
|
||||
|
||||
def _get_maxsize(self):
|
||||
return self._maxsize
|
||||
|
||||
maxsize = property(_get_maxsize, _set_maxsize, doc="""\
|
||||
The maximum allowed number of worker threads.
|
||||
|
||||
This is also (approximately) a limit on the number of tasks that
|
||||
can be queued without blocking the waiting greenlet. If this many
|
||||
tasks are already running, then the next greenlet that submits a task
|
||||
will block waiting for a task to finish.
|
||||
""")
|
||||
|
||||
def __repr__(self, _format_hub=_format_hub):
|
||||
return '<%s at 0x%x tasks=%s size=%s maxsize=%s hub=%s>' % (
|
||||
self.__class__.__name__,
|
||||
id(self),
|
||||
len(self), self.size, self.maxsize,
|
||||
_format_hub(self.hub),
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
# XXX just do unfinished_tasks property
|
||||
# Note that this becomes the boolean value of this class,
|
||||
# that's probably not what we want!
|
||||
return self.task_queue.unfinished_tasks
|
||||
|
||||
def _get_size(self):
|
||||
return len(self._worker_greenlets)
|
||||
|
||||
def _set_size(self, size):
|
||||
if size < 0:
|
||||
raise ValueError('Size of the pool cannot be negative: %r' % (size, ))
|
||||
if size > self._maxsize:
|
||||
raise ValueError('Size of the pool cannot be bigger than maxsize: %r > %r' % (size, self._maxsize))
|
||||
if self.manager:
|
||||
self.manager.kill()
|
||||
while len(self._worker_greenlets) < size:
|
||||
self._add_thread()
|
||||
delay = self.hub.loop.approx_timer_resolution
|
||||
while len(self._worker_greenlets) > size:
|
||||
while len(self._worker_greenlets) - size > self.task_queue.unfinished_tasks:
|
||||
self.task_queue.put(None)
|
||||
if getcurrent() is self.hub:
|
||||
break
|
||||
sleep(delay)
|
||||
delay = min(delay * 2, .05)
|
||||
if self._worker_greenlets:
|
||||
self.fork_watcher.start(self._on_fork)
|
||||
else:
|
||||
self.fork_watcher.stop()
|
||||
|
||||
size = property(_get_size, _set_size, doc="""\
|
||||
The number of running pooled worker threads.
|
||||
|
||||
Setting this attribute will add or remove running
|
||||
worker threads, up to `maxsize`.
|
||||
|
||||
Initially there are no pooled running worker threads, and
|
||||
threads are created on demand to satisfy concurrent
|
||||
requests up to `maxsize` threads.
|
||||
""")
|
||||
|
||||
|
||||
def _on_fork(self):
|
||||
# fork() only leaves one thread; also screws up locks;
|
||||
# let's re-create locks and threads, and do our best to
|
||||
# clean up any worker threads left behind.
|
||||
# NOTE: See comment in gevent.hub.reinit.
|
||||
pid = os.getpid()
|
||||
if pid != self.pid:
|
||||
# The OS threads have been destroyed, but the Python
|
||||
# objects may live on, creating refcount "leaks". Python 2
|
||||
# leaves dead frames (those that are for dead OS threads)
|
||||
# around; Python 3.8 does not.
|
||||
thread_ident_to_frame = dict(sys._current_frames())
|
||||
for worker in list(self._worker_greenlets):
|
||||
frame = thread_ident_to_frame.get(worker._thread_ident)
|
||||
clear_stack_frames(frame)
|
||||
worker.cleanup(worker._hub_of_worker)
|
||||
# We can't throw anything to the greenlet, nor can we
|
||||
# switch to it or set a parent. Those would all be cross-thread
|
||||
# operations, which aren't allowed.
|
||||
worker.__dict__.clear()
|
||||
|
||||
# We've cleared f_locals and on Python 3.4, possibly the actual
|
||||
# array locals of the stack frame, but the task queue may still be
|
||||
# referenced if we didn't actually get all the locals. Shut it down
|
||||
# and clear it before we throw away our reference.
|
||||
self.task_queue.kill()
|
||||
self.__init__(self._maxsize)
|
||||
|
||||
|
||||
def join(self):
|
||||
"""Waits until all outstanding tasks have been completed."""
|
||||
delay = max(0.0005, self.hub.loop.approx_timer_resolution)
|
||||
while self.task_queue.unfinished_tasks > 0:
|
||||
sleep(delay)
|
||||
delay = min(delay * 2, .05)
|
||||
|
||||
def kill(self):
|
||||
self.size = 0
|
||||
self.fork_watcher.close()
|
||||
|
||||
def _adjust_step(self):
|
||||
# if there is a possibility & necessity for adding a thread, do it
|
||||
while (len(self._worker_greenlets) < self._maxsize
|
||||
and self.task_queue.unfinished_tasks > len(self._worker_greenlets)):
|
||||
self._add_thread()
|
||||
# while the number of threads is more than maxsize, kill one
|
||||
# we do not check what's already in task_queue - it could be all Nones
|
||||
while len(self._worker_greenlets) - self._maxsize > self.task_queue.unfinished_tasks:
|
||||
self.task_queue.put(None)
|
||||
if self._worker_greenlets:
|
||||
self.fork_watcher.start(self._on_fork)
|
||||
elif self.fork_watcher is not None:
|
||||
self.fork_watcher.stop()
|
||||
|
||||
def _adjust_wait(self):
|
||||
delay = self.hub.loop.approx_timer_resolution
|
||||
while True:
|
||||
self._adjust_step()
|
||||
if len(self._worker_greenlets) <= self._maxsize:
|
||||
return
|
||||
sleep(delay)
|
||||
delay = min(delay * 2, .05)
|
||||
|
||||
def adjust(self):
|
||||
self._adjust_step()
|
||||
if not self.manager and len(self._worker_greenlets) > self._maxsize:
|
||||
# might need to feed more Nones into the pool to shutdown
|
||||
# threads.
|
||||
self.manager = Greenlet.spawn(self._adjust_wait)
|
||||
|
||||
def _add_thread(self):
|
||||
self._WorkerGreenlet(self)
|
||||
|
||||
def spawn(self, func, *args, **kwargs):
|
||||
"""
|
||||
Add a new task to the threadpool that will run ``func(*args,
|
||||
**kwargs)``.
|
||||
|
||||
Waits until a slot is available. Creates a new native thread
|
||||
if necessary.
|
||||
|
||||
This must only be called from the native thread that owns this
|
||||
object's hub. This is because creating the necessary data
|
||||
structures to communicate back to this thread isn't thread
|
||||
safe, so the hub must not be running something else. Also,
|
||||
ensuring the pool size stays correct only works within a
|
||||
single thread.
|
||||
|
||||
:return: A :class:`gevent.event.AsyncResult`.
|
||||
:raises InvalidThreadUseError: If called from a different thread.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
Document the thread-safety requirements.
|
||||
"""
|
||||
if self.hub != get_hub():
|
||||
raise InvalidThreadUseError
|
||||
|
||||
while 1:
|
||||
semaphore = self._available_worker_threads_greenlet_sem
|
||||
semaphore.acquire()
|
||||
if semaphore is self._available_worker_threads_greenlet_sem:
|
||||
# If we were asked to change size or re-init we could have changed
|
||||
# semaphore objects.
|
||||
break
|
||||
|
||||
# Returned; lets a greenlet in this thread wait
|
||||
# for the pool thread. Signaled when the async watcher
|
||||
# is fired from the pool thread back into this thread.
|
||||
result = AsyncResult()
|
||||
task_queue = self.task_queue
|
||||
# Encapsulates the async watcher the worker thread uses to
|
||||
# call back into this thread. Immediately allocates and starts the
|
||||
# async watcher in this thread, because it uses this hub/loop,
|
||||
# which is not thread safe.
|
||||
thread_result = None
|
||||
try:
|
||||
thread_result = ThreadResult(result, self.hub, semaphore.release)
|
||||
task_queue.put((func, args, kwargs, thread_result))
|
||||
self.adjust()
|
||||
except:
|
||||
if thread_result is not None:
|
||||
thread_result.destroy_in_main_thread()
|
||||
semaphore.release()
|
||||
raise
|
||||
return result
|
||||
|
||||
def _apply_immediately(self):
|
||||
# If we're being called from a different thread than the one that
|
||||
# created us, e.g., because a worker task is trying to use apply()
|
||||
# recursively, we have no choice but to run the task immediately;
|
||||
# if we try to AsyncResult.get() in the worker thread, it's likely to have
|
||||
# nothing to switch to and lead to a LoopExit.
|
||||
return get_hub() is not self.hub
|
||||
|
||||
def _apply_async_cb_spawn(self, callback, result):
|
||||
callback(result)
|
||||
|
||||
def _apply_async_use_greenlet(self):
|
||||
# Always go to Greenlet because our self.spawn uses threads
|
||||
return True
|
||||
|
||||
class _FakeAsync(object):
|
||||
|
||||
def send(self):
|
||||
pass
|
||||
close = stop = send
|
||||
|
||||
def __call__(self, result):
|
||||
"fake out for 'receiver'"
|
||||
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
_FakeAsync = _FakeAsync()
|
||||
|
||||
class ThreadResult(object):
|
||||
"""
|
||||
A one-time event for cross-thread communication.
|
||||
|
||||
Uses a hub's "async" watcher capability; it must be constructed and
|
||||
destroyed in the thread running the hub (because creating, starting, and
|
||||
destroying async watchers isn't guaranteed to be thread safe).
|
||||
"""
|
||||
|
||||
# Using slots here helps to debug reference cycles/leaks
|
||||
__slots__ = ('exc_info', 'async_watcher', '_call_when_ready', 'value',
|
||||
'context', 'hub', 'receiver')
|
||||
|
||||
def __init__(self, receiver, hub, call_when_ready):
|
||||
self.receiver = receiver
|
||||
self.hub = hub
|
||||
self.context = None
|
||||
self.value = None
|
||||
self.exc_info = ()
|
||||
self.async_watcher = hub.loop.async_()
|
||||
self._call_when_ready = call_when_ready
|
||||
self.async_watcher.start(self._on_async)
|
||||
|
||||
@property
|
||||
def exception(self):
|
||||
return self.exc_info[1] if self.exc_info else None
|
||||
|
||||
def _on_async(self):
|
||||
# Called in the hub thread.
|
||||
|
||||
aw = self.async_watcher
|
||||
self.async_watcher = _FakeAsync
|
||||
|
||||
aw.stop()
|
||||
aw.close()
|
||||
|
||||
# Typically this is pool.semaphore.release and we have to
|
||||
# call this in the Hub; if we don't we get the dreaded
|
||||
# LoopExit (XXX: Why?)
|
||||
try:
|
||||
self._call_when_ready()
|
||||
if self.exc_info:
|
||||
self.hub.handle_error(self.context, *self.exc_info)
|
||||
self.context = None
|
||||
self.async_watcher = _FakeAsync
|
||||
self.hub = None
|
||||
self._call_when_ready = _FakeAsync
|
||||
|
||||
self.receiver(self)
|
||||
finally:
|
||||
self.receiver = _FakeAsync
|
||||
self.value = None
|
||||
if self.exc_info:
|
||||
self.exc_info = (self.exc_info[0], self.exc_info[1], None)
|
||||
|
||||
def destroy_in_main_thread(self):
|
||||
"""
|
||||
This must only be called from the thread running the hub.
|
||||
"""
|
||||
self.async_watcher.stop()
|
||||
self.async_watcher.close()
|
||||
self.async_watcher = _FakeAsync
|
||||
|
||||
self.context = None
|
||||
self.hub = None
|
||||
self._call_when_ready = _FakeAsync
|
||||
self.receiver = _FakeAsync
|
||||
|
||||
def set(self, value):
|
||||
self.value = value
|
||||
self.async_watcher.send()
|
||||
|
||||
def handle_error(self, context, exc_info):
|
||||
self.context = context
|
||||
self.exc_info = exc_info
|
||||
self.async_watcher.send()
|
||||
|
||||
# link protocol:
|
||||
def successful(self):
|
||||
return self.exception is None
|
||||
|
||||
|
||||
try:
|
||||
import concurrent.futures
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
__all__.append("ThreadPoolExecutor")
|
||||
|
||||
from gevent.timeout import Timeout as GTimeout
|
||||
from gevent._util import Lazy
|
||||
from concurrent.futures import _base as cfb
|
||||
|
||||
def _ignore_error(future_proxy, fn):
|
||||
def cbwrap(_):
|
||||
del _
|
||||
# We're called with the async result (from the threadpool), but
|
||||
# be sure to pass in the user-visible _FutureProxy object..
|
||||
try:
|
||||
fn(future_proxy)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# Just print, don't raise to the hub's parent.
|
||||
future_proxy.hub.print_exception((fn, future_proxy), None, None, None)
|
||||
return cbwrap
|
||||
|
||||
def _wrap(future_proxy, fn):
|
||||
def f(_):
|
||||
fn(future_proxy)
|
||||
return f
|
||||
|
||||
class _FutureProxy(object):
|
||||
def __init__(self, asyncresult):
|
||||
self.asyncresult = asyncresult
|
||||
|
||||
# Internal implementation details of a c.f.Future
|
||||
|
||||
@Lazy
|
||||
def _condition(self):
|
||||
if monkey.is_module_patched('threading') or self.done():
|
||||
import threading
|
||||
return threading.Condition()
|
||||
# We can only properly work with conditions
|
||||
# when we've been monkey-patched. This is necessary
|
||||
# for the wait/as_completed module functions.
|
||||
raise AttributeError("_condition")
|
||||
|
||||
@Lazy
|
||||
def _waiters(self):
|
||||
self.asyncresult.rawlink(self.__when_done)
|
||||
return []
|
||||
|
||||
def __when_done(self, _):
|
||||
# We should only be called when _waiters has
|
||||
# already been accessed.
|
||||
waiters = getattr(self, '_waiters')
|
||||
for w in waiters: # pylint:disable=not-an-iterable
|
||||
if self.successful():
|
||||
w.add_result(self)
|
||||
else:
|
||||
w.add_exception(self)
|
||||
|
||||
@property
|
||||
def _state(self):
|
||||
if self.done():
|
||||
return cfb.FINISHED
|
||||
return cfb.RUNNING
|
||||
|
||||
def set_running_or_notify_cancel(self):
|
||||
# Does nothing, not even any consistency checks. It's
|
||||
# meant to be internal to the executor and we don't use it.
|
||||
return
|
||||
|
||||
def result(self, timeout=None):
|
||||
try:
|
||||
return self.asyncresult.result(timeout=timeout)
|
||||
except GTimeout:
|
||||
# XXX: Theoretically this could be a completely
|
||||
# unrelated timeout instance. Do we care about that?
|
||||
raise concurrent.futures.TimeoutError()
|
||||
|
||||
def exception(self, timeout=None):
|
||||
try:
|
||||
self.asyncresult.get(timeout=timeout)
|
||||
return self.asyncresult.exception
|
||||
except GTimeout:
|
||||
raise concurrent.futures.TimeoutError()
|
||||
|
||||
def add_done_callback(self, fn):
|
||||
"""Exceptions raised by *fn* are ignored."""
|
||||
if self.done():
|
||||
fn(self)
|
||||
else:
|
||||
self.asyncresult.rawlink(_ignore_error(self, fn))
|
||||
|
||||
def rawlink(self, fn):
|
||||
self.asyncresult.rawlink(_wrap(self, fn))
|
||||
|
||||
def __str__(self):
|
||||
return str(self.asyncresult)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.asyncresult, name)
|
||||
|
||||
class ThreadPoolExecutor(concurrent.futures.ThreadPoolExecutor):
|
||||
"""
|
||||
A version of :class:`concurrent.futures.ThreadPoolExecutor` that
|
||||
always uses native threads, even when threading is monkey-patched.
|
||||
|
||||
The ``Future`` objects returned from this object can be used
|
||||
with gevent waiting primitives like :func:`gevent.wait`.
|
||||
|
||||
.. caution:: If threading is *not* monkey-patched, then the ``Future``
|
||||
objects returned by this object are not guaranteed to work with
|
||||
:func:`~concurrent.futures.as_completed` and :func:`~concurrent.futures.wait`.
|
||||
The individual blocking methods like :meth:`~concurrent.futures.Future.result`
|
||||
and :meth:`~concurrent.futures.Future.exception` will always work.
|
||||
|
||||
.. versionadded:: 1.2a1
|
||||
This is a provisional API.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Takes the same arguments as ``concurrent.futures.ThreadPoolExecuter``, which
|
||||
vary between Python versions.
|
||||
|
||||
The first argument is always *max_workers*, the maximum number of
|
||||
threads to use. Most other arguments, while accepted, are ignored.
|
||||
"""
|
||||
super(ThreadPoolExecutor, self).__init__(*args, **kwargs)
|
||||
self._threadpool = ThreadPool(self._max_workers)
|
||||
|
||||
def submit(self, fn, *args, **kwargs): # pylint:disable=arguments-differ
|
||||
with self._shutdown_lock: # pylint:disable=not-context-manager
|
||||
if self._shutdown:
|
||||
raise RuntimeError('cannot schedule new futures after shutdown')
|
||||
|
||||
future = self._threadpool.spawn(fn, *args, **kwargs)
|
||||
return _FutureProxy(future)
|
||||
|
||||
def shutdown(self, wait=True, **kwargs): # pylint:disable=arguments-differ
|
||||
# In 3.9, this added ``cancel_futures=False``
|
||||
super(ThreadPoolExecutor, self).shutdown(wait, **kwargs)
|
||||
# XXX: We don't implement wait properly
|
||||
kill = getattr(self._threadpool, 'kill', None)
|
||||
if kill: # pylint:disable=using-constant-test
|
||||
self._threadpool.kill()
|
||||
self._threadpool = None
|
||||
|
||||
kill = shutdown # greentest compat
|
||||
|
||||
def _adjust_thread_count(self):
|
||||
# Does nothing. We don't want to spawn any "threads",
|
||||
# let the threadpool handle that.
|
||||
pass
|
||||
Reference in New Issue
Block a user