All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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"; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy