Merge "Fix process based executor task proxying-back events"

This commit is contained in:
Jenkins 2017-07-11 10:48:25 +00:00 committed by Gerrit Code Review
commit eea48a18d7
3 changed files with 87 additions and 9 deletions

View File

@ -34,6 +34,7 @@ import six
from taskflow.engines.action_engine import executor as base from taskflow.engines.action_engine import executor as base
from taskflow import logging from taskflow import logging
from taskflow import task as ta from taskflow import task as ta
from taskflow.types import notifier as nt
from taskflow.utils import iter_utils from taskflow.utils import iter_utils
from taskflow.utils import misc from taskflow.utils import misc
from taskflow.utils import schema_utils as su from taskflow.utils import schema_utils as su
@ -675,16 +676,38 @@ class ParallelProcessTaskExecutor(base.ParallelTaskExecutor):
# so that when the clone runs in another process that this task # so that when the clone runs in another process that this task
# can receive the same notifications (thus making it look like the # can receive the same notifications (thus making it look like the
# the notifications are transparently happening in this process). # the notifications are transparently happening in this process).
needed = set() proxy_event_types = set()
for (event_type, listeners) in task.notifier.listeners_iter(): for (event_type, listeners) in task.notifier.listeners_iter():
if listeners: if listeners:
needed.add(event_type) proxy_event_types.add(event_type)
if progress_callback is not None: if progress_callback is not None:
needed.add(ta.EVENT_UPDATE_PROGRESS) proxy_event_types.add(ta.EVENT_UPDATE_PROGRESS)
if needed: if nt.Notifier.ANY in proxy_event_types:
# NOTE(harlowja): If ANY is present, just have it be
# the **only** event registered, as all other events will be
# sent if ANY is registered (due to the nature of ANY sending
# all the things); if we also include the other event types
# in this set if ANY is present we will receive duplicate
# messages in this process (the one where the local
# task callbacks are being triggered). For example the
# emissions of the tasks notifier (that is running out
# of process) will for specific events send messages for
# its ANY event type **and** the specific event
# type (2 messages, when we just want one) which will
# cause > 1 notify() call on the local tasks notifier, which
# causes more local callback triggering than we want
# to actually happen.
proxy_event_types = set([nt.Notifier.ANY])
if proxy_event_types:
# This sender acts as our forwarding proxy target, it
# will be sent pickled to the process that will execute
# the needed task and it will do the work of using the
# channel object to send back messages to this process for
# dispatch into the local task.
sender = EventSender(channel) sender = EventSender(channel)
for event_type in needed: for event_type in proxy_event_types:
clone.notifier.register(event_type, sender) clone.notifier.register(event_type, sender)
return bool(proxy_event_types)
def register(): def register():
if progress_callback is not None: if progress_callback is not None:
@ -698,14 +721,17 @@ class ParallelProcessTaskExecutor(base.ParallelTaskExecutor):
progress_callback) progress_callback)
self._dispatcher.targets.pop(identity, None) self._dispatcher.targets.pop(identity, None)
rebind_task() should_register = rebind_task()
register() if should_register:
register()
try: try:
fut = self._executor.submit(func, clone, *args, **kwargs) fut = self._executor.submit(func, clone, *args, **kwargs)
except RuntimeError: except RuntimeError:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
deregister() if should_register:
deregister()
fut.atom = task fut.atom = task
fut.add_done_callback(deregister) if should_register:
fut.add_done_callback(deregister)
return fut return fut

View File

@ -1527,6 +1527,48 @@ class ParallelEngineWithProcessTest(EngineTaskTest,
max_workers=self._EXECUTOR_WORKERS, max_workers=self._EXECUTOR_WORKERS,
**kwargs) **kwargs)
def test_update_progress_notifications_proxied(self):
captured = collections.defaultdict(list)
def notify_me(event_type, details):
captured[event_type].append(details)
a = utils.MultiProgressingTask('a')
a.notifier.register(a.notifier.ANY, notify_me)
progress_chunks = list(x / 10.0 for x in range(1, 10))
e = self._make_engine(a, store={'progress_chunks': progress_chunks})
e.run()
self.assertEqual(11, len(captured[task.EVENT_UPDATE_PROGRESS]))
def test_custom_notifications_proxied(self):
captured = collections.defaultdict(list)
def notify_me(event_type, details):
captured[event_type].append(details)
a = utils.EmittingTask('a')
a.notifier.register(a.notifier.ANY, notify_me)
e = self._make_engine(a)
e.run()
self.assertEqual(1, len(captured['hi']))
self.assertEqual(2, len(captured[task.EVENT_UPDATE_PROGRESS]))
def test_just_custom_notifications_proxied(self):
captured = collections.defaultdict(list)
def notify_me(event_type, details):
captured[event_type].append(details)
a = utils.EmittingTask('a')
a.notifier.register('hi', notify_me)
e = self._make_engine(a)
e.run()
self.assertEqual(1, len(captured['hi']))
self.assertEqual(0, len(captured[task.EVENT_UPDATE_PROGRESS]))
class WorkerBasedEngineTest(EngineTaskTest, class WorkerBasedEngineTest(EngineTaskTest,
EngineMultipleResultsTest, EngineMultipleResultsTest,

View File

@ -19,6 +19,7 @@ import string
import threading import threading
import time import time
from oslo_utils import timeutils
import redis import redis
import six import six
@ -104,6 +105,15 @@ class DummyTask(task.Task):
pass pass
class EmittingTask(task.Task):
TASK_EVENTS = (task.EVENT_UPDATE_PROGRESS, 'hi')
def execute(self, *args, **kwargs):
self.notifier.notify('hi',
details={'sent_on': timeutils.utcnow(),
'args': args, 'kwargs': kwargs})
class AddOneSameProvidesRequires(task.Task): class AddOneSameProvidesRequires(task.Task):
default_provides = 'value' default_provides = 'value'