Remove extra runner layer and just use use machine in engine

Just directly use the built machine in the action engine and
avoid having another layer of abstraction that does not provide
that much value. This makes the code cleaner, and more easy to
understand (and so-on).

Change-Id: Iae1279098112254338258c1941c15889f1ad1a79
This commit is contained in:
Joshua Harlow
2015-07-16 17:10:09 -07:00
committed by Joshua Harlow
parent 69c3b04e37
commit 054ca2a6e2
6 changed files with 225 additions and 228 deletions

View File

@@ -258,9 +258,10 @@ Execution
The graph (and helper objects) previously created are now used for guiding The graph (and helper objects) previously created are now used for guiding
further execution (see :py:func:`~taskflow.engines.base.Engine.run`). The further execution (see :py:func:`~taskflow.engines.base.Engine.run`). The
flow is put into the ``RUNNING`` :doc:`state <states>` and a flow is put into the ``RUNNING`` :doc:`state <states>` and a
:py:class:`~taskflow.engines.action_engine.runner.Runner` implementation :py:class:`~taskflow.engines.action_engine.builder.MachineBuilder` state
object starts to take over and begins going through the stages listed machine object and runner object are built (using the `automaton`_ library).
below (for a more visual diagram/representation see That machine and associated runner then starts to take over and begins going
through the stages listed below (for a more visual diagram/representation see
the :ref:`engine state diagram <engine states>`). the :ref:`engine state diagram <engine states>`).
.. note:: .. note::
@@ -338,8 +339,8 @@ above stages will be restarted and resuming will occur).
Finishing Finishing
--------- ---------
At this point the At this point the machine (and runner) that was built using the
:py:class:`~taskflow.engines.action_engine.runner.Runner` has :py:class:`~taskflow.engines.action_engine.builder.MachineBuilder` class has
now finished successfully, failed, or the execution was suspended. Depending on now finished successfully, failed, or the execution was suspended. Depending on
which one of these occurs will cause the flow to enter a new state (typically which one of these occurs will cause the flow to enter a new state (typically
one of ``FAILURE``, ``SUSPENDED``, ``SUCCESS`` or ``REVERTED``). one of ``FAILURE``, ``SUSPENDED``, ``SUCCESS`` or ``REVERTED``).
@@ -365,9 +366,9 @@ this performs is a transition of the flow state from ``RUNNING`` into a
``SUSPENDING`` state (which will later transition into a ``SUSPENDED`` state). ``SUSPENDING`` state (which will later transition into a ``SUSPENDED`` state).
Since an engine may be remotely executing atoms (or locally executing them) Since an engine may be remotely executing atoms (or locally executing them)
and there is currently no preemption what occurs is that the engines and there is currently no preemption what occurs is that the engines
:py:class:`~taskflow.engines.action_engine.runner.Runner` state machine will :py:class:`~taskflow.engines.action_engine.builder.MachineBuilder` state
detect this transition into ``SUSPENDING`` has occurred and the state machine will detect this transition into ``SUSPENDING`` has occurred and the
machine will avoid scheduling new work (it will though let active work state machine will avoid scheduling new work (it will though let active work
continue). After the current work has finished the engine will continue). After the current work has finished the engine will
transition from ``SUSPENDING`` into ``SUSPENDED`` and return from its transition from ``SUSPENDING`` into ``SUSPENDED`` and return from its
:py:func:`~taskflow.engines.base.Engine.run` method. :py:func:`~taskflow.engines.base.Engine.run` method.
@@ -444,10 +445,10 @@ Components
cycle). cycle).
.. automodule:: taskflow.engines.action_engine.analyzer .. automodule:: taskflow.engines.action_engine.analyzer
.. automodule:: taskflow.engines.action_engine.builder
.. automodule:: taskflow.engines.action_engine.compiler .. automodule:: taskflow.engines.action_engine.compiler
.. automodule:: taskflow.engines.action_engine.completer .. automodule:: taskflow.engines.action_engine.completer
.. automodule:: taskflow.engines.action_engine.executor .. automodule:: taskflow.engines.action_engine.executor
.. automodule:: taskflow.engines.action_engine.runner
.. automodule:: taskflow.engines.action_engine.runtime .. automodule:: taskflow.engines.action_engine.runtime
.. automodule:: taskflow.engines.action_engine.scheduler .. automodule:: taskflow.engines.action_engine.scheduler
.. autoclass:: taskflow.engines.action_engine.scopes.ScopeWalker .. autoclass:: taskflow.engines.action_engine.scopes.ScopeWalker
@@ -462,6 +463,7 @@ Hierarchy
taskflow.engines.worker_based.engine.WorkerBasedActionEngine taskflow.engines.worker_based.engine.WorkerBasedActionEngine
:parts: 1 :parts: 1
.. _automaton: http://docs.openstack.org/developer/automaton/
.. _multiprocessing: https://docs.python.org/2/library/multiprocessing.html .. _multiprocessing: https://docs.python.org/2/library/multiprocessing.html
.. _future: https://docs.python.org/dev/library/concurrent.futures.html#future-objects .. _future: https://docs.python.org/dev/library/concurrent.futures.html#future-objects
.. _executor: https://docs.python.org/dev/library/concurrent.futures.html#concurrent.futures.Executor .. _executor: https://docs.python.org/dev/library/concurrent.futures.html#concurrent.futures.Executor

View File

@@ -16,35 +16,34 @@
from automaton import machines from automaton import machines
from automaton import runners
from taskflow import logging from taskflow import logging
from taskflow import states as st from taskflow import states as st
from taskflow.types import failure from taskflow.types import failure
# Waiting state timeout (in seconds). # Default waiting state timeout (in seconds).
_WAITING_TIMEOUT = 60 WAITING_TIMEOUT = 60
# Meta states the state machine uses. # Meta states the state machine uses.
_UNDEFINED = 'UNDEFINED' UNDEFINED = 'UNDEFINED'
_GAME_OVER = 'GAME_OVER' GAME_OVER = 'GAME_OVER'
_META_STATES = (_GAME_OVER, _UNDEFINED) META_STATES = (GAME_OVER, UNDEFINED)
# Event name constants the state machine uses. # Event name constants the state machine uses.
_SCHEDULE = 'schedule_next' SCHEDULE = 'schedule_next'
_WAIT = 'wait_finished' WAIT = 'wait_finished'
_ANALYZE = 'examine_finished' ANALYZE = 'examine_finished'
_FINISH = 'completed' FINISH = 'completed'
_FAILED = 'failed' FAILED = 'failed'
_SUSPENDED = 'suspended' SUSPENDED = 'suspended'
_SUCCESS = 'success' SUCCESS = 'success'
_REVERTED = 'reverted' REVERTED = 'reverted'
_START = 'start' START = 'start'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class _MachineMemory(object): class MachineMemory(object):
"""State machine memory.""" """State machine memory."""
def __init__(self): def __init__(self):
@@ -54,31 +53,31 @@ class _MachineMemory(object):
self.done = set() self.done = set()
class Runner(object): class MachineBuilder(object):
"""State machine *builder* + *runner* that powers the engine components. """State machine *builder* that powers the engine components.
NOTE(harlowja): the machine (states and events that will trigger NOTE(harlowja): the machine (states and events that will trigger
transitions) that this builds is represented by the following transitions) that this builds is represented by the following
table:: table::
+--------------+------------------+------------+----------+---------+ +--------------+------------------+------------+----------+---------+
Start | Event | End | On Enter | On Exit | Start | Event | End | On Enter | On Exit |
+--------------+------------------+------------+----------+---------+ +--------------+------------------+------------+----------+---------+
ANALYZING | completed | GAME_OVER | | | ANALYZING | completed | GAME_OVER | . | . |
ANALYZING | schedule_next | SCHEDULING | | | ANALYZING | schedule_next | SCHEDULING | . | . |
ANALYZING | wait_finished | WAITING | | | ANALYZING | wait_finished | WAITING | . | . |
FAILURE[$] | | | | | FAILURE[$] | . | . | . | . |
GAME_OVER | failed | FAILURE | | | GAME_OVER | failed | FAILURE | . | . |
GAME_OVER | reverted | REVERTED | | | GAME_OVER | reverted | REVERTED | . | . |
GAME_OVER | success | SUCCESS | | | GAME_OVER | success | SUCCESS | . | . |
GAME_OVER | suspended | SUSPENDED | | | GAME_OVER | suspended | SUSPENDED | . | . |
RESUMING | schedule_next | SCHEDULING | | | RESUMING | schedule_next | SCHEDULING | . | . |
REVERTED[$] | | | | | REVERTED[$] | . | . | . | . |
SCHEDULING | wait_finished | WAITING | | | SCHEDULING | wait_finished | WAITING | . | . |
SUCCESS[$] | | | | | SUCCESS[$] | . | . | . | . |
SUSPENDED[$] | | | | | SUSPENDED[$] | . | . | . | . |
UNDEFINED[^] | start | RESUMING | | | UNDEFINED[^] | start | RESUMING | . | . |
WAITING | examine_finished | ANALYZING | | | WAITING | examine_finished | ANALYZING | . | . |
+--------------+------------------+------------+----------+---------+ +--------------+------------------+------------+----------+---------+
Between any of these yielded states (minus ``GAME_OVER`` and ``UNDEFINED``) Between any of these yielded states (minus ``GAME_OVER`` and ``UNDEFINED``)
@@ -91,11 +90,6 @@ class Runner(object):
tasks in parallel, this enables parallel running and/or reversion. tasks in parallel, this enables parallel running and/or reversion.
""" """
# Informational states this action yields while running, not useful to
# have the engine record but useful to provide to end-users when doing
# execution iterations.
ignorable_states = (st.SCHEDULING, st.WAITING, st.RESUMING, st.ANALYZING)
def __init__(self, runtime, waiter): def __init__(self, runtime, waiter):
self._runtime = runtime self._runtime = runtime
self._analyzer = runtime.analyzer self._analyzer = runtime.analyzer
@@ -104,21 +98,21 @@ class Runner(object):
self._storage = runtime.storage self._storage = runtime.storage
self._waiter = waiter self._waiter = waiter
def runnable(self):
"""Checks if the storage says the flow is still runnable/running."""
return self._storage.get_flow_state() == st.RUNNING
def build(self, timeout=None): def build(self, timeout=None):
"""Builds a state-machine (that can be/is used during running).""" """Builds a state-machine (that is used during running)."""
memory = _MachineMemory() memory = MachineMemory()
if timeout is None: if timeout is None:
timeout = _WAITING_TIMEOUT timeout = WAITING_TIMEOUT
# Cache some local functions/methods... # Cache some local functions/methods...
do_schedule = self._scheduler.schedule do_schedule = self._scheduler.schedule
do_complete = self._completer.complete do_complete = self._completer.complete
def is_runnable():
# Checks if the storage says the flow is still runnable...
return self._storage.get_flow_state() == st.RUNNING
def iter_next_nodes(target_node=None): def iter_next_nodes(target_node=None):
# Yields and filters and tweaks the next nodes to execute... # Yields and filters and tweaks the next nodes to execute...
maybe_nodes = self._analyzer.get_next_nodes(node=target_node) maybe_nodes = self._analyzer.get_next_nodes(node=target_node)
@@ -134,7 +128,7 @@ class Runner(object):
# that are now ready to be ran. # that are now ready to be ran.
memory.next_nodes.update(self._completer.resume()) memory.next_nodes.update(self._completer.resume())
memory.next_nodes.update(iter_next_nodes()) memory.next_nodes.update(iter_next_nodes())
return _SCHEDULE return SCHEDULE
def game_over(old_state, new_state, event): def game_over(old_state, new_state, event):
# This reaction function is mainly a intermediary delegation # This reaction function is mainly a intermediary delegation
@@ -142,13 +136,13 @@ class Runner(object):
# the appropriate handler that will deal with the memory values, # the appropriate handler that will deal with the memory values,
# it is *always* called before the final state is entered. # it is *always* called before the final state is entered.
if memory.failures: if memory.failures:
return _FAILED return FAILED
if any(1 for node in iter_next_nodes()): if any(1 for node in iter_next_nodes()):
return _SUSPENDED return SUSPENDED
elif self._analyzer.is_success(): elif self._analyzer.is_success():
return _SUCCESS return SUCCESS
else: else:
return _REVERTED return REVERTED
def schedule(old_state, new_state, event): def schedule(old_state, new_state, event):
# This reaction function starts to schedule the memory's next # This reaction function starts to schedule the memory's next
@@ -156,14 +150,14 @@ class Runner(object):
# if the user of this engine has requested the engine/storage # if the user of this engine has requested the engine/storage
# that holds this information to stop or suspend); handles failures # that holds this information to stop or suspend); handles failures
# that occur during this process safely... # that occur during this process safely...
if self.runnable() and memory.next_nodes: if is_runnable() and memory.next_nodes:
not_done, failures = do_schedule(memory.next_nodes) not_done, failures = do_schedule(memory.next_nodes)
if not_done: if not_done:
memory.not_done.update(not_done) memory.not_done.update(not_done)
if failures: if failures:
memory.failures.extend(failures) memory.failures.extend(failures)
memory.next_nodes.clear() memory.next_nodes.clear()
return _WAIT return WAIT
def wait(old_state, new_state, event): def wait(old_state, new_state, event):
# TODO(harlowja): maybe we should start doing 'yield from' this # TODO(harlowja): maybe we should start doing 'yield from' this
@@ -173,7 +167,7 @@ class Runner(object):
done, not_done = self._waiter(memory.not_done, timeout=timeout) done, not_done = self._waiter(memory.not_done, timeout=timeout)
memory.done.update(done) memory.done.update(done)
memory.not_done = not_done memory.not_done = not_done
return _ANALYZE return ANALYZE
def analyze(old_state, new_state, event): def analyze(old_state, new_state, event):
# This reaction function is responsible for analyzing all nodes # This reaction function is responsible for analyzing all nodes
@@ -215,13 +209,13 @@ class Runner(object):
memory.failures.append(failure.Failure()) memory.failures.append(failure.Failure())
else: else:
next_nodes.update(more_nodes) next_nodes.update(more_nodes)
if self.runnable() and next_nodes and not memory.failures: if is_runnable() and next_nodes and not memory.failures:
memory.next_nodes.update(next_nodes) memory.next_nodes.update(next_nodes)
return _SCHEDULE return SCHEDULE
elif memory.not_done: elif memory.not_done:
return _WAIT return WAIT
else: else:
return _FINISH return FINISH
def on_exit(old_state, event): def on_exit(old_state, event):
LOG.debug("Exiting old state '%s' in response to event '%s'", LOG.debug("Exiting old state '%s' in response to event '%s'",
@@ -239,8 +233,8 @@ class Runner(object):
watchers['on_enter'] = on_enter watchers['on_enter'] = on_enter
m = machines.FiniteMachine() m = machines.FiniteMachine()
m.add_state(_GAME_OVER, **watchers) m.add_state(GAME_OVER, **watchers)
m.add_state(_UNDEFINED, **watchers) m.add_state(UNDEFINED, **watchers)
m.add_state(st.ANALYZING, **watchers) m.add_state(st.ANALYZING, **watchers)
m.add_state(st.RESUMING, **watchers) m.add_state(st.RESUMING, **watchers)
m.add_state(st.REVERTED, terminal=True, **watchers) m.add_state(st.REVERTED, terminal=True, **watchers)
@@ -249,38 +243,25 @@ class Runner(object):
m.add_state(st.SUSPENDED, terminal=True, **watchers) m.add_state(st.SUSPENDED, terminal=True, **watchers)
m.add_state(st.WAITING, **watchers) m.add_state(st.WAITING, **watchers)
m.add_state(st.FAILURE, terminal=True, **watchers) m.add_state(st.FAILURE, terminal=True, **watchers)
m.default_start_state = _UNDEFINED m.default_start_state = UNDEFINED
m.add_transition(_GAME_OVER, st.REVERTED, _REVERTED) m.add_transition(GAME_OVER, st.REVERTED, REVERTED)
m.add_transition(_GAME_OVER, st.SUCCESS, _SUCCESS) m.add_transition(GAME_OVER, st.SUCCESS, SUCCESS)
m.add_transition(_GAME_OVER, st.SUSPENDED, _SUSPENDED) m.add_transition(GAME_OVER, st.SUSPENDED, SUSPENDED)
m.add_transition(_GAME_OVER, st.FAILURE, _FAILED) m.add_transition(GAME_OVER, st.FAILURE, FAILED)
m.add_transition(_UNDEFINED, st.RESUMING, _START) m.add_transition(UNDEFINED, st.RESUMING, START)
m.add_transition(st.ANALYZING, _GAME_OVER, _FINISH) m.add_transition(st.ANALYZING, GAME_OVER, FINISH)
m.add_transition(st.ANALYZING, st.SCHEDULING, _SCHEDULE) m.add_transition(st.ANALYZING, st.SCHEDULING, SCHEDULE)
m.add_transition(st.ANALYZING, st.WAITING, _WAIT) m.add_transition(st.ANALYZING, st.WAITING, WAIT)
m.add_transition(st.RESUMING, st.SCHEDULING, _SCHEDULE) m.add_transition(st.RESUMING, st.SCHEDULING, SCHEDULE)
m.add_transition(st.SCHEDULING, st.WAITING, _WAIT) m.add_transition(st.SCHEDULING, st.WAITING, WAIT)
m.add_transition(st.WAITING, st.ANALYZING, _ANALYZE) m.add_transition(st.WAITING, st.ANALYZING, ANALYZE)
m.add_reaction(_GAME_OVER, _FINISH, game_over) m.add_reaction(GAME_OVER, FINISH, game_over)
m.add_reaction(st.ANALYZING, _ANALYZE, analyze) m.add_reaction(st.ANALYZING, ANALYZE, analyze)
m.add_reaction(st.RESUMING, _START, resume) m.add_reaction(st.RESUMING, START, resume)
m.add_reaction(st.SCHEDULING, _SCHEDULE, schedule) m.add_reaction(st.SCHEDULING, SCHEDULE, schedule)
m.add_reaction(st.WAITING, _WAIT, wait) m.add_reaction(st.WAITING, WAIT, wait)
m.freeze() m.freeze()
return (m, memory)
r = runners.FiniteRunner(m)
return (m, r, memory)
def run_iter(self, timeout=None):
"""Runs iteratively using a locally built state machine."""
machine, runner, memory = self.build(timeout=timeout)
for (_prior_state, new_state) in runner.run_iter(_START):
# NOTE(harlowja): skip over meta-states.
if new_state not in _META_STATES:
if new_state == st.FAILURE:
yield (new_state, memory.failures)
else:
yield (new_state, [])

View File

@@ -19,6 +19,7 @@ import contextlib
import itertools import itertools
import threading import threading
from automaton import runners
from concurrent import futures from concurrent import futures
import fasteners import fasteners
import networkx as nx import networkx as nx
@@ -26,6 +27,7 @@ from oslo_utils import excutils
from oslo_utils import strutils from oslo_utils import strutils
import six import six
from taskflow.engines.action_engine import builder
from taskflow.engines.action_engine import compiler from taskflow.engines.action_engine import compiler
from taskflow.engines.action_engine import executor from taskflow.engines.action_engine import executor
from taskflow.engines.action_engine import runtime from taskflow.engines.action_engine import runtime
@@ -59,9 +61,9 @@ class ActionEngine(base.Engine):
This engine compiles the flow (and any subflows) into a compilation unit This engine compiles the flow (and any subflows) into a compilation unit
which contains the full runtime definition to be executed and then uses which contains the full runtime definition to be executed and then uses
this compilation unit in combination with the executor, runtime, runner this compilation unit in combination with the executor, runtime, machine
and storage classes to attempt to run your flow (and any subflows & builder and storage classes to attempt to run your flow (and any
contained atoms) to completion. subflows & contained atoms) to completion.
NOTE(harlowja): during this process it is permissible and valid to have a NOTE(harlowja): during this process it is permissible and valid to have a
task or multiple tasks in the execution graph fail (at the same time even), task or multiple tasks in the execution graph fail (at the same time even),
@@ -77,6 +79,15 @@ class ActionEngine(base.Engine):
failure/s that were captured (if any) to get reraised. failure/s that were captured (if any) to get reraised.
""" """
IGNORABLE_STATES = frozenset(
itertools.chain([states.SCHEDULING, states.WAITING, states.RESUMING,
states.ANALYZING], builder.META_STATES))
"""
Informational states this engines internal machine yields back while
running, not useful to have the engine record but useful to provide to
end-users when doing execution iterations via :py:meth:`.run_iter`.
"""
def __init__(self, flow, flow_detail, backend, options): def __init__(self, flow, flow_detail, backend, options):
super(ActionEngine, self).__init__(flow, flow_detail, backend, options) super(ActionEngine, self).__init__(flow, flow_detail, backend, options)
self._runtime = None self._runtime = None
@@ -151,20 +162,20 @@ class ActionEngine(base.Engine):
def run_iter(self, timeout=None): def run_iter(self, timeout=None):
"""Runs the engine using iteration (or die trying). """Runs the engine using iteration (or die trying).
:param timeout: timeout to wait for any tasks to complete (this timeout :param timeout: timeout to wait for any atoms to complete (this timeout
will be used during the waiting period that occurs after the will be used during the waiting period that occurs after the
waiting state is yielded when unfinished tasks are being waited waiting state is yielded when unfinished atoms are being waited
for). on).
Instead of running to completion in a blocking manner, this will Instead of running to completion in a blocking manner, this will
return a generator which will yield back the various states that the return a generator which will yield back the various states that the
engine is going through (and can be used to run multiple engines at engine is going through (and can be used to run multiple engines at
once using a generator per engine). the iterator returned also once using a generator per engine). The iterator returned also
responds to the send() method from pep-0342 and will attempt to suspend responds to the ``send()`` method from :pep:`0342` and will attempt to
itself if a truthy value is sent in (the suspend may be delayed until suspend itself if a truthy value is sent in (the suspend may be
all active tasks have finished). delayed until all active atoms have finished).
NOTE(harlowja): using the run_iter method will **not** retain the NOTE(harlowja): using the ``run_iter`` method will **not** retain the
engine lock while executing so the user should ensure that there is engine lock while executing so the user should ensure that there is
only one entity using a returned engine iterator (one per engine) at a only one entity using a returned engine iterator (one per engine) at a
given time. given time.
@@ -172,19 +183,24 @@ class ActionEngine(base.Engine):
self.compile() self.compile()
self.prepare() self.prepare()
self.validate() self.validate()
runner = self._runtime.runner
last_state = None last_state = None
with _start_stop(self._task_executor, self._retry_executor): with _start_stop(self._task_executor, self._retry_executor):
self._change_state(states.RUNNING) self._change_state(states.RUNNING)
try: try:
closed = False closed = False
for (last_state, failures) in runner.run_iter(timeout=timeout): machine, memory = self._runtime.builder.build(timeout=timeout)
if failures: r = runners.FiniteRunner(machine)
failure.Failure.reraise_if_any(failures) for (_prior_state, new_state) in r.run_iter(builder.START):
last_state = new_state
# NOTE(harlowja): skip over meta-states.
if new_state in builder.META_STATES:
continue
if new_state == states.FAILURE:
failure.Failure.reraise_if_any(memory.failures)
if closed: if closed:
continue continue
try: try:
try_suspend = yield last_state try_suspend = yield new_state
except GeneratorExit: except GeneratorExit:
# The generator was closed, attempt to suspend and # The generator was closed, attempt to suspend and
# continue looping until we have cleanly closed up # continue looping until we have cleanly closed up
@@ -198,9 +214,8 @@ class ActionEngine(base.Engine):
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
self._change_state(states.FAILURE) self._change_state(states.FAILURE)
else: else:
ignorable_states = getattr(runner, 'ignorable_states', []) if last_state and last_state not in self.IGNORABLE_STATES:
if last_state and last_state not in ignorable_states: self._change_state(new_state)
self._change_state(last_state)
if last_state not in self.NO_RERAISING_STATES: if last_state not in self.NO_RERAISING_STATES:
it = itertools.chain( it = itertools.chain(
six.itervalues(self.storage.get_failures()), six.itervalues(self.storage.get_failures()),

View File

@@ -21,11 +21,11 @@ from futurist import waiters
from taskflow.engines.action_engine.actions import retry as ra from taskflow.engines.action_engine.actions import retry as ra
from taskflow.engines.action_engine.actions import task as ta from taskflow.engines.action_engine.actions import task as ta
from taskflow.engines.action_engine import analyzer as an from taskflow.engines.action_engine import analyzer as an
from taskflow.engines.action_engine import builder as bu
from taskflow.engines.action_engine import completer as co from taskflow.engines.action_engine import completer as co
from taskflow.engines.action_engine import runner as ru
from taskflow.engines.action_engine import scheduler as sched from taskflow.engines.action_engine import scheduler as sched
from taskflow.engines.action_engine import scopes as sc from taskflow.engines.action_engine import scopes as sc
from taskflow import flow as flow_type from taskflow import flow
from taskflow import states as st from taskflow import states as st
from taskflow import task from taskflow import task
from taskflow.utils import misc from taskflow.utils import misc
@@ -89,7 +89,7 @@ class Runtime(object):
# is able to run (or should not) ensure we retain it and use # is able to run (or should not) ensure we retain it and use
# it later as needed. # it later as needed.
u_v_data = execution_graph.adj[previous_atom][atom] u_v_data = execution_graph.adj[previous_atom][atom]
u_v_decider = u_v_data.get(flow_type.LINK_DECIDER) u_v_decider = u_v_data.get(flow.LINK_DECIDER)
if u_v_decider is not None: if u_v_decider is not None:
edge_deciders[previous_atom.name] = u_v_decider edge_deciders[previous_atom.name] = u_v_decider
metadata['scope_walker'] = walker metadata['scope_walker'] = walker
@@ -114,8 +114,8 @@ class Runtime(object):
return an.Analyzer(self) return an.Analyzer(self)
@misc.cachedproperty @misc.cachedproperty
def runner(self): def builder(self):
return ru.Runner(self, waiters.wait_for_any) return bu.MachineBuilder(self, waiters.wait_for_any)
@misc.cachedproperty @misc.cachedproperty
def completer(self): def completer(self):

View File

@@ -15,11 +15,12 @@
# under the License. # under the License.
from automaton import exceptions as excp from automaton import exceptions as excp
from automaton import runners
import six import six
from taskflow.engines.action_engine import builder
from taskflow.engines.action_engine import compiler from taskflow.engines.action_engine import compiler
from taskflow.engines.action_engine import executor from taskflow.engines.action_engine import executor
from taskflow.engines.action_engine import runner
from taskflow.engines.action_engine import runtime from taskflow.engines.action_engine import runtime
from taskflow.patterns import linear_flow as lf from taskflow.patterns import linear_flow as lf
from taskflow import states as st from taskflow import states as st
@@ -30,7 +31,8 @@ from taskflow.types import notifier
from taskflow.utils import persistence_utils as pu from taskflow.utils import persistence_utils as pu
class _RunnerTestMixin(object): class BuildersTest(test.TestCase):
def _make_runtime(self, flow, initial_state=None): def _make_runtime(self, flow, initial_state=None):
compilation = compiler.PatternCompiler(flow).compile() compilation = compiler.PatternCompiler(flow).compile()
flow_detail = pu.create_flow_detail(flow) flow_detail = pu.create_flow_detail(flow)
@@ -51,17 +53,11 @@ class _RunnerTestMixin(object):
r.compile() r.compile()
return r return r
def _make_machine(self, flow, initial_state=None):
class RunnerTest(test.TestCase, _RunnerTestMixin): runtime = self._make_runtime(flow, initial_state=initial_state)
def test_running(self): machine, memory = runtime.builder.build()
flow = lf.Flow("root") machine_runner = runners.FiniteRunner(machine)
flow.add(*test_utils.make_many(1)) return (runtime, machine, memory, machine_runner)
rt = self._make_runtime(flow, initial_state=st.RUNNING)
self.assertTrue(rt.runner.runnable())
rt = self._make_runtime(flow, initial_state=st.SUSPENDED)
self.assertFalse(rt.runner.runnable())
def test_run_iterations(self): def test_run_iterations(self):
flow = lf.Flow("root") flow = lf.Flow("root")
@@ -69,29 +65,32 @@ class RunnerTest(test.TestCase, _RunnerTestMixin):
1, task_cls=test_utils.TaskNoRequiresNoReturns) 1, task_cls=test_utils.TaskNoRequiresNoReturns)
flow.add(*tasks) flow.add(*tasks)
rt = self._make_runtime(flow, initial_state=st.RUNNING) runtime, machine, memory, machine_runner = self._make_machine(
self.assertTrue(rt.runner.runnable()) flow, initial_state=st.RUNNING)
it = rt.runner.run_iter() it = machine_runner.run_iter(builder.START)
state, failures = six.next(it) prior_state, new_state = six.next(it)
self.assertEqual(st.RESUMING, state) self.assertEqual(st.RESUMING, new_state)
self.assertEqual(0, len(failures)) self.assertEqual(0, len(memory.failures))
state, failures = six.next(it) prior_state, new_state = six.next(it)
self.assertEqual(st.SCHEDULING, state) self.assertEqual(st.SCHEDULING, new_state)
self.assertEqual(0, len(failures)) self.assertEqual(0, len(memory.failures))
state, failures = six.next(it) prior_state, new_state = six.next(it)
self.assertEqual(st.WAITING, state) self.assertEqual(st.WAITING, new_state)
self.assertEqual(0, len(failures)) self.assertEqual(0, len(memory.failures))
state, failures = six.next(it) prior_state, new_state = six.next(it)
self.assertEqual(st.ANALYZING, state) self.assertEqual(st.ANALYZING, new_state)
self.assertEqual(0, len(failures)) self.assertEqual(0, len(memory.failures))
state, failures = six.next(it) prior_state, new_state = six.next(it)
self.assertEqual(st.SUCCESS, state) self.assertEqual(builder.GAME_OVER, new_state)
self.assertEqual(0, len(failures)) self.assertEqual(0, len(memory.failures))
prior_state, new_state = six.next(it)
self.assertEqual(st.SUCCESS, new_state)
self.assertEqual(0, len(memory.failures))
self.assertRaises(StopIteration, six.next, it) self.assertRaises(StopIteration, six.next, it)
@@ -101,15 +100,15 @@ class RunnerTest(test.TestCase, _RunnerTestMixin):
1, task_cls=test_utils.TaskWithFailure) 1, task_cls=test_utils.TaskWithFailure)
flow.add(*tasks) flow.add(*tasks)
rt = self._make_runtime(flow, initial_state=st.RUNNING) runtime, machine, memory, machine_runner = self._make_machine(
self.assertTrue(rt.runner.runnable()) flow, initial_state=st.RUNNING)
transitions = list(rt.runner.run_iter()) transitions = list(machine_runner.run_iter(builder.START))
state, failures = transitions[-1] prior_state, new_state = transitions[-1]
self.assertEqual(st.REVERTED, state) self.assertEqual(st.REVERTED, new_state)
self.assertEqual([], failures) self.assertEqual([], memory.failures)
self.assertEqual(st.REVERTED,
self.assertEqual(st.REVERTED, rt.storage.get_atom_state(tasks[0].name)) runtime.storage.get_atom_state(tasks[0].name))
def test_run_iterations_failure(self): def test_run_iterations_failure(self):
flow = lf.Flow("root") flow = lf.Flow("root")
@@ -117,18 +116,17 @@ class RunnerTest(test.TestCase, _RunnerTestMixin):
1, task_cls=test_utils.NastyFailingTask) 1, task_cls=test_utils.NastyFailingTask)
flow.add(*tasks) flow.add(*tasks)
rt = self._make_runtime(flow, initial_state=st.RUNNING) runtime, machine, memory, machine_runner = self._make_machine(
self.assertTrue(rt.runner.runnable()) flow, initial_state=st.RUNNING)
transitions = list(rt.runner.run_iter()) transitions = list(machine_runner.run_iter(builder.START))
state, failures = transitions[-1] prior_state, new_state = transitions[-1]
self.assertEqual(st.FAILURE, state) self.assertEqual(st.FAILURE, new_state)
self.assertEqual(1, len(failures)) self.assertEqual(1, len(memory.failures))
failure = failures[0] failure = memory.failures[0]
self.assertTrue(failure.check(RuntimeError)) self.assertTrue(failure.check(RuntimeError))
self.assertEqual(st.REVERT_FAILURE, self.assertEqual(st.REVERT_FAILURE,
rt.storage.get_atom_state(tasks[0].name)) runtime.storage.get_atom_state(tasks[0].name))
def test_run_iterations_suspended(self): def test_run_iterations_suspended(self):
flow = lf.Flow("root") flow = lf.Flow("root")
@@ -136,20 +134,22 @@ class RunnerTest(test.TestCase, _RunnerTestMixin):
2, task_cls=test_utils.TaskNoRequiresNoReturns) 2, task_cls=test_utils.TaskNoRequiresNoReturns)
flow.add(*tasks) flow.add(*tasks)
rt = self._make_runtime(flow, initial_state=st.RUNNING) runtime, machine, memory, machine_runner = self._make_machine(
self.assertTrue(rt.runner.runnable()) flow, initial_state=st.RUNNING)
transitions = [] transitions = []
for state, failures in rt.runner.run_iter(): for prior_state, new_state in machine_runner.run_iter(builder.START):
transitions.append((state, failures)) transitions.append((new_state, memory.failures))
if state == st.ANALYZING: if new_state == st.ANALYZING:
rt.storage.set_flow_state(st.SUSPENDED) runtime.storage.set_flow_state(st.SUSPENDED)
state, failures = transitions[-1] state, failures = transitions[-1]
self.assertEqual(st.SUSPENDED, state) self.assertEqual(st.SUSPENDED, state)
self.assertEqual([], failures) self.assertEqual([], failures)
self.assertEqual(st.SUCCESS, rt.storage.get_atom_state(tasks[0].name)) self.assertEqual(st.SUCCESS,
self.assertEqual(st.PENDING, rt.storage.get_atom_state(tasks[1].name)) runtime.storage.get_atom_state(tasks[0].name))
self.assertEqual(st.PENDING,
runtime.storage.get_atom_state(tasks[1].name))
def test_run_iterations_suspended_failure(self): def test_run_iterations_suspended_failure(self):
flow = lf.Flow("root") flow = lf.Flow("root")
@@ -160,46 +160,44 @@ class RunnerTest(test.TestCase, _RunnerTestMixin):
1, task_cls=test_utils.TaskNoRequiresNoReturns, offset=1) 1, task_cls=test_utils.TaskNoRequiresNoReturns, offset=1)
flow.add(*happy_tasks) flow.add(*happy_tasks)
rt = self._make_runtime(flow, initial_state=st.RUNNING) runtime, machine, memory, machine_runner = self._make_machine(
self.assertTrue(rt.runner.runnable()) flow, initial_state=st.RUNNING)
transitions = [] transitions = []
for state, failures in rt.runner.run_iter(): for prior_state, new_state in machine_runner.run_iter(builder.START):
transitions.append((state, failures)) transitions.append((new_state, memory.failures))
if state == st.ANALYZING: if new_state == st.ANALYZING:
rt.storage.set_flow_state(st.SUSPENDED) runtime.storage.set_flow_state(st.SUSPENDED)
state, failures = transitions[-1] state, failures = transitions[-1]
self.assertEqual(st.SUSPENDED, state) self.assertEqual(st.SUSPENDED, state)
self.assertEqual([], failures) self.assertEqual([], failures)
self.assertEqual(st.PENDING, self.assertEqual(st.PENDING,
rt.storage.get_atom_state(happy_tasks[0].name)) runtime.storage.get_atom_state(happy_tasks[0].name))
self.assertEqual(st.FAILURE, self.assertEqual(st.FAILURE,
rt.storage.get_atom_state(sad_tasks[0].name)) runtime.storage.get_atom_state(sad_tasks[0].name))
class RunnerBuildTest(test.TestCase, _RunnerTestMixin):
def test_builder_manual_process(self): def test_builder_manual_process(self):
flow = lf.Flow("root") flow = lf.Flow("root")
tasks = test_utils.make_many( tasks = test_utils.make_many(
1, task_cls=test_utils.TaskNoRequiresNoReturns) 1, task_cls=test_utils.TaskNoRequiresNoReturns)
flow.add(*tasks) flow.add(*tasks)
rt = self._make_runtime(flow, initial_state=st.RUNNING) runtime, machine, memory, machine_runner = self._make_machine(
machine, machine_runner, memory = rt.runner.build() flow, initial_state=st.RUNNING)
self.assertTrue(rt.runner.runnable())
self.assertRaises(excp.NotInitialized, machine.process_event, 'poke') self.assertRaises(excp.NotInitialized, machine.process_event, 'poke')
# Should now be pending... # Should now be pending...
self.assertEqual(st.PENDING, rt.storage.get_atom_state(tasks[0].name)) self.assertEqual(st.PENDING,
runtime.storage.get_atom_state(tasks[0].name))
machine.initialize() machine.initialize()
self.assertEqual(runner._UNDEFINED, machine.current_state) self.assertEqual(builder.UNDEFINED, machine.current_state)
self.assertFalse(machine.terminated) self.assertFalse(machine.terminated)
self.assertRaises(excp.NotFound, machine.process_event, 'poke') self.assertRaises(excp.NotFound, machine.process_event, 'poke')
last_state = machine.current_state last_state = machine.current_state
reaction, terminal = machine.process_event('start') reaction, terminal = machine.process_event(builder.START)
self.assertFalse(terminal) self.assertFalse(terminal)
self.assertIsNotNone(reaction) self.assertIsNotNone(reaction)
self.assertEqual(st.RESUMING, machine.current_state) self.assertEqual(st.RESUMING, machine.current_state)
@@ -208,7 +206,7 @@ class RunnerBuildTest(test.TestCase, _RunnerTestMixin):
last_state = machine.current_state last_state = machine.current_state
cb, args, kwargs = reaction cb, args, kwargs = reaction
next_event = cb(last_state, machine.current_state, next_event = cb(last_state, machine.current_state,
'start', *args, **kwargs) builder.START, *args, **kwargs)
reaction, terminal = machine.process_event(next_event) reaction, terminal = machine.process_event(next_event)
self.assertFalse(terminal) self.assertFalse(terminal)
self.assertIsNotNone(reaction) self.assertIsNotNone(reaction)
@@ -225,7 +223,8 @@ class RunnerBuildTest(test.TestCase, _RunnerTestMixin):
self.assertRaises(excp.NotFound, machine.process_event, 'poke') self.assertRaises(excp.NotFound, machine.process_event, 'poke')
# Should now be running... # Should now be running...
self.assertEqual(st.RUNNING, rt.storage.get_atom_state(tasks[0].name)) self.assertEqual(st.RUNNING,
runtime.storage.get_atom_state(tasks[0].name))
last_state = machine.current_state last_state = machine.current_state
cb, args, kwargs = reaction cb, args, kwargs = reaction
@@ -243,10 +242,11 @@ class RunnerBuildTest(test.TestCase, _RunnerTestMixin):
next_event, *args, **kwargs) next_event, *args, **kwargs)
reaction, terminal = machine.process_event(next_event) reaction, terminal = machine.process_event(next_event)
self.assertFalse(terminal) self.assertFalse(terminal)
self.assertEqual(runner._GAME_OVER, machine.current_state) self.assertEqual(builder.GAME_OVER, machine.current_state)
# Should now be done... # Should now be done...
self.assertEqual(st.SUCCESS, rt.storage.get_atom_state(tasks[0].name)) self.assertEqual(st.SUCCESS,
runtime.storage.get_atom_state(tasks[0].name))
def test_builder_automatic_process(self): def test_builder_automatic_process(self):
flow = lf.Flow("root") flow = lf.Flow("root")
@@ -254,26 +254,25 @@ class RunnerBuildTest(test.TestCase, _RunnerTestMixin):
1, task_cls=test_utils.TaskNoRequiresNoReturns) 1, task_cls=test_utils.TaskNoRequiresNoReturns)
flow.add(*tasks) flow.add(*tasks)
rt = self._make_runtime(flow, initial_state=st.RUNNING) runtime, machine, memory, machine_runner = self._make_machine(
machine, machine_runner, memory = rt.runner.build() flow, initial_state=st.RUNNING)
self.assertTrue(rt.runner.runnable())
transitions = list(machine_runner.run_iter('start')) transitions = list(machine_runner.run_iter(builder.START))
self.assertEqual((runner._UNDEFINED, st.RESUMING), transitions[0]) self.assertEqual((builder.UNDEFINED, st.RESUMING), transitions[0])
self.assertEqual((runner._GAME_OVER, st.SUCCESS), transitions[-1]) self.assertEqual((builder.GAME_OVER, st.SUCCESS), transitions[-1])
self.assertEqual(st.SUCCESS, rt.storage.get_atom_state(tasks[0].name)) self.assertEqual(st.SUCCESS,
runtime.storage.get_atom_state(tasks[0].name))
def test_builder_automatic_process_failure(self): def test_builder_automatic_process_failure(self):
flow = lf.Flow("root") flow = lf.Flow("root")
tasks = test_utils.make_many(1, task_cls=test_utils.NastyFailingTask) tasks = test_utils.make_many(1, task_cls=test_utils.NastyFailingTask)
flow.add(*tasks) flow.add(*tasks)
rt = self._make_runtime(flow, initial_state=st.RUNNING) runtime, machine, memory, machine_runner = self._make_machine(
machine, machine_runner, memory = rt.runner.build() flow, initial_state=st.RUNNING)
self.assertTrue(rt.runner.runnable())
transitions = list(machine_runner.run_iter('start')) transitions = list(machine_runner.run_iter(builder.START))
self.assertEqual((runner._GAME_OVER, st.FAILURE), transitions[-1]) self.assertEqual((builder.GAME_OVER, st.FAILURE), transitions[-1])
self.assertEqual(1, len(memory.failures)) self.assertEqual(1, len(memory.failures))
def test_builder_automatic_process_reverted(self): def test_builder_automatic_process_reverted(self):
@@ -281,13 +280,13 @@ class RunnerBuildTest(test.TestCase, _RunnerTestMixin):
tasks = test_utils.make_many(1, task_cls=test_utils.TaskWithFailure) tasks = test_utils.make_many(1, task_cls=test_utils.TaskWithFailure)
flow.add(*tasks) flow.add(*tasks)
rt = self._make_runtime(flow, initial_state=st.RUNNING) runtime, machine, memory, machine_runner = self._make_machine(
machine, machine_runner, memory = rt.runner.build() flow, initial_state=st.RUNNING)
self.assertTrue(rt.runner.runnable())
transitions = list(machine_runner.run_iter('start')) transitions = list(machine_runner.run_iter(builder.START))
self.assertEqual((runner._GAME_OVER, st.REVERTED), transitions[-1]) self.assertEqual((builder.GAME_OVER, st.REVERTED), transitions[-1])
self.assertEqual(st.REVERTED, rt.storage.get_atom_state(tasks[0].name)) self.assertEqual(st.REVERTED,
runtime.storage.get_atom_state(tasks[0].name))
def test_builder_expected_transition_occurrences(self): def test_builder_expected_transition_occurrences(self):
flow = lf.Flow("root") flow = lf.Flow("root")
@@ -295,16 +294,16 @@ class RunnerBuildTest(test.TestCase, _RunnerTestMixin):
10, task_cls=test_utils.TaskNoRequiresNoReturns) 10, task_cls=test_utils.TaskNoRequiresNoReturns)
flow.add(*tasks) flow.add(*tasks)
rt = self._make_runtime(flow, initial_state=st.RUNNING) runtime, machine, memory, machine_runner = self._make_machine(
machine, machine_runner, memory = rt.runner.build() flow, initial_state=st.RUNNING)
transitions = list(machine_runner.run_iter('start')) transitions = list(machine_runner.run_iter(builder.START))
occurrences = dict((t, transitions.count(t)) for t in transitions) occurrences = dict((t, transitions.count(t)) for t in transitions)
self.assertEqual(10, occurrences.get((st.SCHEDULING, st.WAITING))) self.assertEqual(10, occurrences.get((st.SCHEDULING, st.WAITING)))
self.assertEqual(10, occurrences.get((st.WAITING, st.ANALYZING))) self.assertEqual(10, occurrences.get((st.WAITING, st.ANALYZING)))
self.assertEqual(9, occurrences.get((st.ANALYZING, st.SCHEDULING))) self.assertEqual(9, occurrences.get((st.ANALYZING, st.SCHEDULING)))
self.assertEqual(1, occurrences.get((runner._GAME_OVER, st.SUCCESS))) self.assertEqual(1, occurrences.get((builder.GAME_OVER, st.SUCCESS)))
self.assertEqual(1, occurrences.get((runner._UNDEFINED, st.RESUMING))) self.assertEqual(1, occurrences.get((builder.UNDEFINED, st.RESUMING)))
self.assertEqual(0, len(memory.next_nodes)) self.assertEqual(0, len(memory.next_nodes))
self.assertEqual(0, len(memory.not_done)) self.assertEqual(0, len(memory.not_done))

View File

@@ -31,12 +31,12 @@ import pydot
from automaton import machines from automaton import machines
from taskflow.engines.action_engine import runner from taskflow.engines.action_engine import builder
from taskflow.engines.worker_based import protocol from taskflow.engines.worker_based import protocol
from taskflow import states from taskflow import states
# This is just needed to get at the runner builder object (we will not # This is just needed to get at the machine object (we will not
# actually be running it...). # actually be running it...).
class DummyRuntime(object): class DummyRuntime(object):
def __init__(self): def __init__(self):
@@ -134,9 +134,9 @@ def main():
list(states._ALLOWED_RETRY_TRANSITIONS)) list(states._ALLOWED_RETRY_TRANSITIONS))
elif options.engines: elif options.engines:
source_type = "Engines" source_type = "Engines"
r = runner.Runner(DummyRuntime(), mock.MagicMock()) b = builder.MachineBuilder(DummyRuntime(), mock.MagicMock())
source, memory = r.build() source, memory = b.build()
internal_states.extend(runner._META_STATES) internal_states.extend(builder.META_STATES)
ordering = 'out' ordering = 'out'
elif options.wbe_requests: elif options.wbe_requests:
source_type = "WBE requests" source_type = "WBE requests"