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

org.jgroups.protocols.pbcast.NAKACK2 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).

There is a newer version: 34.0.0.Final
Show newest version
package org.jgroups.protocols.pbcast;

import org.jgroups.*;
import org.jgroups.annotations.*;
import org.jgroups.conf.AttributeType;
import org.jgroups.protocols.RED;
import org.jgroups.protocols.TCP;
import org.jgroups.protocols.TP;
import org.jgroups.stack.DiagnosticsHandler;
import org.jgroups.stack.Protocol;
import org.jgroups.util.*;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static org.jgroups.Message.TransientFlag.DONT_LOOPBACK;
import static org.jgroups.Message.TransientFlag.OOB_DELIVERED;
import static org.jgroups.util.MessageBatch.Mode.OOB;


/**
 * Negative AcKnowledgement layer (NAKs). Messages are assigned a monotonically
 * increasing sequence number (seqno). Receivers deliver messages ordered
 * according to seqno and request retransmission of missing messages.
* Retransmit requests are usually sent to the original sender of a message, but * this can be changed by xmit_from_random_member (send to random member) or * use_mcast_xmit_req (send to everyone). Responses can also be sent to everyone * instead of the requester by setting use_mcast_xmit to true. * * @author Bela Ban */ @MBean(description="Reliable transmission multipoint FIFO protocol") public class NAKACK2 extends Protocol implements DiagnosticsHandler.ProbeHandler { protected static final int NUM_REBROADCAST_MSGS=3; /** * Retransmit messages using multicast rather than unicast. This has the advantage that, if many receivers * lost a message, the sender only retransmits once */ @Property(description="Retransmit retransmit responses (messages) using multicast rather than unicast") protected boolean use_mcast_xmit=true; /** * Use a multicast to request retransmission of missing messages. This may * be costly as every member in the cluster will send a response */ @Property(description="Use a multicast to request retransmission of missing messages") protected boolean use_mcast_xmit_req; /** * Ask a random member for retransmission of a missing message. If set to * true, discard_delivered_msgs will be set to false */ @Property(description="Ask a random member for retransmission of a missing message. Default is false") protected boolean xmit_from_random_member; /** * Messages that have been received in order are sent up the stack (= delivered to the application). * Delivered messages are removed from the retransmit table, so they can get GC'ed by the JVM. When this * property is true, everyone (except the sender of a message) removes the message from their retransmit * table as soon as it has been delivered to the application */ @Property(description="Should messages delivered to application be discarded") protected boolean discard_delivered_msgs=true; @Property(description="Timeout to rebroadcast messages. Default is 2000 msec",type=AttributeType.TIME) protected long max_rebroadcast_timeout=2000; /** If true, logs messages discarded because received from other members */ @Property(description="discards warnings about promiscuous traffic") protected boolean log_discard_msgs=true; @Property(description="If false, trashes warnings about retransmission messages not found in the xmit_table (used for testing)") protected boolean log_not_found_msgs=true; @Property(description="Interval (in milliseconds) at which missing messages (from all retransmit buffers) " + "are retransmitted. 0 turns retransmission off",type=AttributeType.TIME) protected long xmit_interval=1000; @Property(description="Number of rows of the matrix in the retransmission table (only for experts)",writable=false) protected int xmit_table_num_rows=100; @Property(description="Number of elements of a row of the matrix in the retransmission table; gets rounded to the " + "next power of 2 (only for experts). The capacity of the matrix is xmit_table_num_rows * xmit_table_msgs_per_row", writable=false) protected int xmit_table_msgs_per_row=1024; @Property(description="Resize factor of the matrix in the retransmission table (only for experts)",writable=false) protected double xmit_table_resize_factor=1.2; @Property(description="Number of milliseconds after which the matrix in the retransmission table " + "is compacted (only for experts)",writable=false,type=AttributeType.TIME) protected long xmit_table_max_compaction_time=10000; @Property(description="Size of the queue to hold messages received after creating the channel, but before being " + "connected (is_server=false). After becoming the server, the messages in the queue are fed into up() and the " + "queue is cleared. The motivation is to avoid retransmissions (see https://issues.redhat.com/browse/JGRP-1509 " + "for details).") protected int become_server_queue_size=50; @Property(description="Time during which identical warnings about messages from a non member will be suppressed. " + "0 disables this (every warning will be logged). Setting the log level to ERROR also disables this.", type=AttributeType.TIME) protected long suppress_time_non_member_warnings=60000; @Property(description="Max number of messages to ask for in a retransmit request. 0 disables this and uses " + "the max bundle size in the transport",type=AttributeType.SCALAR) protected int max_xmit_req_size; @Property(description="The max size of a message batch when delivering messages. 0 is unbounded") protected int max_batch_size; @Property(description="If enabled, multicasts the highest sent seqno every xmit_interval ms. This is skipped if " + "a regular message has been multicast, and the task aquiesces if the highest sent seqno hasn't changed for " + "resend_last_seqno_max_times times. Used to speed up retransmission of dropped last messages (JGRP-1904)") protected boolean resend_last_seqno=true; @Property(description="Max number of times the last seqno is resent before acquiescing if last seqno isn't incremented") protected int resend_last_seqno_max_times=1; @ManagedAttribute(description="True if sending a message can block at the transport level") protected boolean sends_can_block=true; /* -------------------------------------------------- JMX ---------------------------------------------------------- */ @ManagedAttribute(description="Number of messages sent",type=AttributeType.SCALAR) protected int num_messages_sent; @ManagedAttribute(description="Number of messages received",type=AttributeType.SCALAR) protected int num_messages_received; protected static final Message DUMMY_OOB_MSG=new EmptyMessage().setFlag(Message.Flag.OOB); // Accepts messages which are (1) non-null, (2) no DUMMY_OOB_MSGs and (3) not OOB_DELIVERED protected final Predicate no_dummy_and_no_oob_delivered_msgs_and_no_dont_loopback_msgs=msg -> msg != null && msg != DUMMY_OOB_MSG && (!msg.isFlagSet(Message.Flag.OOB) || msg.setFlagIfAbsent(OOB_DELIVERED)) && !(msg.isFlagSet(DONT_LOOPBACK) && Objects.equals(local_addr, msg.getSrc())); protected static final Predicate dont_loopback_filter=m -> m != null && (m.isFlagSet(DONT_LOOPBACK) || m == DUMMY_OOB_MSG || m.isFlagSet(OOB_DELIVERED)); protected static final BiConsumer BATCH_ACCUMULATOR=MessageBatch::add; protected static final Table.Visitor DECR=(seqno, msg, row, col) -> { if(msg instanceof Refcountable) ((Refcountable)msg).decr(); return true; }; protected final Function SEQNO_GETTER= m -> { NakAckHeader2 hdr=m != null? m.getHeader(id) : null; return hdr == null || hdr.getType() != NakAckHeader2.MSG? -1 : hdr.getSeqno(); }; protected final Predicate HAS_HEADER=m -> m != null && m.getHeader(id) != null; @ManagedAttribute(description="Number of retransmit requests received",type=AttributeType.SCALAR) protected final LongAdder xmit_reqs_received=new LongAdder(); @ManagedAttribute(description="Number of retransmit requests sent",type=AttributeType.SCALAR) protected final LongAdder xmit_reqs_sent=new LongAdder(); @ManagedAttribute(description="Number of retransmit responses received",type=AttributeType.SCALAR) protected final LongAdder xmit_rsps_received=new LongAdder(); @ManagedAttribute(description="Number of retransmit responses sent",type=AttributeType.SCALAR) protected final LongAdder xmit_rsps_sent=new LongAdder(); @ManagedAttribute(description="Is the retransmit task running") public boolean isXmitTaskRunning() {return xmit_task != null && !xmit_task.isDone();} @ManagedAttribute(description="Number of messages from non-members",type=AttributeType.SCALAR) public int getNonMemberMessages() { return suppress_log_non_member != null? suppress_log_non_member.getCache().size() : 0; } @ManagedOperation(description="Clears the cache for messages from non-members") public void clearNonMemberCache() { if(suppress_log_non_member != null) suppress_log_non_member.getCache().clear(); } @ManagedAttribute public void setResendLastSeqno(boolean flag) { if(resend_last_seqno != flag) resend_last_seqno=flag; if(resend_last_seqno) { if(last_seqno_resender == null) last_seqno_resender=new LastSeqnoResender(); } else { if(last_seqno_resender != null) last_seqno_resender=null; } } public NAKACK2 resendLastSeqno(boolean flag) {setResendLastSeqno(flag); return this;} @ManagedAttribute(description="Whether or not the task to resend the last seqno is running (depends on resend_last_seqno)") public boolean resendTaskRunning() {return last_seqno_resender != null;} @ManagedAttribute(description="tracing is enabled or disabled for the given log",writable=true) protected boolean is_trace=log.isTraceEnabled(); /* ------------------------------------------------- Fields ------------------------------------------------------------------------- */ protected volatile boolean is_server; protected volatile List
members=new ArrayList<>(); protected volatile View view; private final AtomicLong seqno=new AtomicLong(0); // current message sequence number (starts with 1) /** Map to store sent and received messages (keyed by sender) */ protected final ConcurrentMap> xmit_table=Util.createConcurrentMap(); /* Optimization: this is the table for my own messages (used in send()) */ protected Table local_xmit_table; /** RetransmitTask running every xmit_interval ms */ protected Future xmit_task; /** Used by the retransmit task to keep the last retransmitted seqno per sender (https://issues.redhat.com/browse/JGRP-1539) */ protected final Map xmit_task_map=new ConcurrentHashMap<>(); //* Used by stable to reduce the number of retransmissions (https://issues.redhat.com/browse/JGRP-2678) */ protected final Map stable_xmit_map=new ConcurrentHashMap<>(); protected volatile boolean leaving; protected volatile boolean running; protected TimeScheduler timer; protected LastSeqnoResender last_seqno_resender; protected final Lock rebroadcast_lock=new ReentrantLock(); protected final Condition rebroadcast_done=rebroadcast_lock.newCondition(); // set during processing of a rebroadcast event protected volatile boolean rebroadcasting=false; protected final Lock rebroadcast_digest_lock=new ReentrantLock(); @GuardedBy("rebroadcast_digest_lock") protected Digest rebroadcast_digest=null; /** Keeps the last N stability messages */ protected final BoundedList stability_msgs=new BoundedList<>(10); /** Keeps a bounded list of the last N digest sets */ protected final BoundedList digest_history=new BoundedList<>(10); protected Queue become_server_queue=new ConcurrentLinkedQueue<>(); /** Log to suppress identical warnings for messages from non-members */ protected SuppressLog
suppress_log_non_member; public long getXmitRequestsReceived() {return xmit_reqs_received.sum();} public long getXmitRequestsSent() {return xmit_reqs_sent.sum();} public long getXmitResponsesReceived() {return xmit_rsps_received.sum();} public long getXmitResponsesSent() {return xmit_rsps_sent.sum();} public boolean useMcastXmit() {return use_mcast_xmit;} public NAKACK2 useMcastXmit(boolean u) {this.use_mcast_xmit=u; return this;} public boolean useMcastXmitReq() {return use_mcast_xmit_req;} public NAKACK2 useMcastXmitReq(boolean flag) {this.use_mcast_xmit_req=flag; return this;} public boolean xmitFromRandomMember() {return xmit_from_random_member;} public NAKACK2 xmitFromRandomMember(boolean x) {this.xmit_from_random_member=x; return this;} public boolean discardDeliveredMsgs() {return discard_delivered_msgs;} public NAKACK2 discardDeliveredMsgs(boolean d) {this.discard_delivered_msgs=d; return this;} public boolean logDiscardMessages() {return log_discard_msgs;} public NAKACK2 logDiscardMessages(boolean l) {this.log_discard_msgs=l; return this;} public boolean logNotFoundMessages() {return log_not_found_msgs;} public NAKACK2 logNotFoundMessages(boolean flag) {log_not_found_msgs=flag; return this;} public NAKACK2 setResendLastSeqnoMaxTimes(int n) {this.resend_last_seqno_max_times=n; return this;} public int getResendLastSeqnoMaxTimes() {return resend_last_seqno_max_times;} public NAKACK2 setXmitFromRandomMember(boolean xmit_from_random_member) { this.xmit_from_random_member=xmit_from_random_member; return this; } public NAKACK2 setDiscardDeliveredMsgs(boolean discard_delivered_msgs) { this.discard_delivered_msgs=discard_delivered_msgs; return this; } public long getMaxRebroadcastTimeout() {return max_rebroadcast_timeout;} public NAKACK2 setMaxRebroadcastTimeout(long m) {this.max_rebroadcast_timeout=m; return this;} public long getXmitInterval() {return xmit_interval;} public NAKACK2 setXmitInterval(long x) {this.xmit_interval=x; return this;} public int getXmitTableNumRows() {return xmit_table_num_rows;} public NAKACK2 setXmitTableNumRows(int x) {this.xmit_table_num_rows=x; return this;} public int getXmitTableMsgsPerRow() {return xmit_table_msgs_per_row;} public NAKACK2 setXmitTableMsgsPerRow(int x) {this.xmit_table_msgs_per_row=x; return this;} public double getXmitTableResizeFactor() {return xmit_table_resize_factor;} public NAKACK2 setXmitTableResizeFactor(double x) {this.xmit_table_resize_factor=x; return this;} public long getXmitTableMaxCompactionTime() {return xmit_table_max_compaction_time;} public NAKACK2 setXmitTableMaxCompactionTime(long x) {this.xmit_table_max_compaction_time=x; return this;} public int getBecomeServerQueueSize() {return become_server_queue_size;} public NAKACK2 setBecomeServerQueueSize(int b) {this.become_server_queue_size=b; return this;} public long getSuppressTimeNonMemberWarnings() {return suppress_time_non_member_warnings;} public NAKACK2 setSuppressTimeNonMemberWarnings(long s) {this.suppress_time_non_member_warnings=s; return this;} public int getMaxXmitReqSize() {return max_xmit_req_size;} public NAKACK2 setMaxXmitReqSize(int m) {this.max_xmit_req_size=m; return this;} public boolean sendsCanBlock() {return sends_can_block;} public NAKACK2 sendsCanBlock(boolean s) {this.sends_can_block=s; return this;} public int getNumMessagesSent() {return num_messages_sent;} public NAKACK2 setNumMessagesSent(int n) {this.num_messages_sent=n; return this;} public int getNumMessagesReceived() {return num_messages_received;} public NAKACK2 setNumMessagesReceived(int n) {this.num_messages_received=n; return this;} public boolean isTrace() {return is_trace;} public NAKACK2 isTrace(boolean i) {this.is_trace=i; return this;} public T setLevel(String level) { T retval=super.setLevel(level); is_trace=log.isTraceEnabled(); return retval; } @ManagedAttribute(description="Actual size of the become_server_queue",type=AttributeType.SCALAR) public int getBecomeServerQueueSizeActual() { return become_server_queue.size(); } /** Returns the receive window for sender; only used for testing. Do not use ! */ public Table getWindow(Address sender) { return xmit_table.get(sender); } /** Only used for unit tests, don't use ! */ public void setTimer(TimeScheduler timer) {this.timer=timer;} @ManagedAttribute(description="Total number of undelivered messages in all retransmit buffers",type=AttributeType.SCALAR) public int getXmitTableUndeliveredMsgs() { int num=0; for(Table buf: xmit_table.values()) num+=buf.size(); return num; } @ManagedAttribute(description="Total number of missing (= not received) messages in all retransmit buffers" ,type=AttributeType.SCALAR) public int getXmitTableMissingMessages() { int num=0; for(Table buf: xmit_table.values()) num+=buf.getNumMissing(); return num; } @ManagedAttribute(description="Capacity of the retransmit buffer. Computed as xmit_table_num_rows * xmit_table_msgs_per_row") public long getXmitTableCapacity() { Table table=local_addr != null? xmit_table.get(local_addr) : null; return table != null? table.capacity() : 0; } @ManagedAttribute(description="Prints the number of rows currently allocated in the matrix. This value will not " + "be lower than xmit_table_now_rows") public int getXmitTableNumCurrentRows() { Table table=local_addr != null? xmit_table.get(local_addr) : null; return table != null? table.getNumRows() : 0; } @ManagedAttribute(description="Returns the number of bytes of all messages in all retransmit buffers. " + "To compute the size, Message.getLength() is used",type=AttributeType.BYTES) public long getSizeOfAllMessages() { long retval=0; for(Table buf: xmit_table.values()) retval+=sizeOfAllMessages(buf,false); return retval; } @ManagedAttribute(description="Returns the number of bytes of all messages in all retransmit buffers. " + "To compute the size, Message.size() is used",type=AttributeType.BYTES) public long getSizeOfAllMessagesInclHeaders() { long retval=0; for(Table buf: xmit_table.values()) retval+=sizeOfAllMessages(buf, true); return retval; } @ManagedAttribute(description="Number of retransmit table compactions") public int getXmitTableNumCompactions() { Table table=local_addr != null? xmit_table.get(local_addr) : null; return table != null? table.getNumCompactions() : 0; } @ManagedAttribute(description="Number of retransmit table moves") public int getXmitTableNumMoves() { Table table=local_addr != null? xmit_table.get(local_addr) : null; return table != null? table.getNumMoves() : 0; } @ManagedAttribute(description="Number of retransmit table resizes") public int getXmitTableNumResizes() { Table table=local_addr != null? xmit_table.get(local_addr) : null; return table != null? table.getNumResizes(): 0; } @ManagedAttribute(description="Number of retransmit table purges") public int getXmitTableNumPurges() { Table table=local_addr != null? xmit_table.get(local_addr) : null; return table != null? table.getNumPurges(): 0; } @ManagedOperation(description="Prints the contents of the receiver windows for all members") public String printMessages() { StringBuilder ret=new StringBuilder(local_addr + ":\n"); for(Map.Entry> entry: xmit_table.entrySet()) { Address addr=entry.getKey(); Table buf=entry.getValue(); ret.append(addr).append(": ").append(buf.toString()).append('\n'); } return ret.toString(); } @ManagedAttribute public long getCurrentSeqno() {return seqno.get();} @ManagedOperation(description="Prints the stability messages received") public String printStabilityMessages() { return Util.printListWithDelimiter(stability_msgs, "\n"); } @ManagedOperation(description="Keeps information about the last N times a digest was set or merged") public String printDigestHistory() { StringBuilder sb=new StringBuilder(local_addr + ":\n"); for(String tmp: digest_history) sb.append(tmp).append("\n"); return sb.toString(); } @ManagedOperation(description="Compacts the retransmit buffer") public void compact() { Table table=local_addr != null? xmit_table.get(local_addr) : null; if(table != null) table.compact(); } @ManagedOperation(description="Prints the number of rows currently allocated in the matrix for all members. " + "This value will not be lower than xmit_table_now_rows") public String dumpXmitTablesNumCurrentRows() { StringBuilder sb=new StringBuilder(); for(Map.Entry> entry: xmit_table.entrySet()) sb.append(String.format("%s: %d\n", entry.getKey(), entry.getValue().getNumRows())); return sb.toString(); } @ManagedOperation(description="Resets all statistics") public void resetStats() { num_messages_sent=num_messages_received=0; xmit_reqs_received.reset(); xmit_reqs_sent.reset(); xmit_rsps_received.reset(); xmit_rsps_sent.reset(); stability_msgs.clear(); digest_history.clear(); Table table=local_addr != null? xmit_table.get(local_addr) : null; if(table != null) table.resetStats(); } public void init() throws Exception { if(xmit_from_random_member && discard_delivered_msgs) { discard_delivered_msgs=false; log.debug("%s: xmit_from_random_member set to true: changed discard_delivered_msgs to false", local_addr); } TP transport=getTransport(); sends_can_block=transport instanceof TCP; // UDP and TCP_NIO2 won't block transport.registerProbeHandler(this); if(!transport.supportsMulticasting()) { if(use_mcast_xmit) { log.debug(Util.getMessage("NoMulticastTransport"), "use_mcast_xmit", transport.getName(), "use_mcast_xmit"); use_mcast_xmit=false; } if(use_mcast_xmit_req) { log.debug(Util.getMessage("NoMulticastTransport"), "use_mcast_xmit_req", transport.getName(), "use_mcast_xmit_req"); use_mcast_xmit_req=false; } } if(xmit_interval <= 0) { // https://issues.redhat.com/browse/JGRP-2675 become_server_queue=new ConcurrentLinkedQueue<>(); RejectedExecutionHandler handler=transport.getThreadPool().getRejectedExecutionHandler(); if(handler != null && !isCallerRunsHandler(handler)) { log.warn("%s: xmit_interval of %d requires a CallerRunsPolicy in the thread pool; replacing %s", local_addr, xmit_interval, handler.getClass().getSimpleName()); transport.getThreadPool().setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); } Class cl=RED.class; if(stack.findProtocol(cl) != null) { String e=String.format("found %s: when retransmission is disabled (xmit_interval=0), this can lead " + "to message loss. Please remove %s, or enable retransmission", cl.getSimpleName(), cl.getSimpleName()); throw new IllegalStateException(e); } } else { if(become_server_queue_size <= 0) { log.warn("%s: %s.become_server_queue_size is <= 0; setting it to 10", local_addr, NAKACK2.class.getSimpleName()); become_server_queue_size=10; } become_server_queue=new ArrayBlockingQueue<>(become_server_queue_size); } if(suppress_time_non_member_warnings > 0) suppress_log_non_member=new SuppressLog<>(log, "MsgDroppedNak", "SuppressMsg"); // max bundle size (minus overhead) divided by times bits per long int estimated_max_msgs_in_xmit_req=(transport.getBundler().getMaxSize() -50) * Global.LONG_SIZE; int old_max_xmit_size=max_xmit_req_size; if(max_xmit_req_size <= 0) max_xmit_req_size=estimated_max_msgs_in_xmit_req; else max_xmit_req_size=Math.min(max_xmit_req_size, estimated_max_msgs_in_xmit_req); if(old_max_xmit_size != max_xmit_req_size) log.trace("%s: set max_xmit_req_size from %d to %d", local_addr, old_max_xmit_size, max_xmit_req_size); if(resend_last_seqno) setResendLastSeqno(resend_last_seqno); } public List providedUpServices() { return Arrays.asList(Event.GET_DIGEST,Event.SET_DIGEST,Event.OVERWRITE_DIGEST,Event.MERGE_DIGEST); } public void start() throws Exception { timer=getTransport().getTimer(); if(timer == null) throw new Exception("timer is null"); running=true; leaving=false; startRetransmitTask(); } public void stop() { running=false; is_server=false; become_server_queue.clear(); stopRetransmitTask(); xmit_task_map.clear(); stable_xmit_map.clear(); local_xmit_table=null; // fixes https://issues.redhat.com/browse/JGRP-2720 reset(); } /** * Callback. Called by superclass when event may be handled.

Do not use {@code down_prot.down()} in this * method as the event is passed down by default by the superclass after this method returns ! */ public Object down(Event evt) { switch(evt.getType()) { case Event.STABLE: // generated by STABLE layer. Delete stable messages passed in arg stable(evt.getArg()); return null; // do not pass down further (Bela Aug 7 2001) case Event.GET_DIGEST: return getDigest(evt.getArg()); case Event.SET_DIGEST: setDigest(evt.getArg()); return null; case Event.OVERWRITE_DIGEST: overwriteDigest(evt.getArg()); return null; case Event.MERGE_DIGEST: mergeDigest(evt.getArg()); return null; case Event.TMP_VIEW: View tmp_view=evt.getArg(); members=tmp_view.getMembers(); break; case Event.VIEW_CHANGE: tmp_view=evt.getArg(); List

mbrs=tmp_view.getMembers(); members=mbrs; view=tmp_view; adjustReceivers(mbrs); is_server=true; // check vids from now on if(suppress_log_non_member != null) suppress_log_non_member.removeExpired(suppress_time_non_member_warnings); xmit_task_map.keySet().retainAll(mbrs); stable_xmit_map.keySet().retainAll(mbrs); break; case Event.BECOME_SERVER: is_server=true; flushBecomeServerQueue(); break; case Event.DISCONNECT: leaving=true; reset(); break; case Event.REBROADCAST: rebroadcasting=true; rebroadcast_digest=evt.getArg(); try { rebroadcastMessages(); } finally { rebroadcasting=false; rebroadcast_digest_lock.lock(); try { rebroadcast_digest=null; } finally { rebroadcast_digest_lock.unlock(); } } return null; } return down_prot.down(evt); } public Object down(Message msg) { Address dest=msg.getDest(); if(dest != null || msg.isFlagSet(Message.Flag.NO_RELIABILITY)) return down_prot.down(msg); // unicast address: not null and not mcast, pass down unchanged if(msg instanceof Refcountable) ((Refcountable)msg).incr(); send(msg); return null; // don't pass down the stack } /** * Callback. Called by superclass when event may be handled.

Do not use {@code passUp} in this * method as the event is passed up by default by the superclass after this method returns ! */ public Object up(Event evt) { switch(evt.getType()) { case Event.STABLE: // generated by STABLE layer. Delete stable messages passed in arg stable(evt.getArg()); return null; // do not pass up further (Bela Aug 7 2001) case Event.SUSPECT: // release the promise if rebroadcasting is in progress... otherwise we wait forever. there will be a new // flush round anyway if(rebroadcasting) cancelRebroadcasting(); break; } return up_prot.up(evt); } public Object up(Message msg) { if(msg.isFlagSet(Message.Flag.NO_RELIABILITY)) return up_prot.up(msg); NakAckHeader2 hdr=msg.getHeader(this.id); if(hdr == null) return up_prot.up(msg); // pass up (e.g. unicast msg) if(!is_server) { // discard messages while not yet server (i.e., until JOIN has returned) queueMessage(msg, hdr.seqno); return null; } switch(hdr.type) { case NakAckHeader2.MSG: handleMessage(msg, hdr); return null; case NakAckHeader2.XMIT_REQ: try { SeqnoList missing=msg.getObject(); if(missing != null) handleXmitReq(msg.getSrc(), missing, hdr.sender); } catch(Exception e) { log.error("failed deserializing retransmission list", e); } return null; case NakAckHeader2.XMIT_RSP: handleXmitRsp(msg, hdr); return null; case NakAckHeader2.HIGHEST_SEQNO: handleHighestSeqno(msg.getSrc(), hdr.seqno); return null; default: log.error(Util.getMessage("HeaderTypeNotKnown"), local_addr, hdr.type); return null; } } public void up(MessageBatch mb) { boolean found_nackack_msg=mb.anyMatch(HAS_HEADER); // found at least 1 msg with a NAKACK2 hdr if(!found_nackack_msg) { if(!mb.isEmpty()) up_prot.up(mb); return; } boolean got_retransmitted_msg=false; // if at least 1 XMIT-RSP was received for(FastArray.FastIterator it=(FastArray.FastIterator)mb.iterator(); it.hasNext();) { final Message msg=it.next(); NakAckHeader2 hdr; if(msg == null || msg.isFlagSet(Message.Flag.NO_RELIABILITY) || (hdr=msg.getHeader(id)) == null) continue; if(!is_server) { // discard messages while not yet server (i.e., until JOIN has returned) queueMessage(msg, hdr.seqno); it.remove(); continue; } switch(hdr.type) { case NakAckHeader2.MSG: break; case NakAckHeader2.XMIT_REQ: it.remove(); try { SeqnoList missing=msg.getObject(); if(missing != null) handleXmitReq(msg.getSrc(), missing, hdr.sender); } catch(Exception e) { log.error("failed deserializing retransmission list", e); } break; case NakAckHeader2.XMIT_RSP: Message xmitted_msg=msgFromXmitRsp(msg, hdr); if(xmitted_msg != null) { it.replace(xmitted_msg); got_retransmitted_msg=true; } break; case NakAckHeader2.HIGHEST_SEQNO: it.remove(); handleHighestSeqno(mb.sender(), hdr.seqno); break; default: log.error(Util.getMessage("HeaderTypeNotKnown"), local_addr, hdr.type); } } if(!mb.isEmpty()) handleMessageBatch(mb); // received XMIT-RSPs: if(got_retransmitted_msg && rebroadcasting) checkForRebroadcasts(); if(!mb.isEmpty()) up_prot.up(mb); } // ProbeHandler interface public Map handleProbe(String... keys) { Map retval=new HashMap<>(); for(String key: keys) { switch(key) { case "digest-history": retval.put(key, printDigestHistory()); break; case "dump-digest": retval.put(key, "\n" + printMessages()); break; } } return retval; } // ProbeHandler interface public String[] supportedKeys() { return new String[]{"digest-history", "dump-digest"}; } /* --------------------------------- Private Methods --------------------------------------- */ protected void queueMessage(Message msg, long seqno) { if(become_server_queue.offer(msg)) // discards item if queue is full log.trace("%s: message %s#%d was queued (not yet server)", local_addr, msg.getSrc(), seqno); else log.trace("%s: message %s#%d was discarded (not yet server, queue full)", local_addr, msg.getSrc(), seqno); } protected void unknownMember(Address sender, Object message) { if(leaving) return; if(log_discard_msgs && log.isWarnEnabled()) { if(suppress_log_non_member != null) suppress_log_non_member.log(SuppressLog.Level.warn, sender, suppress_time_non_member_warnings, local_addr, message, sender, view); else log.warn(Util.getMessage("MsgDroppedNak"), local_addr, message, sender, view); } } /** * Adds the message to the sent_msgs table and then passes it down the stack. Change Bela Ban May 26 2002: we don't * store a copy of the message, but a reference ! This saves us a lot of memory. However, this also means that a * message should not be changed after storing it in the sent-table ! See protocols/DESIGN for details. * Made seqno increment and adding to sent_msgs atomic, e.g. seqno won't get incremented if adding to * sent_msgs fails e.g. due to an OOM (see https://issues.redhat.com/browse/JGRP-179). bela Jan 13 2006 */ protected void send(Message msg) { if(!running) { log.trace("%s: discarded message as we're not in the 'running' state, message: %s", local_addr, msg); return; } Table buf=local_xmit_table; if(buf == null && (buf=local_xmit_table=xmit_table.get(local_addr)) == null) // discard message if there is no entry for local_addr return; if(msg.getSrc() == null) msg.setSrc(local_addr); // this needs to be done so we can check whether the message sender is the local_addr boolean dont_loopback_set=msg.isFlagSet(DONT_LOOPBACK); long msg_id=seqno.incrementAndGet(); long sleep=10; do { try { msg.putHeader(this.id, NakAckHeader2.createMessageHeader(msg_id)); buf.add(msg_id, msg, dont_loopback_set? dont_loopback_filter : null); break; } catch(Throwable t) { if(running) { Util.sleep(sleep); sleep=Math.min(5000, sleep*2); } } } while(running); // moved down_prot.down() out of synchronized clause (bela Sept 7 2006) https://issues.redhat.com/browse/JGRP-300 if(is_trace) log.trace("%s --> [all]: #%d", local_addr, msg_id); down_prot.down(msg); // if this fails, since msg is in sent_msgs, it can be retransmitted num_messages_sent++; if(resend_last_seqno && last_seqno_resender != null) last_seqno_resender.skipNext(); } protected void resend(Message msg) { // needed for byteman ProtPerf script - don't remove! down_prot.down(msg); } /** * Finds the corresponding retransmit buffer and adds the message to it (according to seqno). Then removes as many * messages as possible and passes them up the stack. Discards messages from non-members. */ protected void handleMessage(Message msg, NakAckHeader2 hdr) { Address sender=msg.getSrc(); Table buf=xmit_table.get(sender); if(buf == null) { // discard message if there is no entry for sender unknownMember(sender, hdr.seqno); return; } num_messages_received++; boolean loopback=local_addr.equals(sender); // If the message was sent by myself, then it is already in the table and we don't need to add it. If not, // and the message is OOB, insert a dummy message (same msg, saving space), deliver it and drop it later on // removal. Else insert the real message boolean added=loopback || buf.add(hdr.seqno, msg.isFlagSet(Message.Flag.OOB)? DUMMY_OOB_MSG : msg); // OOB msg is passed up. When removed, we discard it. Affects ordering: https://issues.redhat.com/browse/JGRP-379 if(added && msg.isFlagSet(Message.Flag.OOB)) { if(loopback) { // sent by self msg=buf.get(hdr.seqno); // we *have* to get a message, because loopback means we didn't add it to win ! if(msg != null && msg.isFlagSet(Message.Flag.OOB) && msg.setFlagIfAbsent(OOB_DELIVERED)) deliver(msg, sender, hdr.seqno, "OOB message"); } else // sent by someone else deliver(msg, sender, hdr.seqno, "OOB message"); } removeAndDeliver(buf, sender, loopback, null); // at most 1 thread will execute this at any given time } protected void handleMessageBatch(MessageBatch mb) { Address sender=mb.sender(); Table buf=xmit_table.get(sender); if(buf == null) { // discard message if there is no entry for sender mb.removeIf(HAS_HEADER, true); unknownMember(sender, "batch"); return; } int size=mb.size(); num_messages_received+=size; boolean loopback=local_addr.equals(sender), oob=mb.mode() == OOB; boolean added=loopback || buf.add(mb, SEQNO_GETTER, !oob, oob? DUMMY_OOB_MSG : null); // OOB msg is passed up. When removed, we discard it. Affects ordering: https://issues.redhat.com/browse/JGRP-379 if(added && oob) { Address dest=mb.dest(); MessageBatch oob_batch=loopback? new MessageBatch(dest, sender, null, dest == null, OOB, size) : mb; if(loopback) { for(Message m: mb) { long seq=SEQNO_GETTER.apply(m); Message msg=buf.get(seq); // we *have* to get the message, because loopback means we didn't add it to win ! if(msg != null && msg.isFlagSet(Message.Flag.OOB) && msg.setFlagIfAbsent(OOB_DELIVERED)) oob_batch.add(msg); } } deliverBatch(oob_batch); } removeAndDeliver(buf, sender, loopback, mb.clusterName()); // at most 1 thread will execute this at any given time if(oob || loopback) mb.removeIf(HAS_HEADER, true); } /** Efficient way of checking whether another thread is already processing messages from sender. If that's the case, * we return immediately and let the existing thread process our message (https://issues.redhat.com/browse/JGRP-829). * Benefit: fewer threads blocked on the same lock, these threads can be returned to the thread pool */ protected void removeAndDeliver(Table buf, Address sender, boolean loopback, AsciiString cluster_name) { AtomicInteger adders=buf.getAdders(); if(adders.getAndIncrement() != 0) return; boolean remove_msgs=discard_delivered_msgs && !loopback; MessageBatch batch=new MessageBatch(buf.size()).dest(null).sender(sender).clusterName(cluster_name).multicast(true); Supplier batch_creator=() -> batch; do { try { batch.reset(); // Don't include DUMMY and OOB_DELIVERED messages in the removed set buf.removeMany(remove_msgs, max_batch_size, no_dummy_and_no_oob_delivered_msgs_and_no_dont_loopback_msgs, batch_creator, BATCH_ACCUMULATOR); } catch(Throwable t) { log.error("failed removing messages from table for " + sender, t); } if(!batch.isEmpty()) deliverBatch(batch); } while(adders.decrementAndGet() != 0); if(rebroadcasting) checkForRebroadcasts(); } /** * Retransmits messsages first_seqno to last_seqno from original_sender from xmit_table to xmit_requester, * called when XMIT_REQ is received. * @param xmit_requester The sender of the XMIT_REQ, we have to send the requested copy of the message to this address * @param missing_msgs A list of seqnos that have to be retransmitted * @param original_sender The member who originally sent the messsage. Guaranteed to be non-null */ protected void handleXmitReq(Address xmit_requester, SeqnoList missing_msgs, Address original_sender) { log.trace("%s <-- %s: XMIT(%s%s)", local_addr, xmit_requester, original_sender, missing_msgs); if(stats) xmit_reqs_received.add(missing_msgs.size()); Table buf=xmit_table.get(original_sender); if(buf == null) { log.error(Util.getMessage("SenderNotFound"), local_addr, original_sender); return; } for(long i: missing_msgs) { Message msg=buf.get(i); if(msg == null) { if(log.isWarnEnabled() && log_not_found_msgs && !local_addr.equals(xmit_requester) && i > buf.getLow()) log.warn(Util.getMessage("MessageNotFound"), local_addr, original_sender, i); continue; } if(is_trace) log.trace("%s --> [all]: resending %s#%d", local_addr, original_sender, i); sendXmitRsp(xmit_requester, msg); } } protected void deliver(Message msg, Address sender, long seqno, String error_msg) { if(is_trace) log.trace("%s <-- %s: #%d", local_addr, sender, seqno); try { up_prot.up(msg); } catch(Throwable t) { log.error(Util.getMessage("FailedToDeliverMsg"), local_addr, error_msg, msg, t); } } protected void deliverBatch(MessageBatch batch) { try { if(batch == null || batch.isEmpty()) return; if(is_trace) { Message first=batch.first(), last=batch.last(); StringBuilder sb=new StringBuilder(local_addr + " <-- " + batch.sender() + ": "); if(first != null && last != null) { NakAckHeader2 hdr1=first.getHeader(id), hdr2=last.getHeader(id); if(hdr1 != null && hdr2 != null) sb.append("#").append(hdr1.seqno).append("-").append(hdr2.seqno); } sb.append(" (" + batch.size()).append(" messages)"); log.trace(sb); } up_prot.up(batch); batch.clear(); } catch(Throwable t) { log.error(Util.getMessage("FailedToDeliverMsg"), local_addr, "batch", batch, t); } } /** * Flushes the queue. Done in a separate thread as we don't want to block the * {@link GMS#installView(org.jgroups.View,org.jgroups.util.Digest)} method (called when a view is installed). */ protected void flushBecomeServerQueue() { if(!become_server_queue.isEmpty()) { log.trace("%s: flushing become_server_queue (%d elements)", local_addr, become_server_queue.size()); TP transport=getTransport(); for(;;) { final Message msg=become_server_queue.poll(); if(msg == null) break; transport.getThreadPool().execute(() -> up(msg)); } } } protected void cancelRebroadcasting() { rebroadcast_lock.lock(); try { rebroadcasting=false; rebroadcast_done.signalAll(); } finally { rebroadcast_lock.unlock(); } } /** * Sends a message msg to the requester. We have to wrap the original message into a retransmit message, as we need * to preserve the original message's properties, such as src, headers etc. * @param dest * @param msg */ protected void sendXmitRsp(Address dest, Message msg) { if(msg == null) return; if(stats) xmit_rsps_sent.increment(); if(msg.getSrc() == null) msg.setSrc(local_addr); if(use_mcast_xmit) { // we simply send the original multicast message resend(msg); return; } Message xmit_msg=msg.copy(true, true).setDest(dest); // copy payload and headers NakAckHeader2 hdr=xmit_msg.getHeader(id); NakAckHeader2 newhdr=hdr.copy(); newhdr.type=NakAckHeader2.XMIT_RSP; // change the type in the copy from MSG --> XMIT_RSP xmit_msg.putHeader(id, newhdr); resend(xmit_msg); } protected void handleXmitRsp(Message msg, NakAckHeader2 hdr) { if(msg == null) return; try { if(stats) xmit_rsps_received.increment(); msg.setDest(null); NakAckHeader2 newhdr=hdr.copy(); newhdr.type=NakAckHeader2.MSG; // change the type back from XMIT_RSP --> MSG msg.putHeader(id, newhdr); handleMessage(msg, newhdr); if(rebroadcasting) checkForRebroadcasts(); } catch(Exception ex) { log.error(Util.getMessage("FailedToDeliverMsg"), local_addr, "retransmitted message", msg, ex); } } /** * Compares the sender's highest seqno with my highest seqno: if the sender's is higher, ask sender for retransmission * @param sender The sender * @param seqno The highest seqno sent by sender */ protected void handleHighestSeqno(Address sender, long seqno) { // check whether the highest seqno received from sender is > highest seqno received for sender in my digest. // If yes, request retransmission (see "Last Message Dropped" topic in DESIGN) Table buf=xmit_table.get(sender); if(buf == null) return; long my_highest_received=buf.getHighestReceived(); if(my_highest_received >= 0 && seqno > my_highest_received) { log.trace("%s: my_highest_rcvd (%s#%d) < highest received (%s#%d): requesting retransmission", local_addr, sender, my_highest_received, sender, seqno); retransmit(seqno, seqno, sender, false); } } protected Message msgFromXmitRsp(Message msg, NakAckHeader2 hdr) { if(msg == null) return null; if(stats) xmit_rsps_received.increment(); msg.setDest(null); NakAckHeader2 newhdr=hdr.copy(); newhdr.type=NakAckHeader2.MSG; // change the type back from XMIT_RSP --> MSG msg.putHeader(id,newhdr); return msg; } /** * Takes the argument highest_seqnos and compares it to the current digest. If the current digest has fewer messages, * then send retransmit messages for the missing messages. Return when all missing messages have been received. If * we're waiting for a missing message from P, and P crashes while waiting, we need to exclude P from the wait set. */ protected void rebroadcastMessages() { Digest their_digest; long sleep=max_rebroadcast_timeout / NUM_REBROADCAST_MSGS; long wait_time=max_rebroadcast_timeout, start=System.currentTimeMillis(); while(wait_time > 0) { rebroadcast_digest_lock.lock(); try { if(rebroadcast_digest == null) break; their_digest=rebroadcast_digest.copy(); } finally { rebroadcast_digest_lock.unlock(); } Digest my_digest=getDigest(); boolean xmitted=false; for(Digest.Entry entry: their_digest) { Address member=entry.getMember(); long[] my_entry=my_digest.get(member); if(my_entry == null) continue; long their_high=entry.getHighest(); // Cannot ask for 0 to be retransmitted because the first seqno in NAKACK2 and UNICAST(2) is always 1 ! // Also, we need to ask for retransmission of my_high+1, because we already *have* my_high, and don't // need it, so the retransmission range is [my_high+1 .. their_high]: *exclude* my_high, but *include* // their_high long my_high=Math.max(my_entry[0], my_entry[1]); if(their_high > my_high) { log.trace("%s: fetching %d-%d from %s", local_addr, my_high, their_high, member); retransmit(my_high+1, their_high, member, true); // use multicast to send retransmit request xmitted=true; } } if(!xmitted) return; // we're done; no retransmissions are needed anymore. our digest is >= rebroadcast_digest rebroadcast_lock.lock(); try { try { my_digest=getDigest(); rebroadcast_digest_lock.lock(); try { if(!rebroadcasting || isGreaterThanOrEqual(my_digest, rebroadcast_digest)) return; } finally { rebroadcast_digest_lock.unlock(); } rebroadcast_done.await(sleep, TimeUnit.MILLISECONDS); wait_time-=(System.currentTimeMillis() - start); } catch(InterruptedException ignored) { } } finally { rebroadcast_lock.unlock(); } } } protected void checkForRebroadcasts() { Digest tmp=getDigest(); boolean cancel_rebroadcasting=false; rebroadcast_digest_lock.lock(); try { cancel_rebroadcasting=isGreaterThanOrEqual(tmp, rebroadcast_digest); } catch(Throwable ignored) { ; } finally { rebroadcast_digest_lock.unlock(); } if(cancel_rebroadcasting) cancelRebroadcasting(); } /** * Returns true if all senders of the current digest have their seqnos >= the ones from other */ protected static boolean isGreaterThanOrEqual(Digest first, Digest other) { if(other == null) return true; for(Digest.Entry entry: first) { Address sender=entry.getMember(); long[] their_entry=other.get(sender); if(their_entry == null) continue; long my_highest=entry.getHighest(); long their_highest=Math.max(their_entry[0],their_entry[1]); if(my_highest < their_highest) return false; } return true; } protected static boolean isCallerRunsHandler(RejectedExecutionHandler h) { return h instanceof ThreadPoolExecutor.CallerRunsPolicy || (h instanceof ShutdownRejectedExecutionHandler && ((ShutdownRejectedExecutionHandler)h).handler() instanceof ThreadPoolExecutor.CallerRunsPolicy); } /** * Removes old members from xmit-table and adds new members to xmit-table (at seqnos hd=0, hr=0). * This method is not called concurrently */ protected void adjustReceivers(List

members) { Set
keys=xmit_table.keySet(); // remove members which left for(Address member: keys) { if(!members.contains(member)) { if(Objects.equals(local_addr, member)) continue; Table buf=xmit_table.remove(member); if(buf != null) log.debug("%s: removed %s from xmit_table (not member anymore)", local_addr, member); } } members.stream().filter(mbr -> !keys.contains(mbr)).forEach(mbr -> xmit_table.putIfAbsent(mbr, createTable(0))); } /** * Returns a message digest: for each member P the highest delivered and received seqno is added */ public Digest getDigest() { final Map map=new HashMap<>(); for(Map.Entry> entry: xmit_table.entrySet()) { Address sender=entry.getKey(); // guaranteed to be non-null (CCHM) Table buf=entry.getValue(); // guaranteed to be non-null (CCHM) long[] seqnos=buf.getDigest(); map.put(sender, seqnos); } return new Digest(map); } public Digest getDigest(Address mbr) { if(mbr == null) return getDigest(); Table buf=xmit_table.get(mbr); if(buf == null) return null; long[] seqnos=buf.getDigest(); return new Digest(mbr, seqnos[0], seqnos[1]); } /** * Creates a retransmit buffer for each sender in the digest according to the sender's seqno. * If a buffer already exists, it resets it. */ protected void setDigest(Digest digest) { setDigest(digest,false); } /** * For all members of the digest, adjust the retransmit buffers in xmit_table. If no entry * exists, create one with the initial seqno set to the seqno of the member in the digest. If the member already * exists, and is not the local address, replace it with the new entry (https://issues.redhat.com/browse/JGRP-699) * if the digest's seqno is greater than the seqno in the window. */ protected void mergeDigest(Digest digest) { setDigest(digest,true); } /** * Overwrites existing entries, but does NOT remove entries not found in the digest * @param digest */ protected void overwriteDigest(Digest digest) { if(digest == null) return; StringBuilder sb=new StringBuilder("\n[overwriteDigest()]\n"); sb.append("existing digest: " + getDigest()).append("\nnew digest: " + digest); for(Digest.Entry entry: digest) { Address member=entry.getMember(); if(member == null) continue; long highest_delivered_seqno=entry.getHighestDeliveredSeqno(); Table buf=xmit_table.get(member); if(buf != null) { if(local_addr.equals(member)) { // Adjust the highest_delivered seqno (to send msgs again): https://issues.redhat.com/browse/JGRP-1251 buf.setHighestDelivered(highest_delivered_seqno); continue; // don't destroy my own window } xmit_table.remove(member); } buf=createTable(highest_delivered_seqno); xmit_table.put(member, buf); } sb.append("\n").append("resulting digest: " + getDigest().toString(digest)); digest_history.add(sb.toString()); log.debug(sb.toString()); } /** * Sets or merges the digest. If there is no entry for a given member in xmit_table, create a new buffer. * Else skip the existing entry, unless it is a merge. In this case, skip the existing entry if its seqno is * greater than or equal to the one in the digest, or reset the window and create a new one if not. * @param digest The digest * @param merge Whether to merge the new digest with our own, or not */ protected void setDigest(Digest digest, boolean merge) { if(digest == null) return; StringBuilder sb=log.isDebugEnabled()? new StringBuilder("\n[" + local_addr + (merge? " mergeDigest()]\n" : " setDigest()]\n")) .append("existing digest: " + getDigest()).append("\nnew digest: " + digest) : null; boolean set_own_seqno=false; for(Digest.Entry entry: digest) { Address member=entry.getMember(); if(member == null) continue; long highest_delivered_seqno=entry.getHighestDeliveredSeqno(); Table buf=xmit_table.get(member); if(buf != null) { // We only reset the window if its seqno is lower than the seqno shipped with the digest. Also, we // don't reset our own window (https://issues.redhat.com/browse/JGRP-948, comment 20/Apr/09 03:39 AM) if(!merge || (Objects.equals(local_addr, member)) // never overwrite our own entry || buf.getHighestDelivered() >= highest_delivered_seqno) // my seqno is >= digest's seqno for sender continue; xmit_table.remove(member); // to get here, merge must be false ! if(member.equals(local_addr)) { // Adjust the seqno: https://issues.redhat.com/browse/JGRP-1251 seqno.set(highest_delivered_seqno); set_own_seqno=true; } } buf=createTable(highest_delivered_seqno); xmit_table.put(member, buf); } if(sb != null) { sb.append("\n").append("resulting digest: " + getDigest().toString(digest)); if(set_own_seqno) sb.append("\nnew seqno for " + local_addr + ": " + seqno); digest_history.add(sb.toString()); log.debug(sb.toString()); } } protected Table createTable(long initial_seqno) { return new Table<>(xmit_table_num_rows, xmit_table_msgs_per_row, initial_seqno, xmit_table_resize_factor, xmit_table_max_compaction_time); } /** * Garbage collect messages that have been seen by all members. Update sent_msgs: for the sender P in the digest * which is equal to the local address, garbage collect all messages <= seqno at digest[P]. Update xmit_table: * for each sender P in the digest and its highest seqno seen SEQ, garbage collect all delivered_msgs in the * retransmit buffer corresponding to P which are <= seqno at digest[P]. */ protected void stable(Digest digest) { if(members == null || local_addr == null || digest == null) return; log.trace("%s: received stable digest %s", local_addr, digest); stability_msgs.add(digest.toString()); for(Digest.Entry entry: digest) { Address member=entry.getMember(); if(member == null) continue; long hd=entry.getHighestDeliveredSeqno(); long hr=entry.getHighestReceivedSeqno(); // check whether the last seqno received for a sender P in the stability digest is > last seqno // received for P in my digest. if yes, request retransmission (see "Last Message Dropped" topic in DESIGN) Table buf=xmit_table.get(member); if(buf != null && xmit_interval > 0) { long my_hr=buf.getHighestReceived(); Long prev_hr=stable_xmit_map.get(member); if(prev_hr != null && prev_hr > my_hr) { log.trace("%s: my_highest_rcvd (%d) < stability_highest_rcvd (%d): requesting retransmission of %s", local_addr, my_hr, prev_hr, member + "#" + prev_hr); retransmit(prev_hr, prev_hr, member, false); } stable_xmit_map.put(member, hr); } // delete *delivered* msgs that are stable (all messages with seqnos <= seqno) if(hd >= 0 && buf != null) { buf.forEach(buf.getLow(), hd, DECR); log.trace("%s: deleting msgs <= %s from %s", local_addr, hd, member); buf.purge(hd); } } } protected void retransmit(long first_seqno, long last_seqno, final Address sender, boolean multicast_xmit_request) { if(first_seqno > last_seqno) return; SeqnoList list=new SeqnoList((int)(last_seqno - first_seqno +1), first_seqno).add(first_seqno, last_seqno); retransmit(list,sender,multicast_xmit_request); } protected void retransmit(SeqnoList missing_msgs, final Address sender, boolean multicast_xmit_request) { Address dest=(multicast_xmit_request || this.use_mcast_xmit_req)? null : sender; // to whom do we send the XMIT request ? if(xmit_from_random_member && !local_addr.equals(sender)) { Address random_member=Util.pickRandomElement(members); if(random_member != null && !local_addr.equals(random_member)) dest=random_member; } Message retransmit_msg=new ObjectMessage(dest, missing_msgs).setFlag(Message.Flag.OOB) .putHeader(this.id, NakAckHeader2.createXmitRequestHeader(sender)); log.trace("%s --> %s: XMIT_REQ(%s)", local_addr, dest, missing_msgs); down_prot.down(retransmit_msg); if(stats) xmit_reqs_sent.add(missing_msgs.size()); } protected void reset() { seqno.set(0); xmit_table.clear(); } protected static long sizeOfAllMessages(Table buf, boolean include_headers) { return buf.stream().reduce(0L, (size,el) -> { if(el == null) return size; else return size + (include_headers? el.size() : el.getLength()); }, (l,r) -> l); } protected void startRetransmitTask() { if(xmit_interval > 0 && (xmit_task == null || xmit_task.isDone())) xmit_task=timer.scheduleWithFixedDelay(new RetransmitTask(), 0, xmit_interval, TimeUnit.MILLISECONDS, sends_can_block); } protected void stopRetransmitTask() { if(xmit_task != null) { xmit_task.cancel(true); xmit_task=null; } } /** * Retransmitter task which periodically (every xmit_interval ms) looks at all the retransmit tables and * sends retransmit request to all members from which we have missing messages */ protected class RetransmitTask implements Runnable { public void run() { triggerXmit(); } public String toString() { return NAKACK2.class.getSimpleName() + ": RetransmitTask (interval=" + xmit_interval + " ms)"; } } @ManagedOperation(description="Triggers the retransmission task, asking all senders for missing messages") public void triggerXmit() { for(Map.Entry> entry: xmit_table.entrySet()) { Address target=entry.getKey(); // target to send retransmit requests to Table buf=entry.getValue(); SeqnoList missing; if(buf != null && buf.getNumMissing() > 0 && (missing=buf.getMissing(max_xmit_req_size)) != null) { // getNumMissing() is fast long highest=missing.getLast(); Long prev_seqno=xmit_task_map.get(target); if(prev_seqno == null) { xmit_task_map.put(target, highest); // no retransmission } else { missing.removeHigherThan(prev_seqno); // we only retransmit the 'previous batch' if(highest > prev_seqno) xmit_task_map.put(target, highest); if(!missing.isEmpty()) { // remove msgs that are <= highest-delivered (https://issues.redhat.com/browse/JGRP-2574) long highest_deliverable=buf.getHighestDeliverable(), first=missing.getFirst(); if(first < highest_deliverable) missing.removeLowerThan(highest_deliverable + 1); retransmit(missing, target, false); } } } else if(!xmit_task_map.isEmpty()) xmit_task_map.remove(target); // no current gaps for target } if(resend_last_seqno && last_seqno_resender != null) last_seqno_resender.execute(seqno.get()); } /** Class which is called by RetransmitTask to resend the last seqno sent (if resend_last_seqno is enabled) */ protected class LastSeqnoResender { // Number of times the same seqno has been sent (acquiesces after resend_last_seqno_max_times) protected int num_resends; protected long last_seqno_resent; // the last seqno that was resent by this task // set to true when a regular msg is sent to prevent the task from running protected final AtomicBoolean skip_next_resend=new AtomicBoolean(false); protected void skipNext() { skip_next_resend.compareAndSet(false,true); } protected void execute(long seqno) { if(seqno == 0 || skip_next_resend.compareAndSet(true,false)) return; if(seqno == last_seqno_resent && num_resends >= resend_last_seqno_max_times) return; if(seqno > last_seqno_resent) { last_seqno_resent=seqno; num_resends=1; } else num_resends++; Message msg=new EmptyMessage(null).putHeader(id, NakAckHeader2.createHighestSeqnoHeader(seqno)) .setFlag(Message.Flag.OOB) .setFlag(DONT_LOOPBACK); // we don't need to receive our own broadcast down_prot.down(msg); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy