

	    The 'Receive' problem in the Channel interface
	    ----------------------------------------------


There are essentially 3 ways to receive messages, view and block
notifications with a pull-style channel. I'm looking at each in turn,
discussing the pros and cons.




1. Receive returns only messages, view and block notifications are
   handled in MembershipListener (as callbacks).



This is currently used in JavaGroups. If the client is interested in
receiving views/blocks, it registers with the channel
(SetMembershipListener(MembershipListener)). Interface
MembershipListener has 2 callbacks: ViewAccepted(View) and
Block(). Messages, view and block notifications are stored in the same
queue internal to the channel in FIFO order. When a client calls
Receive, if the object on the top of the channel is a message, it is
returned to the caller. If the object is a view or block and a client
has registered as MembershipListener, then the corresponding
ViewAccepted or Block method is called, otherwise the object is
discarded.


Advantages:

- The Receive method only returns messages. It is not necessary to
  write discrimination code to handle return values from Receive.

- A client won't receive views/blocks unless it registers with the
  channel (SetMembershipListener)


Disadvantages:

- Using callbacks for views/blocks distorts the clean push-style
  interface of the channel

- Although view/block delivery uses push-style, the client actually
  has to call Receive in order to receive views/blocks. This is in
  contrast to the push style of MembershipListener which suggests that
  clients will receive views/blocks as soon as they occur. So clients
  expect to be notified, but actually have to trigger the notification
  themselves.







2. Receive() returns messages. In case of a view or block notification,
   an exception is throw. The caller has to (a) retrieve the view or
   (b) handle the block in the exception's catch clause.


Advantages:

- The channel is all pull-style.


Disadvantages:

- In case of a view/block when calling Receive, an exception is
  thrown. The caller has to write exception handling code as follows:

    try {
        Message m=channel.Receive();
    }
    catch(ViewException view_ex) {
        // do something with the view, e.g. GetView
        // if blocked, unblock message sending
    }
    catch(BlockException block_ex) {
        // send all outstanding messages
        // block message sending
        // call BlockOk()
    }
    catch(TimeoutException timeout_ex) {
        
    }

  Handling of control flow through exceptions is considered bad. Both
  when handling view and block exceptions, the client might have to
  call other methods, themselves wrapped in try-catch clauses, which
  leads to messy code.









3. Receive() returns only objects. They might be messages, views or
   blocks. The caller uses SetOpt to filter the objects returned. He
   might for example decide to only receive messages, and discard
   views/blocks. In this case, he would be guaranteed to receive only
   messages, and could narrow the return value to a message (without
   ever getting a cast exception). If all objects (messages, views,
   blocks) are enabled, a runtime-type checking mechanism has to be
   available to determine what the actual class is.


Advantages:

- The channel is all pull-style.

- The caller knows what types of return values to expect (SetOpt), so
  in the case of receiving only messages, it can perform a cast
  without checking for the return value's type.


Disadvantages:

- The runtime type checking mechanism may not be available in all
  languages (e.g. C++). In this case, we might have to tag each object
  and query the tag (e.g. in a common superclass).
  Having discrimination code (switch) is not considered good OO
  design.

- Runtime-type checking may be expensive


