org.jgroups.protocols.RSVP Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
The newest version!
package org.jgroups.protocols;
import org.jgroups.*;
import org.jgroups.annotations.MBean;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.Property;
import org.jgroups.conf.AttributeType;
import org.jgroups.stack.Protocol;
import org.jgroups.util.AckCollector;
import org.jgroups.util.MessageBatch;
import org.jgroups.util.TimeScheduler;
import org.jgroups.util.Util;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Supplier;
/**
* Protocol which implements synchronous messages (https://issues.redhat.com/browse/JGRP-1389). A send of a message M
* with flag RSVP set will block until all non-faulty recipients (one for unicasts, N for multicasts) have acked M, or
* until a timeout kicks in.
* @author Bela Ban
* @since 3.1
*/
@MBean(description="Implements synchronous acks for messages which have their RSVP flag set)")
public class RSVP extends Protocol {
/* ----------------------------------------- Properties -------------------------------------------------- */
@Property(description="Max time in milliseconds to block for an RSVP'ed message (0 blocks forever).",
type=AttributeType.TIME)
protected long timeout=10000;
@Property(description="Whether an exception should be thrown when the timeout kicks in, and we haven't yet received " +
"all acks. An exception would be thrown all the way up to JChannel.send(). If we use RSVP_NB, this will be ignored.")
protected boolean throw_exception_on_timeout=true;
@Property(description="When true, we pass the message up to the application and only then send an ack. When false, " +
"we send an ack first and only then pass the message up to the application.")
protected boolean ack_on_delivery=true;
@Property(description="Interval (in milliseconds) at which we resend the RSVP request. " +
"Needs to be < timeout. 0 disables it.",type=AttributeType.TIME)
protected long resend_interval=2000;
/* --------------------------------------------- Fields ------------------------------------------------------ */
/** ID to be used to identify messages. Short.MAX_VALUE (ca 32K plus 32K negative) should be enough, and wrap-around
* shouldn't be an issue. Using Message.Flag.RSVP should be the exception, not the rule... */
protected short current_id;
protected TimeScheduler timer;
protected volatile List members=new ArrayList<>();
/** Used to store IDs and their acks */
protected final ConcurrentMap ids=new ConcurrentHashMap<>();
protected Future> resend_task;
@ManagedAttribute(description="If we have UNICAST or UNICAST3 in the stack, we don't need to handle unicast messages " +
"as they're retransmitted anyway")
protected boolean handle_unicasts=true;
@ManagedAttribute(description="Number of pending RSVP requests")
public int getPendingRsvpRequests() {return ids.size();}
public long getTimeout() {return timeout;}
public RSVP setTimeout(long t) {this.timeout=t; return this;}
public boolean throwExceptionOnTimeout() {return throw_exception_on_timeout;}
public RSVP throwExceptionOnTimeout(boolean b) {throw_exception_on_timeout=b; return this;}
public boolean ackOnDelivery() {return ack_on_delivery;}
public RSVP ackOnDelivery(boolean b) {ack_on_delivery=b; return this;}
public long getResendInterval() {return resend_interval;}
public RSVP setResendInterval(long i) {resend_interval=i; return this;}
public void init() throws Exception {
super.init();
timer=getTransport().getTimer();
if(timeout > 0 && resend_interval > 0 && resend_interval >= timeout) {
log.warn(Util.getMessage("RSVP_Misconfig"), resend_interval, timeout);
resend_interval=timeout / 3;
}
handle_unicasts=stack.findProtocol(UNICAST3.class) == null;
}
public void start() throws Exception {
super.start();
startResendTask();
}
public void stop() {
stopResendTask();
ids.values().forEach(Entry::destroy);
ids.clear();
super.stop();
}
public Object down(Event evt) {
switch(evt.getType()) {
case Event.VIEW_CHANGE:
handleView(evt.getArg());
break;
}
return down_prot.down(evt);
}
public Object down(Message msg) {
Address target=msg.getDest();
if((target != null && !handle_unicasts) || !(msg.isFlagSet(Message.Flag.RSVP) || msg.isFlagSet(Message.Flag.RSVP_NB)))
return down_prot.down(msg);
short next_id=getNextId();
RsvpHeader hdr=new RsvpHeader(RsvpHeader.REQ, next_id);
msg.putHeader(id, hdr);
boolean block=msg.isFlagSet(Message.Flag.RSVP);
Entry entry=target != null? new Entry(target) : new Entry(members); // volatile read of members
Object retval=null;
try {
ids.put(next_id, entry);
// sync members again - if a view was received after reading members intro Entry, but
// before adding Entry to ids (https://issues.redhat.com/browse/JGRP-1503)
entry.retainAll(members);
// Send the message
if(log.isTraceEnabled())
log.trace(local_addr + ": " + hdr.typeToString() + " --> " + (target == null? "cluster" : target));
retval=down_prot.down(msg);
if(msg.isFlagSet(Message.TransientFlag.DONT_LOOPBACK))
entry.ack(local_addr);
// Block on AckCollector (if we need to block)
if(block)
entry.block(timeout);
}
catch(TimeoutException e) {
if(throw_exception_on_timeout)
throw new RuntimeException(e);
else if(log.isWarnEnabled())
log.warn(Util.getMessage("RSVP_Timeout"), entry);
}
finally {
if(block) {
Entry tmp=ids.remove(next_id);
if(tmp != null)
tmp.destroy();
}
}
return retval;
}
public Object up(Event evt) {
switch(evt.getType()) {
case Event.VIEW_CHANGE:
handleView(evt.getArg());
break;
}
return up_prot.up(evt);
}
public Object up(Message msg) {
if(!(msg.isFlagSet(Message.Flag.RSVP) || msg.isFlagSet(Message.Flag.RSVP_NB)))
return up_prot.up(msg);
Address dest=msg.getDest();
RsvpHeader hdr=msg.getHeader(id);
if(hdr == null) {
if(dest == null || handle_unicasts)
log.error(Util.getMessage("MessageWithRSVPFlagNeedsToHaveAnRsvpHeader"));
return up_prot.up(msg);
}
Address sender=msg.getSrc();
if(log.isTraceEnabled())
log.trace(local_addr + ": " + hdr.typeToString() + " <-- " + sender);
switch(hdr.type) {
case RsvpHeader.REQ:
if(this.ack_on_delivery) {
try {
return up_prot.up(msg);
}
finally {
sendResponse(sender, hdr.id);
}
}
else {
sendResponse(sender, hdr.id);
return up_prot.up(msg);
}
case RsvpHeader.REQ_ONLY:
return null;
case RsvpHeader.RSP:
handleResponse(msg.getSrc(), hdr.id);
return null;
}
return up_prot.up(msg);
}
public void up(MessageBatch batch) {
List response_ids=null;
Address dest=batch.dest();
for(Iterator it=batch.iterator(); it.hasNext();) {
Message msg=it.next();
if(!(msg.isFlagSet(Message.Flag.RSVP) || msg.isFlagSet(Message.Flag.RSVP_NB)))
continue;
RsvpHeader hdr=msg.getHeader(id);
if(hdr == null) {
if(dest == null || handle_unicasts)
log.error(Util.getMessage("MessageWithRSVPFlagNeedsToHaveAnRsvpHeader"));
continue;
}
switch(hdr.type) {
case RsvpHeader.REQ:
if(!ack_on_delivery) // send ack on *reception*
sendResponse(batch.sender(), hdr.id);
else {
if(response_ids == null)
response_ids=new ArrayList<>();
response_ids.add(hdr.id);
}
break;
case RsvpHeader.REQ_ONLY:
case RsvpHeader.RSP:
if(hdr.type == RsvpHeader.RSP)
handleResponse(msg.getSrc(), hdr.id);
it.remove();
break;
}
}
if(!batch.isEmpty())
up_prot.up(batch);
// we're sending RSVP responses if ack_on_delivery is true. Unfortunately, this is done after the entire
// *batch* was delivered, not after each message that was delivered
if(response_ids != null)
for(short rsp_id: response_ids)
sendResponse(batch.sender(), rsp_id);
}
protected void handleView(View view) {
members=view.getMembers();
for(Iterator> it=ids.entrySet().iterator(); it.hasNext();) {
Entry entry=it.next().getValue();
if(entry != null && entry.retainAll(view.getMembers()) && entry.size() == 0) {
entry.destroy();
it.remove();
}
}
}
protected void handleResponse(Address member, short id) {
Entry entry=ids.get(id);
if(entry != null) {
entry.ack(member);
if(entry.size() == 0) {
entry.destroy();
ids.remove(id);
}
}
}
protected void sendResponse(Address dest, short id) {
try {
RsvpHeader hdr=new RsvpHeader(RsvpHeader.RSP,id);
Message msg=new EmptyMessage(dest) .putHeader(this.id, hdr)
.setFlag(Message.Flag.RSVP, Message.Flag.DONT_BUNDLE, Message.Flag.OOB);
if(log.isTraceEnabled())
log.trace(local_addr + ": " + hdr.typeToString() + " --> " + dest);
down_prot.down(msg);
}
catch(Throwable t) {
log.error(Util.getMessage("FailedSendingResponse"), t);
}
}
protected synchronized short getNextId() {
return current_id++;
}
protected synchronized void startResendTask() {
if(resend_task == null || resend_task.isDone())
resend_task=timer.scheduleWithFixedDelay(new ResendTask(), resend_interval, resend_interval, TimeUnit.MILLISECONDS,
getTransport() instanceof TCP);
}
protected synchronized void stopResendTask() {
if(resend_task != null)
resend_task.cancel(false);
resend_task=null;
}
@ManagedAttribute(description="Is the resend task running")
protected synchronized boolean isResendTaskRunning() {
return resend_task != null && !resend_task.isDone();
}
protected static class Entry {
protected final AckCollector ack_collector;
protected final Address target; // if null --> multicast, else --> unicast
protected final long timestamp; // creation time (ns)
/** Unicast entry */
protected Entry(Address member) {
this.target=member;
this.ack_collector=new AckCollector(member);
this.timestamp=System.nanoTime();
}
/** Multicast entry */
protected Entry(Collection members) {
this.target=null;
this.ack_collector=new AckCollector(members);
this.timestamp=System.nanoTime();
}
protected void ack(Address member) {ack_collector.ack(member);}
protected boolean retainAll(Collection members) {return ack_collector.retainAll(members);}
protected int size() {return ack_collector.size();}
protected void block(long timeout) throws TimeoutException {ack_collector.waitForAllAcks(timeout);}
protected void destroy() {ack_collector.destroy();}
public String toString() {return ack_collector.toString();}
}
protected class ResendTask implements Runnable {
public void run() {
Set sent=new HashSet<>(); // list of all unicast dests we already sent a beacon msg
boolean mcast_sent=false;
for(Map.Entry entry: ids.entrySet()) {
Short rsvp_id=entry.getKey();
Entry val=entry.getValue();
long age=TimeUnit.MILLISECONDS.convert(System.nanoTime() - val.timestamp, TimeUnit.NANOSECONDS);
if(age >= timeout || val.ack_collector.size() == 0) {
if(age >= timeout)
log.warn(Util.getMessage("RSVP_Timeout"), entry);
val.destroy();
ids.remove(rsvp_id);
continue;
}
Address dest=val.target;
// make sure we only send 1 mcast per resend cycle
if(dest == null) {
if(mcast_sent)
continue;
else
mcast_sent=true;
}
else if(!sent.add(dest)) // only send a unicast beacon once for each target dest
continue;
RsvpHeader hdr=new RsvpHeader(RsvpHeader.REQ_ONLY, rsvp_id);
Message msg=new EmptyMessage(dest).setFlag(Message.Flag.RSVP).putHeader(id, hdr);
if(log.isTraceEnabled())
log.trace(local_addr + ": " + hdr.typeToString() + " --> " + (val.target == null? "cluster" : val.target));
down_prot.down(msg);
}
}
}
protected static class RsvpHeader extends Header {
protected static final byte REQ = 1;
protected static final byte REQ_ONLY = 2;
protected static final byte RSP = 3;
protected byte type;
protected short id;
public RsvpHeader() {
}
public RsvpHeader(byte type, short id) {
this.type=type;
this.id=id;
}
public short getMagicId() {return 76;}
public Supplier extends Header> create() {return RsvpHeader::new;}
@Override
public int serializedSize() {
return Global.BYTE_SIZE + Global.SHORT_SIZE;
}
@Override
public void writeTo(DataOutput out) throws IOException {
out.writeByte(type);
out.writeShort(id);
}
@Override
public void readFrom(DataInput in) throws IOException {
type=in.readByte();
id=in.readShort();
}
public String toString() {return typeToString() + "(" + id + ")";}
protected String typeToString() {
switch(type) {
case REQ : return "REQ";
case REQ_ONLY: return "REQ-ONLY";
case RSP: return "RSP";
default: return "unknown";
}
}
}
}