ruote has got a new “await” attribute (can be placed on any expression) that suspends the application of the expression until a condition (entering or leaving a tag or a participant) realizes. Useful when expressing graphs.
(with an update thanks to Larry Marburger)
Ruote is a Ruby workflow engine. It orchestrates tasks, routing work among participants. The most interesting questions people come up with are the ones about “how to model that flow in ruote?”.
The other day on IRC (freenode #ruote), Typedef asked me how one could model this in ruote:
“for example if I had a graph like A -> C <- B -> D, where task C depends on A and B, and D depends only on B. I’d like to be able to launch D as soon as B completes without waiting for A, but block C until both A and B complete.”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # # nice and all, but makes "d" # an orphan / detached branch # sequence do concurrence do a sequence do b d :forget => true end end c end
Consider the diagram on the right, the rectangle is the top sequence, when the concurrence is done, C gets applied. The concurrence makes sure A and B are applied concurrently, the inner sequence align D after B and the :forget flag is here to let the B branch reply to the concurrence right after B finishes.
But, conceptually, that is more like the graph on the right, where D becomes an orphan and the main flow goes on without waiting for it. It’s not explicitely required, but I think that this piece of flow should end when all its tasks completed (hence the rectangle I draw).
So I went back to the coding board. I wrote the four tasks in a concurrence and told myself: “this is great, the four tasks exist in the same space, when all four of them terminate, the concurrence terminates…”
I wondered how I could materialize Typedef’s arrows. And I realized that he was probably right trying to apply await, this expression could shine in this scenario.
This “await” expression is a rework of the listen expression. It’s meant for waiting for events to happen in other branches of the workflow execution tree.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # # works... but never goes out of # the concurrence because the two # "await" behave like daemons, # ready to spin again for each # tracked event # concurrence do sequence :tag => 'ab' do a b :tag => 'b' end await :left_tag => 'ab' do c end await :left_tag => 'b' do d end end
There are 3 kind of events “await” can listen to: participant (on apply and on reply), tag (on entering and on leaving) and errors.
The problem with this definition is that it never exits, the two await expressions behave like little daemons, that’s how “await” (and “listen”) are supposed to work, when they have a block they become daemons, sitting, listening and spinning a new process branch for each event that matches.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # # what we wanted, but it feels # clunky # concurrence do sequence :tag => 'ab' do a b :tag => 'b' end sequence do await :left_tag => 'ab' c end sequence do await :left_tag => 'b' d end end
The solution would be to use “await” without a block. It then behaves like a lock, waiting for the event to happen to let the flow resume.
But, although it runs as we wanted, the definition feels clunky compared to the “daemons” version above.
So I went ahead and added an :await attribute to the ruote expressions. When an expression sports it, it waits for the described event before applying for real (it stays in a paused state until the event occurs).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # # using the new :await attribute # concurrence do sequence :tag => 'ab' do a b :tag => 'b' end sequence :await => 'ab' do c end sequence :await => 'b' do d end end
This new “await” attribute can express things like
:await => "left_tag:x"
:await => "reached_participant:alfred"
:await => "b"
is a shortcut for
:await => "left_tag:b"
I think this “wait for a tagged region to terminate” scenario is important and deserves a short notation.
1 2 3 4 5 6 7 8 9 10 11 # # short version # concurrence do sequence :tag => 'ab' do a b :tag => 'b' end c :await => 'ab' d :await => 'b' end
Here are the main links for ruote:
- http://ruote.rubyforge.org (site)
- https://github.com/jmettraux/ruote (source)
- http://groups.google.com/group/openwferu-users (mailing list)
- freenode #ruote (irc)
I wish you a Merry Christmas and a Happy New Year!
Larry pointed out that my reasoning started wrong for A and B.
As Typedef enunciated it, A and B have no dependencies. All the versions I’ve shown above do A then B, that’s a waste of B’s time, it should be A and B (concurrence).
1 2 3 4 5 6 7 8 9 10 11 12 # # short version, # with Larry Marburger's fix # concurrence do concurrence :tag => 'ab' do a b :tag => 'b' end c :await => 'ab' d :await => 'b' end
So here is the refined process, with A and B wrapped in a concurrence, so that the work starts with A and B.