Source code for wbia.web.futures_utils.thread_actor
# -*- coding: utf-8 -*-
""" Implements ThreadActor """
from __future__ import absolute_import, division, print_function
from concurrent.futures import _base
from concurrent.futures import thread
from wbia.web.futures_utils import _base_actor
import queue
import threading
import weakref
import utool as ut
(print, rrr, profile) = ut.inject2(__name__)
# Most of this code is duplicated from the concurrent.futures.thread and
# concurrent.futures.process modules, writen by Brian Quinlan. The main
# difference is that we expose an `Actor` class which can be inherited from and
# provides the `executor` classmethod. This creates an asynchronously
# maintained instance of this class in a separate thread/process
__author__ = 'Jon Crall (erotemic@gmail.com)'
class _WorkItem(object):
def __init__(self, future, message):
self.future = future
self.message = message
def _thread_actor_eventloop(executor_reference, work_queue, _ActorClass, *args, **kwargs):
"""
actor event loop run in a separate thread.
Creates the instance of the actor (passing in the required *args, and
**kwargs). Then the eventloop starts and feeds the actor messages from the
_call_queue. Results are placed in the _result_queue, which are then placed
in Future objects.
"""
try:
actor = _ActorClass(*args, **kwargs)
while True:
work_item = work_queue.get(block=True)
if work_item is not None:
if work_item.future.set_running_or_notify_cancel():
# Send the message to the actor
try:
result = actor.handle(work_item.message)
except BaseException as e:
work_item.future.set_exception(e)
# Delete references to object.
del e
else:
work_item.future.set_result(result)
# Delete references to object. See issue16284
del work_item
continue
executor = executor_reference()
# Exit if:
# - The interpreter is shutting down OR
# - The executor that owns the worker has been collected OR
# - The executor that owns the worker has been shutdown.
if thread._shutdown or executor is None or executor._shutdown:
# Notice other workers
work_queue.put(None)
return
del executor
except BaseException:
_base.LOGGER.critical('Exception in worker', exc_info=True)
[docs]class ThreadActorExecutor(_base_actor.ActorExecutor):
def __init__(self, _ActorClass, *args, **kwargs):
"""Initializes a new ThreadPoolExecutor instance.
"""
self._ActorClass = _ActorClass
self._work_queue = queue.Queue()
self._threads = set()
self._shutdown = False
self._shutdown_lock = threading.Lock()
self._did_initialize = False
if args or kwargs:
# If given actor initialization args we must start the Actor
# immediately. Otherwise just wait until we get a message
self._initialize_actor(*args, **kwargs)
[docs] def post(self, message):
with self._shutdown_lock:
if self._shutdown:
raise RuntimeError('cannot schedule new futures after shutdown')
f = _base.Future()
w = _WorkItem(f, message)
self._work_queue.put(w)
self._initialize_actor()
return f
post.__doc__ = _base_actor.ActorExecutor.post.__doc__
def _initialize_actor(self, *args, **kwargs):
# When the executor gets lost, the weakref callback will wake up
# the worker threads.
def weakref_cb(_, q=self._work_queue):
q.put(None)
# We only maintain one thread for an actor
if len(self._threads) < 1:
assert self._did_initialize is False, 'only initialize actor once'
self._did_initialize = True
t = threading.Thread(
target=_thread_actor_eventloop,
args=(weakref.ref(self, weakref_cb), self._work_queue, self._ActorClass)
+ args,
kwargs=kwargs,
)
t.daemon = True
t.start()
self._threads.add(t)
thread._threads_queues[t] = self._work_queue
[docs] def shutdown(self, wait=True):
with self._shutdown_lock:
self._shutdown = True
self._work_queue.put(None)
if wait:
for t in self._threads:
t.join()
shutdown.__doc__ = _base.Executor.shutdown.__doc__
[docs]class ThreadActor(_base_actor.Actor):
[docs] @classmethod
def executor(cls, *args, **kwargs):
return ThreadActorExecutor(cls, *args, **kwargs)
# executor.__doc__ = _base_actor.Actor.executor.__doc___
# ThreadActor.__doc__ = _base_actor.Actor.__doc___