Multiple performance transactions

In the Coordination rules exposed in the previous lesson and included in the initialization and application logic scripts of our application, the performance part was constituted of a unique transaction, ensuring that either all the operations within this transaction are to be performed or none of them.

However, a performance part can be constituted of multiple transactions. In this case, the defined transactions are executed sequentially, as soon as the performance part of the rule gets triggered. The fact that the outcome of a given transaction is a success or a failure (commit or abortion) has no direct impact on the following ones. However, as resources may be consumed if a transaction is committed may prevent following transaction to be committed as it will be explained here after.

From a syntactic point of view, transactions are delimited with brackets. Adding another transaction to an existing rule is as easy as grouping operations under a new pair of brackets. This may be represented as follows:

precondition
 ::
 {
 performance_transaction01
 }
 {
 performance_transaction02
 }.

Alternative performance transactions: a bug tracking example

In the previous lesson, we have seen that denis did not reach his attended destination, despite the fact that a resource ('A','D') was initialy available. This lesson exemplifies how the multiple transactions featured in the performance part of the Coordination rules and used here as alternative transactions can help to track and find out what happened.

Note : even if this feature is quite convenient here as a tracking tool, it reveals all its power in large applications with complex scenarios. Indeed, it constitutes a very simple way to implement multiple alternative (incremental) behaviors in response to a given initial context.

The problem

The rule managing the travels of the customers is reminded below:

 {*,!}["Test","Customer"].rd(name,departure) &
 {*,!}["Test","Destination"].rd(name,arrival) &
 {*,!}["Test","Connection"].rd(departure,arrival)
 ::
 {
 ["Test","Customer"].get(name,departure) ;
 ["Test","Destination"].get(name,arrival) ;
 ["Test","Connection"].get(departure,arrival) ;
 ["Test","Customer"].put(name,arrival) ;
 ["Test","Travel"].put(departure,arrival,name)
 }.

denis stayed at departure D and did not reach his attended D destination despite the fact that an ('A','D') resource was initialy available in the Connection bag. Let's here forget about the explanation given in the previous lesson and build up an argument to reduce the fields of investigations.

If denis did not move, this may be cause by 2 main reasons:

  • the performance part of the rule is not triggered
  • the performance part of the rule is actually triggered, but the transaction is aborted as a consequence of an operation failure.

Multiple alternative transactions

Multiple alternative transactions may take place in the performance part of a rule. This means that, if a transaction fails, the next one is attempted, and so on until one transaction succeeds or no more transactions are defined in the rule.

Principle

As explained above, the multiple transactions mechanism is natively integrated in this middleware. The principle in making these transactions exclusive (i.e. only one of the possible transactions is actually performed and committed) is to add in each transaction an operation which is consuming a unique resource (or unique combination of resources) acting as a flag.

This way, if a transaction is committed, this unique flag resource is consumed. As a consequence, it is no longer available for the other transactions which, then, may not get performed. On the other hand, if a transaction fails, it does not consume the flag's resource. Then, the resource is available for the next transaction which may get performed and committed.

In the performance part, transactions are delimited with brackets. Adding an alternative transaction to an existing rule is as easy as grouping operations under a new pair of brackets. This may be represented as follows:

 precondition
 ::
 {
 default_performance_transaction
 get(flagresource)
 }
 {
 alternative_performance_transaction01
 get(flagresource)
 }
 {
 alternative_performance_transaction02
 get(flagresource)
 }.

Obviously, a given operation (such as a context-checking operation) may be repeated in several transactions as, in the end, only one transaction is actually performed and committed.

Implementation

In our example, to find out why denis did not travel, an alternative transaction is added to the performance part of the trom.ls rule. This alternative transaction contains a kind of logging operation as it is certainly performed if it gets triggered. Then the behavior of the overall rule becomes:

  • the performance part is not triggered
    • denis does not travel and no log is recorded
  • the performance part is triggered
    • denis travels and no log is recorded
    • denis does not travel and a log event is recorded

The corresponding implemented rule is :

 {*,!}["Test","Customer"].rd(name,departure) &
 {*,!}["Test","Destination"].rd(name,arrival) &
 {*,!}["Test","Connection"].rd(departure,arrival)
 ::
 {
 ["Test","Customer"].get(name,departure) ;
 ["Test","Destination"].get(name,arrival) ;
 ["Test","Connection"].get(departure,arrival) ;
 ["Test","Customer"].put(name,arrival) ;
 ["Test","Travel"].put(departure,arrival,name)
 }
 {
 ["Test","Customer"].get(name,departure) ;
 ["Test","Destination"].get(name,arrival) ;
 ["Test","Travel"].put(departure,"failed",name)
 }

Only the highlighted transaction in the performance part differs from the previous version.

Please note that the get() operations over the Customer and Destination bags are repeated in both transactions. Here, we use this combination as an exclusive flag to enable the alternative transactions mechanism described above.

The customer and destination resources are consumed through get() operations to get the "problematic resources" out of the normal execution of the system and keep a coherent data model.

As the travel bag is used to store the travels performed by the customers, and as this bag does not integrate any write constraint, it is also used to store (log) travel problems detected in the rule. The travel problems are identified with a failed value in the destination field of the associated resource.

Launch the application with :

python quinoa.py --go

Then, use the monitor to examine the application state : http://localhost:9999/NameServer/MONITOR

The application state should look like :

| Customer| Destination| Connection| Travel| |---|---|---|---|---| | ('bob','B')| ('bob','D')| ('A','B')| ('A','D','alice')| | ('alice','D')||| ('A','failed','denis')| | ('charles','D')||| ('C','D','charles')|

In the last Travel column, you may note the ('A','failed','denis') resource. This resource is inserted by the second (alternative) transaction of the performance part of the rule. This means both that the performance part was actually triggered and that the first (default) transaction was aborted. Since, in the alternative transaction, we performed the same get() operations on the Customer and Destination bags, and as we performed a put() in the same Travel bag (which defines no constraint on the written field values), the default transaction failed either on the get() operation performed on the Customer bag or on the put() operation performed on the Connection bag.

A quick evaluation on the context leads to the fact the default transaction failed on the lack of any ('A','D') resource required by the get() operation performed on the Connection bag (as the initial one was consumed when alice travelled along the same connection).

Conclusion

We introduced here the concept of multiple alternative transactions in the performance part of a rule. This principle may be applied to any rule and a given performance part may contain as many alternative transactions as required.

This principle, even if demonstrated in a quite specific context (tracking a bug), is really convenient when dealing with complex scenario. Notably, it may be very useful in the case of self-configuring/self-adapting/self-repairing applications where degraded or secondary solutions may be involved if the optimal ones are not available.

Please note that defining a new alternative or emergency scenario for a given use case remains as simple as adding a new transaction definition in an existing rule, without the need for intricate condition evaluations.