SourceForge.net Logo

Concurrent states.

One of the more challenging concepts described in UML state diagrams are concurrent states, which, simply put, is a state composed of a number of substates which handle events concurrently. This also implies a join bar, after all it is neccesary to define satifactorily when the enclosing state completes.

The concurrent state itself does not handle any events except b_done and b_failed, as generated by the child (they come via the input queue, for once), but merely passes on the events it receives to it's children. This is handled by the kernel, so the user is not troubled and (having the ssm tables handly) it's a small effor to post events to the proper queue's right away, thus avoiding waiting for the scheduler once.

The concurrent state itself does little more than keep track of the counts and emit b_done or b_failed when it races or joins (see below). The work is handled by b_ssm_post, b_ssm_fork and b_ssm_complete.

Joining and Racing.

Joining implies that all states terminate before the concurrent state terminates. Racing, in contrast, means that the first substate to terminate also terminates the concurrent state, aborting all other substates.

PlanB offers four options

Lifecycle.

As I said, the bulk of the work is done by the kernel. The specifics of the ssm's to start up (basically, the initial states) are provided as arguments and the appropriate number of ssm's is started. Should forking fail for some reason, a (triggerless) transition to failed occurs.

Whenever an compltion event is seen, the bookkeeping is done and if a race or a join condition is detected, an appropriate final transiition is scheduled.

In the leave-gate all forked ssm's which are still active are aborted.

Routing.

The above implies that some routing needs to be done to prevent completion events from the sub-ssm's from being broadcast to all siblings and failing to reach the input queue of the SSM if we insist on avoiding the overhead involved in letting the concurrent state handle the distribution. Which makes you wonder...

Still, if that shortcut is chosen (should it prove to be one) some method is required to distinguish external events from the sub-ssm's completion events or other events sent to the parent. If the number of children is not that big, s simple linear search (in O(n)=n) would suffice. If large numbers of sub-ssm are required, this may bite, though.

Setting a flag would require only O(n)=1 and may be implemented fairly easily by introducing a variant on b_ssm_post implicitly addressing the parent (without the extra arg required by b_ssm_post()) and setting some appropriate flag in the event body.

An extra problem for the straightforward approach is that is becomes quite difficult to communicate directly with the parent-ssm. It may be usefull (for instance) to send status events to the parent-ssm so it can take appropriate action. The 'let-the-state-handle-it' approach would simply broadcast them back at the originating ssm and all it's siblings or require a very precise definition and subclassing, errr... substating, b_concurrent_state.

The conclusion is, of course, that this kind of routing makes sense and the price is not too high to pay. Hence routing is a viable option, given the expected overhead and extra latency involved in letting the concurrent state handle it.