[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

CALL Channels in Java



Hi Peter and Thomas,

The new version of CTJ (version 0.9 rev. 14) contains some new useful
features:
- improved output guards (they were implemented in version 0.9 rev. 6 and
temporarily removed because of implementation difficulties)
- CTJ channels support CRCR (Concurrent-Read-Concurrent-Write) by default
and they can be enabled for exclusive use. CTJ channels can be set to CRCW,
ERCW, CREW or EREW (Concurrent/Exclusive-Read-Concurrent/Exclusive-Write)
using the claimInput()-releaseInput() methods for Exclusive-Read and
claimOutput()-releaseOutput() for Exclusive-Write (the example below will
show the use of these methods),
- updated TCPIP and UDP link drivers are available (where disabled since
rev.10),
- the select() method is back (was protected since rev.10 and now public
available),
- new Guards such as SKIP-guards and TIMEOUT-guards are implemented,
- new STOP() and SKIP() processes are included,
- all wrappers (Integer, Byte, Short, Float, Double, ..) are almost similar
to the Java wrappers and contain the same set of useful methods,
- several bug-fixes in the kernel.

Some of these features enable you to create CALL channels. Creating CALL
channels with CTJ is almost similar to Peter's approach using JCSP. The
example below shows some additional features, such as
- multiple return arguments,
- exclusive channel communication,
- guarded CALL channels,
- compositional and select() approach,
- separates process address spaces by copying object data 
  during communication,
- and support of 'remote' CALL channels.

In this example, the CALL channel performs z,w = f(x,y).

CALL CHANNEL INTERFACES
~~~~~~~~~~~~~~~~~~~~~~~

interface CallChannelClient_of_Example
{
  public void callMethod(Integer x, Integer y, Double z, Boolean w);
}

The arguments x and y are input arguments and the arguments z and w are
output arguments of the callMethod(...) method.

interface CallChannelServer_of_Example
{ 
  public void read(Integer x, Integer y);
  public void write(Double z, Boolean w);
  public ChannelInput guardChannel();
}

Perhaps, the read(x,y) and write(z,w) could be renamed in something like
getArguments(x,y) and accept(z,w).

CALL CHANNEL CLASS
~~~~~~~~~~~~~~~~~~

class CallChannel_of_Example 
implements CallChannelClient_of_Example, CallChannelServer_of_Example
{
  private Channel_of_Object toServer;
  private Channel_of_Object fromServer;

 /**
  * Create a CallChannel.
  **/

  public CallChannel_of_Example() 
  { 
    toServer   = new Channel_of_Object();
    fromServer = new Channel_of_Object();
  }

 /**
  * Create a 'remote' CallChannel using link drivers.
  **/

  public CallChannel_of_Example(LinkDriver ldToServer, LinkDriver
ldFromServer) 
  { 
    toServer   = new Channel_of_Object(ldToServer);
    fromServer = new Channel_of_Object(ldFromServer);
  }

 /**
  * Perform call method.
  **/

  public void callMethod(Integer x, Integer y, Double z, Boolean w)
  {
    // claim channels -- Exclusive Read and Exclusive Write (EREW)
    //-------------------------------------------------------------
    toServer.claimOutput();   // claim toServer channel
    fromServer.claimInput();  // claim fromServer channel

    toServer.write(x);        // 'toServer' is the guard channel
    toServer.write(y);
    fromServer.read(z);
    fromServer.read(w);

    // release channels -- Concurrent Read and Concurrent Write (CRCW)
    //-----------------------------------------------------------------
    toServer.releaseOutput();   // release toServer channel
    fromServer.releaseInput();  // release fromServer channel
  }

 /**
  * Read all input arguments from client.
**/

  public void read(Integer x, Integer y)
  {
    // claim channels -- Exclusive Read and Exclusive Write (EREW)
    //-------------------------------------------------------------
    toServer.claimInput();    // claim toServer input channel
    fromServer.claimOutput(); // claim fromServer output channel

    toServer.read(x);
    toServer.read(y);
  }

 /**
  * Write results to client.
  **/

  public void write(Double z, Boolean w)
  {
    fromServer.write(z);
    fromServer.write(w);

    // release channels -- Concurrent Read and Concurrent Write (CRCW)
    //-----------------------------------------------------------------
    toServer.releaseInput();    // release toServer input channel
    fromServer.releaseOutput();  // release fromServer output channel
  }

 /**
  * Return guard channel.
  **/
 
  public ChannelInput guardChannel()
  {
    return (ChannelInput)toServer;
  }
}

Many clients could share the CALL channel 'CallChannel_of_Example'. In that
case, the callMethod() must be synchronised using the Java 'synchronized'
keyword. However, this is not sufficient when the toServer or fromServer
channels are shared by other processes. Instead of using the 'synchronized'
keyword, I used a claim-release construct (see claimInput()-releaseInput()
and claimOutput()-releaseOutput() methods) that enforces an exclusive claim
of shared channels. 

One should be careful when using the Java 'synchronized' construct with
channels. The channel read(..) and write(..) methods inside a monitor can
lock the monitor when they block the process and wait for communication.
Channels do not release (or unlock) the outer monitor when they block. When
the outer monitor locks then all other related monitors will also lock. The
program may deadlock. The claim-release construct provides a solution to
this problem.

Example of the claim-release construct
--------------------------------------
Two processes, e.g. Process1 and Process2, want each to write two objects in
sequence to a shared channel. The reading by the reader process may be
partitioned and the results can be wrong.

PAR
  SEQ   // Process1
    channel.write(a);
    channel.write(b);
  SEQ   // Process2
    channel.write(q);
    channel.write(r);
  SEQ  // a reader process
    channel.read(x);
    channel.read(y);

The results will be
x = a and y = b or
x = q and y = r or
x = a and y = q or
x = q and y = a

The claim-release construct provides exclusive access of channel input or
channel output.

PAR
  SEQ   // Process1
    channel.claimOutput();
    channel.write(a);
    channel.write(b);
    channel.releaseOutput();
  SEQ   // Process2
    channel.claimOutput();
    channel.write(q);
    channel.write(r);
    channel.releaseOutput();
  SEQ  // a reader process
    channel.read(x);
    channel.read(y);

The results will be
x = a and y = b or
x = q and y = r

There is still a race competition between Process1 and Process2, but the
writing does not partition.

When Process1 first claims the channel output by invoking
channel.claimOutput() then Process2 will block when it invokes
channel.claimOutput() before Process1 releases the channel output. Process2
continues when Process1 invokes the channel.releaseOutput() method and
claims the channel output.

A problem is that the programmer can forget to claim a channel input or
output. For instance, if you take out the channel.claimOutput() and
channel.releaseOutput() in Process2 and the channel output was claimed by
Process1 then an exception will be thrown on the first channel.write(q)
statement by Process2. If Process2 first executes channel.write(q) and then
Process1 claims the channel output an exception will be thrown by the second
channel.write(r) statement. When the processes execute in some sequence
(i.e. only context switching at termination of the processes) no exceptions
will be thrown and everything works fine. 

The claim-release construct must be used in a proper way!

Claiming and releasing channel inputs or channel outputs is not required.
The CTJ channel read() and write() methods will throw an exception (e.g.
NotOwnerException) when they are invoked but claimed by another owner. The
read() and write() methods will not throw an exception when the channel
input or channel output was not claimed. The process that claims a channel
input or channel output is the owner of the read() or write() method.
 
APPLICATION
~~~~~~~~~~~

Shared CALL channel by server and client
-----------------------------------------

CallChannelServer_of_Example callchannel = 
  new CallChannel_of_Example();

or with buffer link drivers

CallChannelServer_of_Example callchannel = 
  new CallChannel_of_Example(new Buffer(10), new Buffer(10));

Other link drivers, such as TCPIP or UDP, can be plugged in.

Server Application
------------------

// COMPOSITIONAL APPROACH

class ServerExample implements csp.lang.Process
{ 
  Alternative alt;

  public ServerExample(CallChannelServer_of_Example channel)
  {
    alt = new Alternative(new Guard[] 
          {
            new guard(callchannel.guardChannel(), 
              new Process() 
              {
                CallChannel_of_Example callchannel = channel;
                Integer x = new Integer();
                Integer y = new Integer();
                Double  z = new Double();
                Boolean w = new Boolean();
                int     divideFactor = 2;

                // the callMethod method performing z,w = f(x,y)
                public void run() 
                {
                  callchannel.read(x,y);
                  z.value = (x.value + y.value)/divideFactor;
                  w.value = z.value > 10;
                  callchannel.write(z,w);
                }
              }),
            ... // other guards
          });
  public void run()
  {
    while(true)
    {
      alt.run();
    }
  }
}

// SELECT APPROACH 

class ServerExample implements csp.lang.Process
{ 
  CallChannelServer_of_Example callchannel;
  Alternative alt;
  // declare local objects/memory
  Integer x = new Integer();
  Integer y = new Integer();
  Double  z = new Double();
  Boolean w = new Boolean();
  int     divideFactor = 2;

  public ServerExample(CallChannelServer_of_Example channel)
  {
    this.callchannel = channel;
    alt = new Alternative(new Guard[] 
          {
            new guard(callchannel.guardChannel()), 
            ...
          });
  }

  public void run()
  {
    while(true)
    {
      switch(alt.select())
      {
        case 0:
          // the callMethod method peforming z,w = f(x,y)
          callchannel.read(x,y);
          z.value = (x.value + y.value)/divideFactor;
          w.value = z.value > 10;
          callchannel.write(z,w);
          break;
        case ?: ... other processes
      }
    }
  }
}

The read() method receives all input data and the write() method returns all
result data. 

Client Application
------------------

class ClientExample implements csp.lang.Process
{ 
  CallChannelClient_of_Example callchannel;

  public Client(CallChannelClient_of_Example channel)
  {
    callchannel = channel;
  }

  public void run()
  {
    // declare local objects/memory
    Integer x = new Integer();
    Integer y = new Integer();
    Double  z = new Double();
    Boolean w = new Boolean();

    // fill in messages
    x.value = 100;
    y.value = 23;

    // do the call
    callchannel.callMethod(x,y,z,w);

    // results:
    // z.value = (100+23)/2 = 61.5;
    // w.value = (z > 10)   = true;
  }
}


I hope that this approach can be helpful to you. I appreciate any comments.

Gerald.

PS.
The class/interface names CallChannel_of_Example,
CallChannelClient_of_Example, CallChannelServer_of_Example,
Channel_of_Object, ChannelInput and all others are chosen so that channels
and CALL channels easily can be found in the source code. All channel
classes/interfaces begin with the word "Channel" and CALL channel
classes/interfaces begin with the word "CallChannel". For class/interface
names they may seem vague but to me this naming convention is practical.
However, they can be changed to something else. I'm open for suggestions.