In order to realise complex operations, a family of primitives (called await) is provided to suspend the execution of an operation until some specified condition is met, breaking the execution of an operation in multiple transactional steps. By suspending the execution of an operation, other operations can be invoked before the current one is terminated. When the specified condition holds and no operations are in execution, the suspended operation is resumed. Complex operations which can be implemented by using this mechanism include:

In the following example, two agents share and concurrently use an artifact, which provides an operation using this mechanism.

MAS example05_complexop {

    environment:
    c4jason.CartagoEnvironment

    agents:
    complexop_userA agentArchClass c4jason.CAgentArch;
    complexop_userB agentArchClass c4jason.CAgentArch;

    classpath: "../../../lib/cartago.jar";"../../../lib/c4jason.jar";
}

The artifact used by the two agents has the following code:

public class ArtifactWithComplexOp extends Artifact {

  int internalCount;

  void init(){
    internalCount = 0;
  }

  @OPERATION void complexOp(int ntimes){
    doSomeWork();
    signal("step1_completed");
    await("myCondition", ntimes);
    signal("step2_completed",internalCount);
  }

  @GUARD boolean myCondition(int ntimes){
    return internalCount >= ntimes;
  }

  @OPERATION void update(int delta){
    internalCount+=delta;
  }

  private void doSomeWork(){}

}

In complexOp first we do some work, then we generate a signal step1_completed, and after that, by means of await, we suspend the execution of the operation until the condition defined by the guard method myCondition - whose name (and parameters, if needed) are specified as parameters of the await primitive - holds.

The effect is to suspend the execution of the operation until the value of internalCount is greater than or equal to the value specified by the complexOp ntimes parameter. Besides complexOp, the update operation is provided to increment the internal counter. In the example one agent - complexop_userA - executes a complexOp and the other agent - complexop_userB - repeatedly execute update. The action and plan of the first agent is suspended until the second agent has executed a number of updates which is sufficient to resume the complexOp operation. Here it is the complexop_userA source code:

!do_test.

@do_test
+!do_test
  <- println("[userA] creating the artifact...");
     makeArtifact("a0","c4jexamples.ArtifactWithComplexOp",[],Id);
     focus(Id);
     println("[userA] executing the action...");
     complexOp(10);
     println("[userA] action completed."). 

+step1_completed
  <- println("[userA] first step completed.").

+step2_completed(C)
  <- println("[userA] second step completed: ",C).

It is worth noting that the agent reacts to step1_completed signal generated by the artifact, printing a message on the console, even if the do_test plan execution is suspended waiting for complexOp(10) action completion. complexop_userB source code:

!do_test.

+!do_test
  <- !discover("a0");      !use_it(10).            +!use_it(NTimes) : NTimes > 0
  <- update(3);
     println("[userB] updated.");
     !use_it(NTimes - 1).

+!use_it(0)
  <- println("[userB] completed.").

+!discover(ArtName)
  <- lookupArtifact(ArtName,_).
-!discover(ArtName)
  <- .wait(10);
     !discover(ArtName).

The agent simply executes for 10 times the update operation. By running the example it is possible to see the interleaving of the agent actions.

Highlights:

Here we show an example of how to exploit structured operations to implement a coordination artifact, a simple tuple space, and its usage to solve the dining philosophers coordination problem. The in and rd operations (that corresponds to the in and rd Linda primitives) are easily implemented exploiting the await mechanism:

public class TupleSpace extends Artifact {

  TupleSet tset;

  void init(){
    tset = new TupleSet();
  }

  @OPERATION void out(String name, Object... args){
    tset.add(new Tuple(name,args));
  }

  @OPERATION void in(String name, Object... params){
    TupleTemplate tt = new TupleTemplate(name,params);
    await("foundMatch",tt);
    Tuple t = tset.removeMatching(tt);
    bind(tt,t);
  }

  @OPERATION void rd(String name, Object... params){
    TupleTemplate tt = new TupleTemplate(name,params);
    await("foundMatch",tt);
    Tuple t = tset.readMatching(tt);
    bind(tt,t);
  }

  private void bind(TupleTemplate tt, Tuple t){
    Object[] tparams = t.getContents();
    int index = 0;
    for (Object p: tt.getContents()){
      if (p instanceof OpFeedbackParam){
        ((OpFeedbackParam) p).set(tparams[index]);
      }
      index++;
    }
  }

  @GUARD boolean foundMatch(TupleTemplate tt){
    return tset.hasTupleMatching(tt);
  }
}

(The description of Tuple, TupleTemplate and TupleSet classes is omitted).

This is actually the implementation of the blackboard tuple space artifact available by default in any workspace. It follows a solution to the dining philosophers problem using a tuple space:

MAS example05a_philo {

    environment:
    c4jason.CartagoEnvironment

    agents:
    waiter agentArchClass c4jason.CAgentArch;
    philo agentArchClass c4jason.CAgentArch #5;

    classpath: "../../../lib/cartago.jar";"../../../lib/c4jason.jar";
}

The MAS is composed by a waiter agent and five philosophers. The waiter is responsible of preparing the environment, injecting the tuples representing the forks (five fork(F) tuples) and tickets (four ticket tuples), which allow for avoiding deadlocks.

philo(0,"philo1",0,1).
philo(1,"philo2",1,2).
philo(2,"philo3",2,3).
philo(3,"philo4",3,4).
philo(4,"philo5",4,0).

!prepare_table.

+!prepare_table
  <- for ( .range(I,0,4) ) {
       out("fork",I);
       ?philo(I,Name,Left,Right);
       out("philo_init",Name,Left,Right);
     };
     for ( .range(I,1,4) ) {
       out("ticket");
     };
     println("done.").

The philosophers repeatedly get a couple of forks, use them to eat, and then release them. Before taking the forks they must get a ticket, which is released then after releasing the forks.

!start.

+!start
  <- .my_name(Me);
     in("philo_init",Me,Left,Right);
     +my_left_fork(Left);
     +my_right_fork(Right);
     println(Me," ready.");
     !!living.

+!living
 <- !thinking;
    !eating;
    !!living.

+!eating
 <- !acquireRes;
    !eat;
    !releaseRes.

+!acquireRes :
  my_left_fork(F1) & my_right_fork(F2)
  <- in("ticket");
     in("fork",F1);
     in("fork",F2).

+!releaseRes:
  my_left_fork(F1) & my_right_fork(F2)
 <-  out("fork",F1);
     out("fork",F2);
     out("ticket").

+!thinking
  <- .my_name(Me); println(Me," thinking").
+!eat
  <- .my_name(Me); println(Me," eating").

Highlights