ruote and the :await attribute 2012-12-20 home comments

tl;dr
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.”

Typedef was asking how he could put the await expression to good use for that. I always try to drive people away from listen and await unless they are really needed, so I proposed:

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

The bare

: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

I’ve added some documentation about this new “await attribute” to the common attributes page and to the await expression documentation.

Here are the main links for ruote:

I wish you a Merry Christmas and a Happy New Year!

 

UPDATE (2012-12-22)

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.

Thanks Larry!

© 2011-2013 John Mettraux