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

org.jgroups.protocols.UNICAST2 Maven / Gradle / Ivy

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

import org.jgroups.*;
import org.jgroups.annotations.MBean;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.annotations.Property;
import org.jgroups.conf.PropertyConverters;
import org.jgroups.stack.Protocol;
import org.jgroups.util.*;

import java.io.DataInput;
import java.io.DataOutput;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
 * Reliable unicast layer. Implemented with negative acks. Every sender keeps its messages in an AckSenderWindow. A
 * receiver stores incoming messages in a NakReceiverWindow, and asks the sender for retransmission if a gap is
 * detected. Every now and then (stable_interval), a timer task sends a STABLE message to all senders, including the
 * highest received and delivered seqnos. A sender purges messages lower than highest delivered and asks the STABLE
 * sender for messages it might have missed (smaller than highest received). A STABLE message can also be sent when
 * a receiver has received more than max_bytes from a given sender.

* The advantage of this protocol over {@link org.jgroups.protocols.UNICAST} is that it doesn't send acks for every * message. Instead, it sends 'acks' after receiving max_bytes and/ or periodically (stable_interval). * @author Bela Ban * @deprecated Will be removed in 4.0 */ @Deprecated @MBean(description="Reliable unicast layer") public class UNICAST2 extends Protocol implements AgeOutCache.Handler

{ public static final long DEFAULT_FIRST_SEQNO=Global.DEFAULT_FIRST_UNICAST_SEQNO; /* ------------------------------------------ Properties ------------------------------------------ */ @Deprecated protected int[] timeout= {400, 800, 1600, 3200}; // for NakSenderWindow: max time to wait for missing acks /** * The first value (in milliseconds) to use in the exponential backoff * retransmission mechanism. Only enabled if the value is > 0 */ @Deprecated @Property(description="The first value (in milliseconds) to use in the exponential backoff. Enabled if greater than 0", deprecatedMessage="Not used anymore") protected int exponential_backoff=300; @Property(description="Max number of messages to be removed from a NakReceiverWindow. This property might " + "get removed anytime, so don't use it !") protected int max_msg_batch_size=500; @Property(description="Max number of bytes before a stability message is sent to the sender") protected long max_bytes=10000000; @Property(description="Max number of milliseconds before a stability message is sent to the sender(s)") protected long stable_interval=60000L; @Property(description="Max number of STABLE messages sent for the same highest_received seqno. A value < 1 is invalid") protected int max_stable_msgs=5; @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 (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=2000; @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) protected long xmit_table_max_compaction_time=10 * 60 * 1000; @Deprecated @Property(description="If enabled, the removal of a message from the retransmission table causes an " + "automatic purge (only for experts)",writable=false, deprecatedMessage="not used anymore") protected boolean xmit_table_automatic_purging=true; @Property(description="Whether to use the old retransmitter which retransmits individual messages or the new one " + "which uses ranges of retransmitted messages. Default is true. Note that this property will be removed in 3.0; " + "it is only used to switch back to the old (and proven) retransmitter mechanism if issues occur") protected boolean use_range_based_retransmitter=true; @Property(description="If true, trashes warnings about retransmission messages not found in the xmit_table (used for testing)") protected boolean log_not_found_msgs=true; @Property(description="Time (in milliseconds) after which an idle incoming or outgoing connection is closed. The " + "connection will get re-established when used again. 0 disables connection reaping") protected long conn_expiry_timeout=0; // @Property(description="Max time (in ms) after which a connection to a non-member is closed") protected long max_retransmit_time=60 * 1000L; @Property(description="Interval (in milliseconds) at which missing messages (from all retransmit buffers) " + "are retransmitted") protected long xmit_interval=1000; /* --------------------------------------------- JMX ---------------------------------------------- */ @ManagedAttribute(description="Number of messages sent") protected int num_messages_sent=0; @ManagedAttribute(description="Number of messages received") protected int num_messages_received=0; /* --------------------------------------------- Fields ------------------------------------------------ */ protected final ConcurrentMap send_table=Util.createConcurrentMap(); protected final ConcurrentMap recv_table=Util.createConcurrentMap(); /** 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.jboss.org/browse/JGRP-1539) */ protected final Map xmit_task_map=new HashMap<>(); protected final ReentrantLock recv_table_lock=new ReentrantLock(); protected volatile List
members=new ArrayList<>(11); protected Address local_addr=null; protected TimeScheduler timer=null; // used for retransmissions (passed to AckSenderWindow) protected volatile boolean running=false; protected short last_conn_id=0; protected AgeOutCache
cache=null; protected Future stable_task_future=null; // bcasts periodic STABLE message (added to timer below) protected Future connection_reaper; // closes idle connections protected static final Filter dont_loopback_filter=new Filter() { public boolean accept(Message msg) { return msg != null && msg.isTransientFlagSet(Message.TransientFlag.DONT_LOOPBACK); } }; @Deprecated public int[] getTimeout() {return timeout;} @Deprecated @Property(name="timeout",converter=PropertyConverters.IntegerArray.class, description="list of timeouts", deprecatedMessage="not used anymore") public void setTimeout(int[] val) {} public void setMaxMessageBatchSize(int size) { if(size >= 1) max_msg_batch_size=size; } @ManagedAttribute public String getLocalAddress() {return local_addr != null? local_addr.toString() : "null";} @ManagedAttribute public String getMembers() {return Util.printListWithDelimiter(members, ",");} @ManagedAttribute(description="Returns the number of outgoing (send) connections") public int getNumSendConnections() { return send_table.size(); } @ManagedAttribute(description="Returns the number of incoming (receive) connections") public int getNumReceiveConnections() { return recv_table.size(); } @ManagedAttribute(description="Returns the total number of outgoing (send) and incoming (receive) connections") public int getNumConnections() { return getNumReceiveConnections() + getNumSendConnections(); } @ManagedOperation public String printConnections() { StringBuilder sb=new StringBuilder(); if(!send_table.isEmpty()) { sb.append("send connections:\n"); for(Map.Entry entry: send_table.entrySet()) { sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } } if(!recv_table.isEmpty()) { sb.append("\nreceive connections:\n"); for(Map.Entry entry: recv_table.entrySet()) { sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } } return sb.toString(); } /** Is the send connection to target established */ public boolean connectionEstablished(Address target) { SenderEntry entry=target != null? send_table.get(target) : null; return entry != null && entry.connEstablished(); } @ManagedAttribute(description="Whether the ConnectionReaper task is running") public boolean isConnectionReaperRunning() {return connection_reaper != null && !connection_reaper.isDone();} @ManagedAttribute(description="Total number of undelivered messages in all receive windows") public long getXmitTableUndeliveredMessages() { long retval=0; for(ReceiverEntry entry: recv_table.values()) { if(entry.received_msgs != null) retval+=entry.received_msgs.size(); } return retval; } @ManagedAttribute(description="Total number of missing messages in all receive windows") public long getXmitTableMissingMessages() { long retval=0; for(ReceiverEntry entry: recv_table.values()) { if(entry.received_msgs != null) retval+=entry.received_msgs.getNumMissing(); } return retval; } @ManagedAttribute(description="Number of compactions in all (receive and send) windows") public int getXmitTableNumCompactions() { int retval=0; for(ReceiverEntry entry: recv_table.values()) { if(entry.received_msgs != null) retval+=entry.received_msgs.getNumCompactions(); } for(SenderEntry entry: send_table.values()) { if(entry.sent_msgs != null) retval+=entry.sent_msgs.getNumCompactions(); } return retval; } @ManagedAttribute(description="Number of moves in all (receive and send) windows") public int getXmitTableNumMoves() { int retval=0; for(ReceiverEntry entry: recv_table.values()) { if(entry.received_msgs != null) retval+=entry.received_msgs.getNumMoves(); } for(SenderEntry entry: send_table.values()) { if(entry.sent_msgs != null) retval+=entry.sent_msgs.getNumMoves(); } return retval; } @ManagedAttribute(description="Number of resizes in all (receive and send) windows") public int getXmitTableNumResizes() { int retval=0; for(ReceiverEntry entry: recv_table.values()) { if(entry.received_msgs != null) retval+=entry.received_msgs.getNumResizes(); } for(SenderEntry entry: send_table.values()) { if(entry.sent_msgs != null) retval+=entry.sent_msgs.getNumResizes(); } return retval; } @ManagedAttribute(description="Number of purges in all (receive and send) windows") public int getXmitTableNumPurges() { int retval=0; for(ReceiverEntry entry: recv_table.values()) { if(entry.received_msgs != null) retval+=entry.received_msgs.getNumPurges(); } for(SenderEntry entry: send_table.values()) { if(entry.sent_msgs != null) retval+=entry.sent_msgs.getNumPurges(); } return retval; } @ManagedOperation(description="Prints the contents of the receive windows for all members") public String printReceiveWindowMessages() { StringBuilder ret=new StringBuilder(local_addr + ":\n"); for(Map.Entry entry: recv_table.entrySet()) { Address addr=entry.getKey(); Table buf=entry.getValue().received_msgs; ret.append(addr).append(": ").append(buf.toString()).append('\n'); } return ret.toString(); } @ManagedOperation(description="Prints the contents of the send windows for all members") public String printSendWindowMessages() { StringBuilder ret=new StringBuilder(local_addr + ":\n"); for(Map.Entry entry: send_table.entrySet()) { Address addr=entry.getKey(); Table buf=entry.getValue().sent_msgs; ret.append(addr).append(": ").append(buf.toString()).append('\n'); } return ret.toString(); } @ManagedAttribute(description="Number of retransmit requests received") protected final AtomicLong xmit_reqs_received=new AtomicLong(0); @ManagedAttribute(description="Number of retransmit requests sent") protected final AtomicLong xmit_reqs_sent=new AtomicLong(0); @ManagedAttribute(description="Number of retransmit responses sent") protected final AtomicLong xmit_rsps_sent=new AtomicLong(0); @ManagedAttribute(description="Is the retransmit task running") public boolean isXmitTaskRunning() {return xmit_task != null && !xmit_task.isDone();} public long getMaxRetransmitTime() { return max_retransmit_time; } @Property(description="Max number of milliseconds we try to retransmit a message to any given member. After that, " + "the connection is removed. Any new connection to that member will start with seqno #1 again. 0 disables this") public void setMaxRetransmitTime(long max_retransmit_time) { this.max_retransmit_time=max_retransmit_time; if(cache != null && max_retransmit_time > 0) cache.setTimeout(max_retransmit_time); } @ManagedAttribute public int getAgeOutCacheSize() { return cache != null? cache.size() : 0; } @ManagedOperation public String printAgeOutCache() { return cache != null? cache.toString() : "n/a"; } public AgeOutCache
getAgeOutCache() { return cache; } /** Used for testing only */ public boolean hasSendConnectionTo(Address dest) { return send_table.containsKey(dest); } public void resetStats() { num_messages_sent=num_messages_received=0; xmit_reqs_received.set(0); xmit_reqs_sent.set(0); xmit_rsps_sent.set(0); } public TimeScheduler getTimer() { return timer; } /** * Only used for unit tests, don't use ! * @param timer */ public void setTimer(TimeScheduler timer) { this.timer=timer; } public void init() throws Exception { super.init(); if(max_stable_msgs < 1) throw new IllegalArgumentException("max_stable_msgs ( " + max_stable_msgs + ") must be > 0"); if(max_bytes <= 0) throw new IllegalArgumentException("max_bytes has to be > 0"); } public void start() throws Exception { timer=getTransport().getTimer(); if(timer == null) throw new Exception("timer is null"); if(max_retransmit_time > 0) cache=new AgeOutCache<>(timer, max_retransmit_time, this); running=true; if(stable_interval > 0) startStableTask(); if(conn_expiry_timeout > 0) startConnectionReaper(); startRetransmitTask(); } public void stop() { running=false; stopStableTask(); stopConnectionReaper(); stopRetransmitTask(); xmit_task_map.clear(); removeAllConnections(); } public Object up(Event evt) { switch(evt.getType()) { case Event.MSG: Message msg=(Message)evt.getArg(); if(msg.getDest() == null || msg.isFlagSet(Message.Flag.NO_RELIABILITY)) // only handle unicast messages break; // pass up Unicast2Header hdr=(Unicast2Header)msg.getHeader(this.id); if(hdr == null) break; Address sender=msg.getSrc(); switch(hdr.type) { case Unicast2Header.DATA: // received regular message if(handleDataReceived(sender, hdr.seqno, hdr.conn_id, hdr.first, msg, evt) && hdr.first) sendAck(sender, hdr.seqno, hdr.conn_id); return null; // we pass the deliverable message up in handleDataReceived() case Unicast2Header.XMIT_REQ: // received ACK for previously sent message handleXmitRequest(sender, (SeqnoList)msg.getObject()); break; case Unicast2Header.SEND_FIRST_SEQNO: handleResendingOfFirstMessage(sender, hdr.seqno); break; case Unicast2Header.ACK: if(log.isTraceEnabled()) log.trace(local_addr + " <-- ACK(" + sender + "," + hdr.seqno + " [conn_id=" + hdr.conn_id + "])"); SenderEntry entry=send_table.get(msg.getSrc()); if(entry != null) { if(entry.send_conn_id != hdr.conn_id) { if(log.isTraceEnabled()) log.trace(local_addr + ": ACK from " + sender + " is discarded as the connection IDs don't match: " + "my conn-id=" + entry.send_conn_id + ", hdr.conn_id=" + hdr.conn_id); } else entry.connEstablished(true); } break; case Unicast2Header.STABLE: stable(msg.getSrc(), hdr.conn_id, hdr.seqno, hdr.high_seqno); break; default: log.error(Util.getMessage("UnicastHeaderType"), hdr.type); break; } return null; } return up_prot.up(evt); // Pass up to the layer above us } public void up(MessageBatch batch) { if(batch.dest() == null) { // not a unicast batch up_prot.up(batch); return; } int size=batch.size(); Map> msgs=new TreeMap<>(); // map of messages, keyed by conn-id for(Message msg: batch) { if(msg == null || msg.isFlagSet(Message.Flag.NO_RELIABILITY)) continue; Unicast2Header hdr=(Unicast2Header)msg.getHeader(id); if(hdr == null) continue; batch.remove(msg); // remove the message from the batch, so it won't be passed up the stack if(hdr.type != Unicast2Header.DATA) { up(new Event(Event.MSG, msg)); continue; } List list=msgs.get(hdr.conn_id); if(list == null) msgs.put(hdr.conn_id, list=new ArrayList<>(size)); list.add(msg); } if(!msgs.isEmpty()) handleBatchReceived(batch.sender(), msgs); // process msgs: if(!batch.isEmpty()) up_prot.up(batch); } public Object down(Event evt) { switch (evt.getType()) { case Event.MSG: // Add UnicastHeader, add to AckSenderWindow and pass down Message msg=(Message)evt.getArg(); Address dst=msg.getDest(); /* only handle unicast messages */ if (dst == null || msg.isFlagSet(Message.Flag.NO_RELIABILITY)) break; if(!running) { if(log.isTraceEnabled()) log.trace(local_addr + ": discarded message as start() has not yet been called, message: " + msg); return null; } SenderEntry entry=send_table.get(dst); if(entry == null) { entry=new SenderEntry(getNewConnectionId()); SenderEntry existing=send_table.putIfAbsent(dst, entry); if(existing != null) entry=existing; else { if(log.isTraceEnabled()) log.trace(local_addr + ": created connection to " + dst + " (conn_id=" + entry.send_conn_id + ")"); if(cache != null && !members.contains(dst)) cache.add(dst); } } boolean dont_loopback_set=msg.isTransientFlagSet(Message.TransientFlag.DONT_LOOPBACK) && dst.equals(local_addr); short send_conn_id=entry.send_conn_id; long seqno=entry.sent_msgs_seqno.getAndIncrement(); long sleep=10; do { try { msg.putHeader(this.id, Unicast2Header.createDataHeader(seqno, send_conn_id, seqno == DEFAULT_FIRST_SEQNO)); entry.sent_msgs.add(seqno,msg, dont_loopback_set? dont_loopback_filter : null); // add *including* UnicastHeader, adds to retransmitter if(conn_expiry_timeout > 0) entry.update(); if(dont_loopback_set) entry.sent_msgs.purge(entry.sent_msgs.getHighestDeliverable()); break; } catch(Throwable t) { if(!running) break; Util.sleep(sleep); sleep=Math.min(5000, sleep*2); } } while(running); if(log.isTraceEnabled()) { StringBuilder sb=new StringBuilder(); sb.append(local_addr).append(" --> DATA(").append(dst).append(": #").append(seqno). append(", conn_id=").append(send_conn_id); if(seqno == DEFAULT_FIRST_SEQNO) sb.append(", first"); sb.append(')'); log.trace(sb); } num_messages_sent++; return down_prot.down(evt); case Event.VIEW_CHANGE: // remove connections to peers that are not members anymore ! View view=(View)evt.getArg(); List
new_members=view.getMembers(); Set
non_members=new HashSet<>(send_table.keySet()); non_members.addAll(recv_table.keySet()); members=new_members; non_members.removeAll(new_members); if(cache != null) cache.removeAll(new_members); if(!non_members.isEmpty()) { if(log.isTraceEnabled()) log.trace(local_addr + ": removing non members " + non_members); for(Address non_mbr: non_members) removeConnection(non_mbr); } xmit_task_map.keySet().retainAll(view.getMembers()); break; case Event.SET_LOCAL_ADDRESS: local_addr=(Address)evt.getArg(); break; } return down_prot.down(evt); // Pass on to the layer below us } /** * Purge all messages in window for local_addr, which are <= low. Check if the window's highest received message is * > high: if true, retransmit all messages from high - win.high to sender * @param sender * @param hd Highest delivered seqno * @param hr Highest received seqno */ protected void stable(Address sender, short conn_id, long hd, long hr) { SenderEntry entry=send_table.get(sender); Table win=entry != null? entry.sent_msgs : null; if(win == null) return; if(log.isTraceEnabled()) log.trace(new StringBuilder().append(local_addr).append(" <-- STABLE(").append(sender) .append(": ").append(hd).append("-") .append(hr).append(", conn_id=" + conn_id) +")"); if(entry.send_conn_id != conn_id) { log.trace(local_addr + ": my conn_id (" + entry.send_conn_id + ") != received conn_id (" + conn_id + "); discarding STABLE message !"); return; } win.purge(hd,true); long win_hr=win.getHighestReceived(); if(win_hr > hr) { for(long seqno=hr; seqno <= win_hr; seqno++) { Message msg=win.get(seqno); // destination is still the same (the member which sent the STABLE message) if(msg != null) down_prot.down(new Event(Event.MSG, msg)); } } } @ManagedOperation(description="Sends a STABLE message to all senders. This causes message purging and potential" + " retransmissions from senders") public void sendStableMessages() { for(Map.Entry entry: recv_table.entrySet()) { Address dest=entry.getKey(); ReceiverEntry val=entry.getValue(); Table win=val != null? val.received_msgs : null; if(win != null) { long[] tmp=win.getDigest(); long low=tmp[0], high=tmp[1]; if(val.last_highest == high) { if(val.num_stable_msgs >= max_stable_msgs) { continue; } else val.num_stable_msgs++; } else { val.last_highest=high; val.num_stable_msgs=1; } sendStableMessage(dest, val.recv_conn_id, low, high); } } } protected void sendStableMessage(Address dest, short conn_id, long hd, long hr) { Message stable_msg=new Message(dest).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL) .putHeader(this.id, Unicast2Header.createStableHeader(conn_id, hd, hr)); if(log.isTraceEnabled()) { StringBuilder sb=new StringBuilder(); sb.append(local_addr).append(" --> STABLE(").append(dest).append(": ") .append(hd).append("-").append(hr).append(", conn_id=").append(conn_id).append(")"); log.trace(sb.toString()); } down_prot.down(new Event(Event.MSG, stable_msg)); } protected void startStableTask() { if(stable_task_future == null || stable_task_future.isDone()) { final Runnable stable_task=new Runnable() { public void run() { try { sendStableMessages(); } catch(Throwable t) { log.error(Util.getMessage("SendingOfSTABLEMessagesFailed"), t); } } public String toString() { return UNICAST2.class.getSimpleName() + ": StableTask (interval=" + stable_interval + " ms)"; } }; stable_task_future=timer.scheduleWithFixedDelay(stable_task, stable_interval, stable_interval, TimeUnit.MILLISECONDS); if(log.isTraceEnabled()) log.trace(local_addr + ": stable task started (interval=" + stable_interval + ")"); } } protected void stopStableTask() { if(stable_task_future != null) { stable_task_future.cancel(false); stable_task_future=null; } } protected synchronized void startConnectionReaper() { if(connection_reaper == null || connection_reaper.isDone()) connection_reaper=timer.scheduleWithFixedDelay(new ConnectionReaper(), conn_expiry_timeout, conn_expiry_timeout, TimeUnit.MILLISECONDS); } protected synchronized void stopConnectionReaper() { if(connection_reaper != null) connection_reaper.cancel(false); } /** * Removes and resets from connection table (which is already locked). Returns true if member was found, otherwise * false. This method is public only so it can be invoked by unit testing, but should not otherwise be used ! */ public void removeConnection(Address mbr) { removeSendConnection(mbr); removeReceiveConnection(mbr); } public void removeSendConnection(Address mbr) { send_table.remove(mbr); } public void removeReceiveConnection(Address mbr) { ReceiverEntry entry2=recv_table.remove(mbr); if(entry2 != null) { Table win=entry2.received_msgs; if(win != null) { long[] digest=win.getDigest(); sendStableMessage(mbr, entry2.recv_conn_id, digest[0], digest[1]); } entry2.reset(); } } /** * This method is public only so it can be invoked by unit testing, but should not otherwise be used ! */ @ManagedOperation(description="Trashes all connections to other nodes. This is only used for testing") public void removeAllConnections() { send_table.clear(); sendStableMessages(); for(ReceiverEntry entry2: recv_table.values()) entry2.reset(); recv_table.clear(); } public void retransmit(SeqnoList missing, Address sender) { Unicast2Header hdr=Unicast2Header.createXmitReqHeader(); Message retransmit_msg=new Message(sender, missing).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL); if(log.isTraceEnabled()) log.trace(local_addr + ": sending XMIT_REQ (" + missing + ") to " + sender); retransmit_msg.putHeader(this.id, hdr); down_prot.down(new Event(Event.MSG,retransmit_msg)); xmit_reqs_sent.addAndGet(missing.size()); } /** * Called by AgeOutCache, to removed expired connections * @param key */ public void expired(Address key) { if(key != null) { if(log.isDebugEnabled()) log.debug(local_addr + ": removing connection to " + key + " because it expired"); removeConnection(key); } } /** * Check whether the hashmap contains an entry e for sender (create if not). If * e.received_msgs is null and first is true: create a new AckReceiverWindow(seqno) and * add message. Set e.received_msgs to the new window. Else just add the message. */ protected boolean handleDataReceived(Address sender, long seqno, short conn_id, boolean first, Message msg, Event evt) { if(log.isTraceEnabled()) { StringBuilder sb=new StringBuilder(); sb.append(local_addr).append(" <-- DATA(").append(sender).append(": #").append(seqno); if(conn_id != 0) sb.append(", conn_id=").append(conn_id); if(first) sb.append(", first"); sb.append(')'); log.trace(sb); } ReceiverEntry entry=getReceiverEntry(sender, seqno, first, conn_id); if(entry == null) return false; if(conn_expiry_timeout > 0) entry.update(); Table win=entry.received_msgs; boolean added=win.add(seqno, msg); // win is guaranteed to be non-null if we get here num_messages_received++; if(added) { int len=msg.getLength(); if(len > 0 && entry.incrementStable(len)) { long[] digest=win.getDigest(); sendStableMessage(sender, entry.recv_conn_id, digest[0], digest[1]); } } // An OOB message is passed up immediately. Later, when remove() is called, we discard it. This affects ordering ! // http://jira.jboss.com/jira/browse/JGRP-377 if(msg.isFlagSet(Message.Flag.OOB) && added) { try { up_prot.up(evt); } catch(Throwable t) { log.error("couldn't deliver OOB message %s: %s", msg, t); } } removeAndPassUp(win, sender); return true; } protected void handleBatchReceived(Address sender, Map> map) { for(Map.Entry> element: map.entrySet()) { final List msg_list=element.getValue(); if(log.isTraceEnabled()) { StringBuilder sb=new StringBuilder(); sb.append(local_addr).append(" <-- DATA(").append(sender).append(": " + printMessageList(msg_list)).append(')'); log.trace(sb); } short conn_id=element.getKey(); ReceiverEntry entry=null; Table win=null; boolean added=false; // set to true when at least 1 message was added int total_len=0; for(Message msg: msg_list) { Unicast2Header hdr=(Unicast2Header)msg.getHeader(id); entry=getReceiverEntry(sender, hdr.seqno, hdr.first, conn_id); if(entry == null) continue; win=entry.received_msgs; boolean msg_added=win.add(hdr.seqno, msg); // win is guaranteed to be non-null if we get here added|=msg_added; num_messages_received++; total_len+=msg.getLength(); if(hdr.first && msg_added) sendAck(sender, hdr.seqno, hdr.conn_id); // An OOB message is passed up immediately. Later, when remove() is called, we discard it. This affects ordering ! // http://jira.jboss.com/jira/browse/JGRP-377 if(msg.isFlagSet(Message.Flag.OOB) && msg_added) { try { up_prot.up(new Event(Event.MSG, msg)); } catch(Throwable t) { log.error("couldn't deliver OOB message %s: %s", msg, t); } } } if(entry != null && conn_expiry_timeout > 0) entry.update(); if(added && total_len > 0 && entry != null && entry.incrementStable(total_len) && win != null) { long[] digest=win.getDigest(); sendStableMessage(sender, entry.recv_conn_id, digest[0], digest[1]); } } ReceiverEntry tmp=recv_table.get(sender); Table win=tmp != null? tmp.received_msgs : null; if(win != null) removeAndPassUp(win, sender); } protected String printMessageList(List list) { StringBuilder sb=new StringBuilder(); int size=list.size(); Message first=size > 0? list.get(0) : null, second=size > 1? list.get(size-1) : null; Unicast2Header hdr; if(first != null) { hdr=(Unicast2Header)first.getHeader(id); if(hdr != null) sb.append("#" + hdr.seqno); } if(second != null) { hdr=(Unicast2Header)second.getHeader(id); if(hdr != null) sb.append(" - #" + hdr.seqno); } return sb.toString(); } /** * Try to remove as many messages as possible and pass them up. Prevents concurrent passing up of messages by * different threads (http://jira.jboss.com/jira/browse/JGRP-198); this is all the more important once we have a * concurrent stack (http://jira.jboss.com/jira/browse/JGRP-181), where lots of threads can come up to this point * concurrently, but only 1 is allowed to pass at a time.

* We *can* deliver messages from *different* senders concurrently, e.g. reception of P1, Q1, P2, Q2 can result in * delivery of P1, Q1, Q2, P2: FIFO (implemented by UNICAST) says messages need to be delivered only in the * order in which they were sent by their senders */ protected void removeAndPassUp(Table win, Address sender) { final AtomicBoolean processing=win.getProcessing(); if(!processing.compareAndSet(false, true)) return; boolean released_processing=false; try { while(true) { List msgs=win.removeMany(processing, true, max_msg_batch_size); // remove my own messages if(msgs == null || msgs.isEmpty()) { released_processing=true; return; } MessageBatch batch=new MessageBatch(local_addr, sender, null, false, msgs); for(Message msg_to_deliver: batch) { // discard OOB msg: it has already been delivered (http://jira.jboss.com/jira/browse/JGRP-377) if(msg_to_deliver.isFlagSet(Message.Flag.OOB)) batch.remove(msg_to_deliver); } try { if(log.isTraceEnabled()) { Message first=batch.first(), last=batch.last(); StringBuilder sb=new StringBuilder(local_addr + ": delivering"); if(first != null && last != null) { Unicast2Header hdr1=(Unicast2Header)first.getHeader(id), hdr2=(Unicast2Header)last.getHeader(id); sb.append(" #").append(hdr1.seqno).append(" - #").append(hdr2.seqno); } sb.append(" (" + batch.size()).append(" messages)"); log.trace(sb); } up_prot.up(batch); } catch(Throwable t) { log.error(Util.getMessage("FailedToDeliverBatch"), batch, t); } } } finally { // processing is always set in win.remove(processing) above and never here ! This code is just a // 2nd line of defense should there be an exception before win.remove(processing) sets processing if(!released_processing) processing.set(false); } } protected ReceiverEntry getReceiverEntry(Address sender, long seqno, boolean first, short conn_id) { ReceiverEntry entry=recv_table.get(sender); if(entry != null && entry.recv_conn_id == conn_id) return entry; recv_table_lock.lock(); try { entry=recv_table.get(sender); if(first) { if(entry == null) { entry=getOrCreateReceiverEntry(sender, seqno, conn_id); } else { // entry != null && win != null if(conn_id != entry.recv_conn_id) { if(log.isTraceEnabled()) log.trace(local_addr + ": conn_id=" + conn_id + " != " + entry.recv_conn_id + "; resetting receiver window"); recv_table.remove(sender); entry=getOrCreateReceiverEntry(sender, seqno, conn_id); } else { ; } } } else { // entry == null && win == null OR entry != null && win == null OR entry != null && win != null if(entry == null || entry.recv_conn_id != conn_id) { recv_table_lock.unlock(); sendRequestForFirstSeqno(sender, seqno); // drops the message and returns (see below) return null; } } return entry; } finally { if(recv_table_lock.isHeldByCurrentThread()) recv_table_lock.unlock(); } } protected ReceiverEntry getOrCreateReceiverEntry(Address sender, long seqno, short conn_id) { Table table=new Table<>(xmit_table_num_rows, xmit_table_msgs_per_row, seqno-1, xmit_table_resize_factor, xmit_table_max_compaction_time); ReceiverEntry entry=new ReceiverEntry(table, conn_id); ReceiverEntry entry2=recv_table.putIfAbsent(sender, entry); if(entry2 != null) return entry2; if(log.isTraceEnabled()) log.trace(local_addr + ": created receiver window for " + sender + " at seqno=#" + seqno + " for conn-id=" + conn_id); return entry; } protected void handleXmitRequest(Address sender, SeqnoList missing) { if(log.isTraceEnabled()) log.trace(new StringBuilder().append(local_addr).append(" <-- XMIT(").append(sender). append(": #").append(missing).append(')')); SenderEntry entry=send_table.get(sender); xmit_reqs_received.addAndGet(missing.size()); Table win=entry != null? entry.sent_msgs : null; if(win != null) { for(long seqno: missing) { Message msg=win.get(seqno); if(msg == null) { if(log.isWarnEnabled() && log_not_found_msgs && !local_addr.equals(sender) && seqno > win.getLow()) { StringBuilder sb=new StringBuilder(); sb.append(local_addr + ": (requester=").append(sender).append(") message ").append(sender) .append("::").append(seqno).append(" not found in retransmission table of ").append(sender) .append(":\n").append(win); log.warn(sb.toString()); } continue; } down_prot.down(new Event(Event.MSG, msg)); xmit_rsps_sent.incrementAndGet(); } } } /** * We need to resend our first message with our conn_id * @param sender * @param seqno Resend the non null messages in the range [lowest .. seqno] */ protected void handleResendingOfFirstMessage(Address sender, long seqno) { if(log.isTraceEnabled()) log.trace(local_addr + " <-- SEND_FIRST_SEQNO(" + sender + "," + seqno + ")"); SenderEntry entry=send_table.get(sender); Table win=entry != null? entry.sent_msgs : null; if(win == null) { if(log.isTraceEnabled()) log.trace(local_addr + ": sender window for " + sender + " not found"); return; } boolean first_sent=false; for(long i=win.getLow() +1; i <= seqno; i++) { Message rsp=win.get(i); if(rsp == null) continue; if(first_sent) { down_prot.down(new Event(Event.MSG, rsp)); } else { first_sent=true; // We need to copy the UnicastHeader and put it back into the message because Message.copy() doesn't copy // the headers and therefore we'd modify the original message in the sender retransmission window // (https://jira.jboss.org/jira/browse/JGRP-965) Message copy=rsp.copy(); Unicast2Header hdr=(Unicast2Header)copy.getHeader(this.id); Unicast2Header newhdr=hdr.copy(); newhdr.first=true; copy.putHeader(this.id, newhdr); down_prot.down(new Event(Event.MSG, copy)); } } } protected void startRetransmitTask() { if(xmit_task == null || xmit_task.isDone()) xmit_task=timer.scheduleWithFixedDelay(new RetransmitTask(), 0, xmit_interval, TimeUnit.MILLISECONDS); } protected void stopRetransmitTask() { if(xmit_task != null) { xmit_task.cancel(true); xmit_task=null; } } protected synchronized short getNewConnectionId() { short retval=last_conn_id; if(last_conn_id >= Short.MAX_VALUE || last_conn_id < 0) last_conn_id=0; else last_conn_id++; return retval; } protected void sendRequestForFirstSeqno(Address dest, long seqno_received) { Message msg=new Message(dest).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL); Unicast2Header hdr=Unicast2Header.createSendFirstSeqnoHeader(seqno_received); msg.putHeader(this.id, hdr); if(log.isTraceEnabled()) log.trace(local_addr + " --> SEND_FIRST_SEQNO(" + dest + "," + seqno_received + ")"); down_prot.down(new Event(Event.MSG, msg)); } protected void sendAck(Address dest, long seqno, short conn_id) { Message msg=new Message(dest).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL) .putHeader(this.id, Unicast2Header.createAckHeader(seqno, conn_id)); if(log.isTraceEnabled()) log.trace(local_addr + " --> ACK(" + dest + "," + seqno + " [conn_id=" + conn_id + "])"); down_prot.down(new Event(Event.MSG, msg)); } @ManagedOperation(description="Closes connections that have been idle for more than conn_expiry_timeout ms") public void reapIdleConnections() { if(conn_expiry_timeout <= 0) return; // remove expired connections from send_table for(Map.Entry entry: send_table.entrySet()) { SenderEntry val=entry.getValue(); long age=val.age(); if(age >= conn_expiry_timeout) { removeSendConnection(entry.getKey()); if(log.isDebugEnabled()) log.debug(local_addr + ": removed expired connection for " + entry.getKey() + " (" + age + " ms old) from send_table"); } } // remove expired connections from recv_table for(Map.Entry entry: recv_table.entrySet()) { ReceiverEntry val=entry.getValue(); long age=val.age(); if(age >= conn_expiry_timeout) { removeReceiveConnection(entry.getKey()); if(log.isDebugEnabled()) log.debug(local_addr + ": removed expired connection for " + entry.getKey() + " (" + age + " ms old) from recv_table"); } } } /** * The following types and fields are serialized: *

     * | DATA | seqno | conn_id | first |
     * | ACK  | seqno |
     * | SEND_FIRST_SEQNO | seqno |
     * 
*/ public static class Unicast2Header extends Header { public static final byte DATA = 0; public static final byte XMIT_REQ = 1; public static final byte SEND_FIRST_SEQNO = 2; public static final byte STABLE = 3; public static final byte ACK = 4; byte type; long seqno; // DATA, STABLE, SEND_FIRST_SEQNO and ACK long high_seqno; // STABLE short conn_id; // DATA, STABLE, ACK boolean first; // DATA public Unicast2Header() {} // used for externalization public static Unicast2Header createDataHeader(long seqno, short conn_id, boolean first) { return new Unicast2Header(DATA, seqno, 0L, conn_id, first); } public static Unicast2Header createXmitReqHeader() { return new Unicast2Header(XMIT_REQ); } public static Unicast2Header createStableHeader(short conn_id, long low, long high) { if(low > high) throw new IllegalArgumentException("low (" + low + ") needs to be <= high (" + high + ")"); Unicast2Header retval=new Unicast2Header(STABLE, low); retval.high_seqno=high; retval.conn_id=conn_id; return retval; } public static Unicast2Header createSendFirstSeqnoHeader(long seqno_received) { return new Unicast2Header(SEND_FIRST_SEQNO, seqno_received); } public static Unicast2Header createAckHeader(long acked_seqno, short conn_id) { Unicast2Header retval=new Unicast2Header(ACK,acked_seqno); retval.conn_id=conn_id; return retval; } protected Unicast2Header(byte type) { this.type=type; } protected Unicast2Header(byte type, long seqno) { this.type=type; this.seqno=seqno; } protected Unicast2Header(byte type, long seqno, long high, short conn_id, boolean first) { this.type=type; this.seqno=seqno; this.high_seqno=high; this.conn_id=conn_id; this.first=first; } public byte getType() { return type; } public long getSeqno() { return seqno; } public long getHighSeqno() { return high_seqno; } public short getConnId() { return conn_id; } public boolean isFirst() { return first; } public String toString() { StringBuilder sb=new StringBuilder(); sb.append(type2Str(type)).append(", seqno=").append(seqno); if(conn_id != 0) sb.append(", conn_id=").append(conn_id); if(first) sb.append(", first"); return sb.toString(); } public static String type2Str(byte t) { switch(t) { case DATA: return "DATA"; case XMIT_REQ: return "XMIT_REQ"; case SEND_FIRST_SEQNO: return "SEND_FIRST_SEQNO"; case STABLE: return "STABLE"; case ACK: return "ACK"; default: return ""; } } public final int size() { int retval=Global.BYTE_SIZE; // type switch(type) { case DATA: retval+=Bits.size(seqno) // seqno + Global.SHORT_SIZE // conn_id + Global.BYTE_SIZE; // first break; case XMIT_REQ: break; case STABLE: retval+=Bits.size(seqno, high_seqno) + Global.SHORT_SIZE; // conn_id break; case SEND_FIRST_SEQNO: retval+=Bits.size(seqno); break; case ACK: retval+=Bits.size(seqno) + Global.SHORT_SIZE; // conn_id break; } return retval; } public Unicast2Header copy() { return new Unicast2Header(type, seqno, high_seqno, conn_id, first); } public void writeTo(DataOutput out) throws Exception { out.writeByte(type); switch(type) { case DATA: Bits.writeLong(seqno, out); out.writeShort(conn_id); out.writeBoolean(first); break; case XMIT_REQ: break; case STABLE: Bits.writeLongSequence(seqno, high_seqno, out); out.writeShort(conn_id); break; case SEND_FIRST_SEQNO: Bits.writeLong(seqno, out); break; case ACK: Bits.writeLong(seqno, out); out.writeShort(conn_id); break; } } public void readFrom(DataInput in) throws Exception { type=in.readByte(); switch(type) { case DATA: seqno=Bits.readLong(in); conn_id=in.readShort(); first=in.readBoolean(); break; case XMIT_REQ: break; case STABLE: long[] seqnos=Bits.readLongSequence(in); seqno=seqnos[0]; high_seqno=seqnos[1]; conn_id=in.readShort(); break; case SEND_FIRST_SEQNO: seqno=Bits.readLong(in); break; case ACK: seqno=Bits.readLong(in); conn_id=in.readShort(); break; } } } protected final class SenderEntry { // stores (and retransmits) msgs sent by us to a given peer final Table sent_msgs; final AtomicLong sent_msgs_seqno=new AtomicLong(DEFAULT_FIRST_SEQNO); // seqno for msgs sent by us final short send_conn_id; protected final AtomicLong timestamp=new AtomicLong(0); protected volatile boolean ack_received; // true if ack for the first message was received public SenderEntry(short send_conn_id) { this.send_conn_id=send_conn_id; this.sent_msgs=new Table<>(xmit_table_num_rows, xmit_table_msgs_per_row, 0, xmit_table_resize_factor, xmit_table_max_compaction_time); update(); } protected void update() {timestamp.set(System.currentTimeMillis());} protected long age() {return System.currentTimeMillis() - timestamp.longValue();} protected boolean connEstablished() {return ack_received;} protected SenderEntry connEstablished(boolean flag) {ack_received=flag; return this;} protected Message getFirstMessage() {return sent_msgs.get(sent_msgs.getLow() +1);} public String toString() { StringBuilder sb=new StringBuilder(); if(sent_msgs != null) sb.append(sent_msgs).append(", "); sb.append("send_conn_id=" + send_conn_id).append(" (" + age() + " ms old) [") .append((ack_received? "acked" : "not acked") + "])"); return sb.toString(); } } protected final class ReceiverEntry { protected final Table received_msgs; // stores all msgs rcvd by a certain peer in seqno-order protected final short recv_conn_id; protected int received_bytes=0; protected final AtomicLong timestamp=new AtomicLong(0); protected final Lock lock=new ReentrantLock(); protected long last_highest=-1; protected int num_stable_msgs=0; public ReceiverEntry(Table received_msgs, short recv_conn_id) { this.received_msgs=received_msgs; this.recv_conn_id=recv_conn_id; update(); } /** Adds len bytes, if max_bytes is exceeded, the value is reset and true returned, else false */ boolean incrementStable(int len) { lock.lock(); try { if(received_bytes+len >= max_bytes) { received_bytes=0; return true; } received_bytes+=len; return false; } finally { lock.unlock(); } } void reset() { received_bytes=0; last_highest=-1; num_stable_msgs=0; } void update() {timestamp.set(System.currentTimeMillis());} long age() {return System.currentTimeMillis() - timestamp.longValue();} public String toString() { StringBuilder sb=new StringBuilder(); if(received_msgs != null) sb.append(received_msgs).append(", "); sb.append("recv_conn_id=" + recv_conn_id); sb.append(" (" + age() + " ms old)"); return sb.toString(); } } protected class ConnectionReaper implements Runnable { public void run() { reapIdleConnections(); } public String toString() { return UNICAST2.class.getSimpleName() + ": ConnectionReaper (interval=" + conn_expiry_timeout + " ms)"; } } /** * 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 UNICAST2.class.getSimpleName() + ": RetransmitTask (interval=" + xmit_interval + " ms)"; } } @ManagedOperation(description="Triggers the retransmission task, asking all senders for missing messages") public void triggerXmit() { SeqnoList missing; for(Map.Entry entry: recv_table.entrySet()) { Address target=entry.getKey(); // target to send retransmit requests to ReceiverEntry val=entry.getValue(); Table buf=val != null? val.received_msgs : null; if(buf != null && buf.getNumMissing() > 0 && (missing=buf.getMissing()) != 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()) retransmit(missing, target); } } else if(!xmit_task_map.isEmpty()) xmit_task_map.remove(target); // no current gaps for target } // Now check all SenderEntries for !ack_received and resend the first message if true // (https://issues.jboss.org/browse/JGRP-1563) for(SenderEntry entry: send_table.values()) { if(!entry.connEstablished()) { Message msg=entry.getFirstMessage(); if(msg == null) continue; Unicast2Header hdr=(Unicast2Header)msg.getHeader(id); if(hdr.first) { if(log.isTraceEnabled()) log.trace(local_addr + ": resending first message " + hdr.seqno + " to " + msg.getDest()); down_prot.down(new Event(Event.MSG,msg)); } else { Message copy=msg.copy(); hdr=(Unicast2Header)copy.getHeader(id); Unicast2Header newhdr=hdr.copy(); newhdr.first=true; copy.putHeader(id, newhdr); if(log.isTraceEnabled()) log.trace(local_addr + ": resending first message " + hdr.seqno + " to " + msg.getDest()); down_prot.down(new Event(Event.MSG, copy)); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy