org.jgroups.protocols.PRIO Maven / Gradle / Ivy
package org.jgroups.protocols;
import org.jgroups.Address;
import org.jgroups.Event;
import org.jgroups.Message;
import org.jgroups.annotations.Experimental;
import org.jgroups.annotations.Property;
import org.jgroups.stack.Protocol;
import org.jgroups.util.MessageBatch;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
/**
* This protocol will provide message sending and receiving prioritization. The protocol assumes that any prioritized
* message will contain a PrioHeader header entry that will contain the byte value priority. Priority values are from
* 0 to 255 where 0 is the highest priority.
*
* When a message is received (up/down), it is added to the up/downMessageQueue. The up/downMessageThread will block
* on the queue until new message is added. Messages with the highest priority (0=highest) will bubble to the top
* of the queue and those be processed before other messages received at the same time.
*
* Example of setting a message priority:
*
* // Create a message to send to everyone
* Message message = new Message( null, null, messagePayload );
* // Add the priority protocol header
* PrioHeader header = new PrioHeader( 1 );
* short protocolId = ClassConfigurator.getProtocolId(PRIO.class);
* message.putHeader( protocolId, header);
*
* @author Michael Earl
*/
@Experimental
public class PRIO extends Protocol {
private PriorityBlockingQueue downMessageQueue;
private PriorityBlockingQueue upMessageQueue;
private DownMessageThread downMessageThread;
private UpMessageThread upMessageThread;
@Property(description="The number of miliseconds to sleep before after an error occurs before sending the next message")
private int message_failure_sleep_time = 120000; // two seconds (bela: 2 minutes, is that what you wanted ?)
@Property(description="true to prioritize outgoing messages")
private boolean prioritize_down = true;
@Property(description="true to prioritize incoming messages")
private boolean prioritize_up = true;
private Address local_addr;
/**
* This method is called on a {@link org.jgroups.Channel#connect(String)}. Starts work.
* Protocols are connected and queues are ready to receive events.
* Will be called from bottom to top. This call will replace
* the START and START_OK events.
* @exception Exception Thrown if protocol cannot be started successfully. This will cause the ProtocolStack
* to fail, so {@link org.jgroups.Channel#connect(String)} will throw an exception
*/
public void start() throws Exception {
if (prioritize_down) {
downMessageQueue = new PriorityBlockingQueue<>( 100, new PriorityCompare() );
downMessageThread = new DownMessageThread( this, downMessageQueue );
downMessageThread.start();
}
if (prioritize_up) {
upMessageQueue = new PriorityBlockingQueue<>( 100, new PriorityCompare() );
upMessageThread = new UpMessageThread( this, upMessageQueue );
upMessageThread.start();
}
}
/**
* This method is called on a {@link org.jgroups.Channel#disconnect()}. Stops work (e.g. by closing multicast socket).
* Will be called from top to bottom. This means that at the time of the method invocation the
* neighbor protocol below is still working. This method will replace the
* STOP, STOP_OK, CLEANUP and CLEANUP_OK events. The ProtocolStack guarantees that
* when this method is called all messages in the down queue will have been flushed
*/
public void stop() {
if (downMessageThread != null) {
downMessageThread.setRunning(false);
downMessageThread.interrupt();
}
if (upMessageThread != null) {
upMessageThread.setRunning(false);
upMessageThread.interrupt();
}
}
/**
* An event was received from the layer below. Usually the current layer will want to examine
* the event type and - depending on its type - perform some computation
* (e.g. removing headers from a MSG event type, or updating the internal membership list
* when receiving a VIEW_CHANGE event).
* Finally the event is either a) discarded, or b) an event is sent down
* the stack using down_prot.down()
or c) the event (or another event) is sent up
* the stack using up_prot.up()
.
*/
public Object up(Event evt) {
switch(evt.getType()) {
case Event.MSG:
Message message = (Message)evt.getArg();
if ( message.isFlagSet( Message.Flag.OOB ) ) {
return up_prot.up(evt);
}
else {
PrioHeader hdr=(PrioHeader)message.getHeader(id);
if(hdr != null) {
log.trace("%s: adding priority message %d to UP queue", local_addr, hdr.getPriority());
upMessageQueue.add( new PriorityMessage( evt, hdr.getPriority() ) );
// send with hdr.prio
return null;
}
return up_prot.up(evt);
}
default:
return up_prot.up(evt);
}
}
public void up(MessageBatch batch) {
for(Message msg: batch) {
if(msg.isFlagSet(Message.Flag.OOB))
continue;
PrioHeader hdr=(PrioHeader)msg.getHeader(id);
if(hdr != null) {
log.trace("%s: adding priority message %d to UP queue", local_addr, hdr.getPriority());
upMessageQueue.add( new PriorityMessage( new Event(Event.MSG, msg), hdr.getPriority() ) );
batch.remove(msg); // sent up by UpMessageThread; we don't need to send it up, too
}
}
if(!batch.isEmpty())
up_prot.up(batch);
}
/**
* An event is to be sent down the stack. The layer may want to examine its type and perform
* some action on it, depending on the event's type. If the event is a message MSG, then
* the layer may need to add a header to it (or do nothing at all) before sending it down
* the stack using down_prot.down()
. In case of a GET_ADDRESS event (which tries to
* retrieve the stack's address from one of the bottom layers), the layer may need to send
* a new response event back up the stack using up_prot.up()
.
*/
public Object down(Event evt) {
switch(evt.getType()) {
case Event.MSG:
Message message = (Message)evt.getArg();
if ( message.isFlagSet( Message.Flag.OOB ) )
return down_prot.down(evt);
PrioHeader hdr=(PrioHeader)message.getHeader(id);
if(hdr != null) {
log.trace("%s: adding priority message %d to DOWN queue", local_addr, hdr.getPriority());
downMessageQueue.add( new PriorityMessage( evt, hdr.getPriority() ) );
// send with hdr.prio
return null;
}
return down_prot.down(evt);
case Event.SET_LOCAL_ADDRESS:
local_addr=(Address)evt.getArg();
return down_prot.down(evt);
default:
return down_prot.down(evt);
}
}
/**
* This class is a simple wrapper to contain the Event, timestamp and priority of the message.
* Instances of this class are added to the message queue
*/
protected static class PriorityMessage {
Event event;
long timestamp;
byte priority;
protected PriorityMessage( Event event, byte priority ) {
this.event = event;
this.timestamp = System.currentTimeMillis();
this.priority = priority;
}
}
/**
* Thread to send messages to the down protocol.
*
* The messageQueue contains the prioritized messages
*/
private class DownMessageThread extends MessageThread {
private DownMessageThread( PRIO prio, PriorityBlockingQueue messageQueue ) {
super( prio, messageQueue );
}
protected void handleMessage( PriorityMessage message ) {
log.trace("%s: sending priority %d message", local_addr, message.priority);
down_prot.down( message.event );
}
}
/**
* Thread to send messages to the up protocol.
*
* The messageQueue contains the prioritized messages
*/
private class UpMessageThread extends MessageThread {
private UpMessageThread( PRIO prio, PriorityBlockingQueue messageQueue ) {
super( prio, messageQueue );
}
protected void handleMessage( PriorityMessage message ) {
log.trace("%s: delivering priority %d message", local_addr, message.priority);
up_prot.up( message.event );
}
}
/**
* This Thread class will process PriorityMessage's off of the queue and call the handleMessage method
* to send the message
*/
private abstract class MessageThread extends Thread
{
private PRIO prio;
private PriorityBlockingQueue messageQueue;
private volatile boolean running=true;
private MessageThread( PRIO prio, PriorityBlockingQueue messageQueue ) {
this.prio = prio;
this.messageQueue = messageQueue;
setName( "PRIO " + (messageQueue == downMessageQueue ? "down" : "up") );
}
protected abstract void handleMessage( PriorityMessage message );
@Override
public void run() {
while (running) {
PriorityMessage priorityMessage = null;
try {
priorityMessage = messageQueue.take();
handleMessage( priorityMessage );
}
catch( InterruptedException e ) {
break;
}
catch (Exception e) {
log.error( "Error handling message. Sleeping " + (prio.message_failure_sleep_time/1000) + " seconds", e );
try
{
sleep( prio.message_failure_sleep_time );
}
catch (InterruptedException ex)
{
break;
}
/*
* Add it back to the queue to be processed again
*/
messageQueue.add( priorityMessage );
}
}
}
public void setRunning(boolean flag) {
running=flag;
}
}
/**
* Comparator for PriorityMessage's
*/
private static class PriorityCompare implements Comparator {
/**
* Compare two messages based on priority and time stamp in that order
* @param msg1 - first message
* @param msg2 - second message
* @return int result of comparison
*/
@Override
public int compare( PriorityMessage msg1, PriorityMessage msg2 ) {
if ( msg1.priority > msg2.priority ) {
return 1;
}
else if ( msg1.priority < msg2.priority ) {
return -1;
}
else {
if ( msg1.timestamp > msg2.timestamp ) {
return 1;
}
else if ( msg1.timestamp < msg2.timestamp ) {
return -1;
}
else {
return 0;
}
}
}
}
}