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

bboss.org.jgroups.protocols.pbcast.NAKACK Maven / Gradle / Ivy

The newest version!
package bboss.org.jgroups.protocols.pbcast;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import bboss.org.jgroups.Address;
import bboss.org.jgroups.Event;
import bboss.org.jgroups.Global;
import bboss.org.jgroups.Message;
import bboss.org.jgroups.View;
import bboss.org.jgroups.annotations.DeprecatedProperty;
import bboss.org.jgroups.annotations.GuardedBy;
import bboss.org.jgroups.annotations.MBean;
import bboss.org.jgroups.annotations.ManagedAttribute;
import bboss.org.jgroups.annotations.ManagedOperation;
import bboss.org.jgroups.annotations.Property;
import bboss.org.jgroups.conf.PropertyConverters;
import bboss.org.jgroups.protocols.TP;
import bboss.org.jgroups.stack.ExponentialInterval;
import bboss.org.jgroups.stack.Interval;
import bboss.org.jgroups.stack.NakReceiverWindow;
import bboss.org.jgroups.stack.Protocol;
import bboss.org.jgroups.stack.Retransmitter;
import bboss.org.jgroups.stack.StaticInterval;
import bboss.org.jgroups.util.BoundedList;
import bboss.org.jgroups.util.Buffer;
import bboss.org.jgroups.util.Digest;
import bboss.org.jgroups.util.TimeScheduler;
import bboss.org.jgroups.util.Util;


/**
 * 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 * @version $Id: NAKACK.java,v 1.255 2010/06/14 08:11:19 belaban Exp $ */ @MBean(description="Reliable transmission multipoint FIFO protocol") @DeprecatedProperty(names={"max_xmit_size", "eager_lock_release", "stats_list_size"}) public class NAKACK extends Protocol implements Retransmitter.RetransmitCommand, NakReceiverWindow.Listener, TP.ProbeHandler { /** the weight with which we take the previous smoothed average into account, WEIGHT should be >0 and <= 1 */ private static final double WEIGHT=0.9; private static final double INITIAL_SMOOTHED_AVG=30.0; private static final int NUM_REBROADCAST_MSGS=3; /* ----------------------------------------------------- Properties --------------------- ------------------------------------ */ @Property(name="retransmit_timeout", converter=PropertyConverters.LongArray.class, description="Timeout before requesting retransmissions. Default is 600, 1200, 2400, 4800") private long[] retransmit_timeouts= { 600, 1200, 2400, 4800 }; // time(s) to wait before requesting retransmission @Property(description="If true, retransmissions stats will be captured. Default is false") boolean enable_xmit_time_stats=false; @Property(description="Garbage collection lag") private int gc_lag=20; // number of msgs garbage collection lags behind @Property(description="Max number of messages to be removed from a NakReceiverWindow. This property might " + "get removed anytime, so don't use it !") private int max_msg_batch_size=20000; /** * 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 messages using multicast rather than unicast") private 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. Default is false") private boolean use_mcast_xmit_req=false; /** * 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") private boolean xmit_from_random_member=false; /** * The first value (in milliseconds) to use in the exponential backoff * retransmission mechanism. Only enabled if the value is > 0 */ @Property(description="The first value (in milliseconds) to use in the exponential backoff. Enabled if greater than 0. Default is 0") private long exponential_backoff=0; /** * If enabled, we use statistics gathered from actual retransmission times * to compute the new retransmission times */ @Property(description="Use statistics gathered from actual retransmission times to compute new retransmission times. Default is false") private boolean use_stats_for_retransmission=false; @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") @Deprecated private boolean use_range_based_retransmitter=true; /** * Messages that have been received in order are sent up the stack (= * delivered to the application). Delivered messages are removed from * NakReceiverWindow.xmit_table and moved to * NakReceiverWindow.delivered_msgs, where they are later garbage collected * (by STABLE). Since we do retransmits only from sent messages, never * received or delivered messages, we can turn the moving to delivered_msgs * off, so we don't keep the message around, and don't need to wait for * garbage collection to remove them. */ @Property(description="Should messages delivered to application be discarded") private boolean discard_delivered_msgs=false; /** * If value is > 0, the retransmit buffer is bounded: only the * max_xmit_buf_size latest messages are kept, older ones are discarded when * the buffer size is exceeded. A value <= 0 means unbounded buffers */ @Property(description="If value is > 0, the retransmit buffer is bounded. If value <= 0 unbounded buffers are used. Default is 0") private int max_xmit_buf_size=0; @Property(description="Size of retransmission history. Default is 50 entries") private int xmit_history_max_size=50; @Property(description="Timeout to rebroadcast messages. Default is 2000 msec") private long max_rebroadcast_timeout=2000; /** * When not finding a message on an XMIT request, include the last N * stability messages in the error message */ @Property(description="Should stability history be printed if we fail in retransmission. Default is false") protected boolean print_stability_history_on_failed_xmit=false; /** If true, logs messages discarded because received from other members */ @Property(description="discards warnings about promiscuous traffic") private boolean log_discard_msgs=true; @Property(description="If true, trashes warnings about retransmission messages not found in the xmit_table (used for testing)") private boolean log_not_found_msgs=true; /* -------------------------------------------------- JMX ---------------------------------------------------------- */ @ManagedAttribute(description="Number of retransmit requests received") private long xmit_reqs_received; @ManagedAttribute(description="Number of retransmit requests sent") private long xmit_reqs_sent; @ManagedAttribute(description="Number of retransmit responses received") private long xmit_rsps_received; @ManagedAttribute(description="Number of retransmit responses sent") private long xmit_rsps_sent; @ManagedAttribute(description="Number of missing messages received") private long missing_msgs_received; /** * Maintains retransmission related data across a time. Only used if enable_xmit_time_stats is set to true. * At program termination, accumulated data is dumped to a file named by the address of the member. * Careful, don't enable this in production as the data in this hashmap are * never reaped ! Really only meant for diagnostics ! */ private ConcurrentMap xmit_time_stats=null; private long xmit_time_stats_start; /** Captures stats on XMIT_REQS, XMIT_RSPS per sender */ private ConcurrentMap sent=new ConcurrentHashMap(); /** Captures stats on XMIT_REQS, XMIT_RSPS per receiver */ private ConcurrentMap received=new ConcurrentHashMap(); /** * Per-sender map of seqnos and timestamps, to keep track of avg times for retransmission of messages */ private final ConcurrentMap> xmit_stats=new ConcurrentHashMap>(); /** * Maintains a list of the last N retransmission times (duration it took to retransmit a message) for all members */ private final ConcurrentMap> xmit_times_history=new ConcurrentHashMap>(); /** * Maintains a smoothed average of the retransmission times per sender, * these are the actual values that are used for new retransmission requests */ private final Map smoothed_avg_xmit_times=new HashMap(); /** Keeps the last 50 retransmit requests */ private final BoundedList xmit_history=new BoundedList(50); /* ------------------------------------------------- Fields ------------------------------------------------------------------------- */ private boolean is_server=false; private Address local_addr=null; private final List
members=new CopyOnWriteArrayList
(); private View view; @GuardedBy("seqno_lock") private long seqno=0; // current message sequence number (starts with 1) private final Lock seqno_lock=new ReentrantLock(); /** Map to store sent and received messages (keyed by sender) */ private final ConcurrentMap xmit_table=new ConcurrentHashMap(11); private volatile boolean leaving=false; private volatile boolean running=false; private TimeScheduler timer=null; private final Lock rebroadcast_lock=new ReentrantLock(); private final Condition rebroadcast_done=rebroadcast_lock.newCondition(); // set during processing of a rebroadcast event private volatile boolean rebroadcasting=false; private final Lock rebroadcast_digest_lock=new ReentrantLock(); @GuardedBy("rebroadcast_digest_lock") private Digest rebroadcast_digest=null; /** BoundedList, keeps the last 10 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); /** Regular messages which have been added, but not removed */ private final AtomicInteger undelivered_msgs=new AtomicInteger(0); public NAKACK() { } @ManagedAttribute public int getUndeliveredMessages() { return undelivered_msgs.get(); } public long getXmitRequestsReceived() {return xmit_reqs_received;} public long getXmitRequestsSent() {return xmit_reqs_sent;} public long getXmitResponsesReceived() {return xmit_rsps_received;} public long getXmitResponsesSent() {return xmit_rsps_sent;} public long getMissingMessagesReceived() {return missing_msgs_received;} @ManagedAttribute(description="Total number of missing messages") public int getPendingRetransmissionRequests() { int num=0; for(NakReceiverWindow win: xmit_table.values()) { num+=win.getPendingXmits(); } return num; } @ManagedAttribute public int getXmitTableSize() { int num=0; for(NakReceiverWindow win: xmit_table.values()) { num+=win.size(); } return num; } @ManagedOperation public String printRetransmitStats() { StringBuilder sb=new StringBuilder(); for(Map.Entry entry: xmit_table.entrySet()) sb.append(entry.getKey()).append(": ").append(entry.getValue().printRetransmitStats()).append("\n"); return sb.toString(); } public int getReceivedTableSize() { return getPendingRetransmissionRequests(); } /** * Please don't use this method; it is only provided for unit testing ! * @param mbr * @return */ public NakReceiverWindow getWindow(Address mbr) { return xmit_table.get(mbr); } public void resetStats() { xmit_reqs_received=xmit_reqs_sent=xmit_rsps_received=xmit_rsps_sent=missing_msgs_received=0; sent.clear(); received.clear(); stability_msgs.clear(); digest_history.clear(); xmit_history.clear(); } public void init() throws Exception { if(enable_xmit_time_stats) { if(log.isWarnEnabled()) log.warn("enable_xmit_time_stats is experimental, and may be removed in any release"); xmit_time_stats=new ConcurrentHashMap(); xmit_time_stats_start=System.currentTimeMillis(); } if(xmit_from_random_member) { if(discard_delivered_msgs) { discard_delivered_msgs=false; log.warn("xmit_from_random_member set to true: changed discard_delivered_msgs to false"); } } TP transport=getTransport(); if(transport != null) { transport.registerProbeHandler(this); if(!transport.supportsMulticasting()) { if(use_mcast_xmit) { log.warn("use_mcast_xmit should not be used because the transport (" + transport.getName() + ") does not support IP multicasting; setting use_mcast_xmit to false"); use_mcast_xmit=false; } if(use_mcast_xmit_req) { log.warn("use_mcast_xmit_req should not be used because the transport (" + transport.getName() + ") does not support IP multicasting; setting use_mcast_xmit_req to false"); use_mcast_xmit_req=false; } } } } public int getGcLag() { return gc_lag; } public void setGcLag(int gc_lag) { this.gc_lag=gc_lag; } public boolean isUseMcastXmit() { return use_mcast_xmit; } public void setUseMcastXmit(boolean use_mcast_xmit) { this.use_mcast_xmit=use_mcast_xmit; } public boolean isXmitFromRandomMember() { return xmit_from_random_member; } public void setXmitFromRandomMember(boolean xmit_from_random_member) { this.xmit_from_random_member=xmit_from_random_member; } public boolean isDiscardDeliveredMsgs() { return discard_delivered_msgs; } public void setDiscardDeliveredMsgs(boolean discard_delivered_msgs) { boolean old=this.discard_delivered_msgs; this.discard_delivered_msgs=discard_delivered_msgs; if(old != this.discard_delivered_msgs) { for(NakReceiverWindow win: xmit_table.values()) { win.setDiscardDeliveredMessages(this.discard_delivered_msgs); } } } public int getMaxXmitBufSize() { return max_xmit_buf_size; } public void setMaxXmitBufSize(int max_xmit_buf_size) { this.max_xmit_buf_size=max_xmit_buf_size; } /** * * @return * @deprecated removed in 2.6 */ public long getMaxXmitSize() { return -1; } /** * * @param max_xmit_size * @deprecated removed in 2.6 */ public void setMaxXmitSize(long max_xmit_size) { } public void setLogDiscardMessages(boolean flag) { log_discard_msgs=flag; } public void setLogDiscardMsgs(boolean flag) { setLogDiscardMessages(flag); } public boolean getLogDiscardMessages() { return log_discard_msgs; } public Map dumpStats() { Map retval=super.dumpStats(); retval.put("msgs", printMessages()); return retval; } public String printStats() { StringBuilder sb=new StringBuilder(); sb.append("sent:\n"); for(Iterator> it=sent.entrySet().iterator(); it.hasNext();) { Map.Entry entry=it.next(); Object key=entry.getKey(); if(key == null || key == Global.NULL) key=""; StatsEntry val=entry.getValue(); sb.append(key).append(": ").append(val).append("\n"); } sb.append("\nreceived:\n"); for(Iterator> it=received.entrySet().iterator(); it.hasNext();) { Map.Entry entry=it.next(); Object key=entry.getKey(); if(key == null || key == Global.NULL) key=""; StatsEntry val=entry.getValue(); sb.append(key).append(": ").append(val).append("\n"); } sb.append("\nStability messages received\n"); sb.append(printStabilityMessages()).append("\n"); return sb.toString(); } @ManagedOperation(description="TODO") public String printStabilityMessages() { StringBuilder sb=new StringBuilder(); sb.append(Util.printListWithDelimiter(stability_msgs, "\n")); return sb.toString(); } public String printStabilityHistory() { StringBuilder sb=new StringBuilder(); int i=1; for(Digest digest: stability_msgs) { sb.append(i++).append(": ").append(digest).append("\n"); } return sb.toString(); } @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="TODO") public String printLossRates() { StringBuilder sb=new StringBuilder(); NakReceiverWindow win; for(Map.Entry entry: xmit_table.entrySet()) { win=entry.getValue(); sb.append(entry.getKey()).append(": ").append(win.printLossRate()).append("\n"); } return sb.toString(); } @ManagedAttribute public double getAverageLossRate() { double retval=0.0; int count=0; if(xmit_table.isEmpty()) return 0.0; for(NakReceiverWindow win: xmit_table.values()) { retval+=win.getLossRate(); count++; } return retval / (double)count; } @ManagedAttribute public double getAverageSmoothedLossRate() { double retval=0.0; int count=0; if(xmit_table.isEmpty()) return 0.0; for(NakReceiverWindow win: xmit_table.values()) { retval+=win.getSmoothedLossRate(); count++; } return retval / (double)count; } public Vector providedUpServices() { Vector retval=new Vector(5); retval.addElement(new Integer(Event.GET_DIGEST)); retval.addElement(new Integer(Event.SET_DIGEST)); retval.addElement(new Integer(Event.OVERWRITE_DIGEST)); retval.addElement(new Integer(Event.MERGE_DIGEST)); return retval; } public void start() throws Exception { timer=getTransport().getTimer(); if(timer == null) throw new Exception("timer is null"); running=true; leaving=false; if(xmit_time_stats != null) { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { String filename="xmit-stats-" + local_addr + ".log"; try { dumpXmitStats(filename); } catch(IOException e) { e.printStackTrace(); } } }); } } public void stop() { running=false; reset(); // clears sent_msgs and destroys all NakReceiverWindows } /** * Callback. Called by superclass when event may be handled.

Do not use 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.MSG: Message msg=(Message)evt.getArg(); Address dest=msg.getDest(); if(dest != null && !dest.isMulticastAddress()) { break; // unicast address: not null and not mcast, pass down unchanged } send(evt, msg); return null; // don't pass down the stack case Event.STABLE: // generated by STABLE layer. Delete stable messages passed in arg stable((Digest)evt.getArg()); return null; // do not pass down further (Bela Aug 7 2001) case Event.GET_DIGEST: return getDigest(); case Event.SET_DIGEST: setDigest((Digest)evt.getArg()); return null; case Event.OVERWRITE_DIGEST: overwriteDigest((Digest)evt.getArg()); return null; case Event.MERGE_DIGEST: mergeDigest((Digest)evt.getArg()); return null; case Event.TMP_VIEW: View tmp_view=(View)evt.getArg(); Vector

mbrs=tmp_view.getMembers(); members.clear(); members.addAll(mbrs); // adjustReceivers(false); break; case Event.VIEW_CHANGE: tmp_view=(View)evt.getArg(); mbrs=tmp_view.getMembers(); members.clear(); members.addAll(mbrs); view=tmp_view; adjustReceivers(members); is_server=true; // check vids from now on Set
tmp=new LinkedHashSet
(members); tmp.add(null); // for null destination (= mcast) sent.keySet().retainAll(tmp); received.keySet().retainAll(tmp); xmit_stats.keySet().retainAll(tmp); // in_progress.keySet().retainAll(mbrs); // remove elements which are not in the membership break; case Event.BECOME_SERVER: is_server=true; break; case Event.SET_LOCAL_ADDRESS: local_addr=(Address)evt.getArg(); break; case Event.DISCONNECT: leaving=true; reset(); break; case Event.REBROADCAST: rebroadcasting=true; rebroadcast_digest=(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); } /** * Callback. Called by superclass when event may be handled.

Do not use 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.MSG: Message msg=(Message)evt.getArg(); NakAckHeader hdr=(NakAckHeader)msg.getHeader(this.id); if(hdr == null) break; // pass up (e.g. unicast msg) // discard messages while not yet server (i.e., until JOIN has returned) if(!is_server) { if(log.isTraceEnabled()) log.trace("message was discarded (not yet server)"); return null; } // Changed by bela Jan 29 2003: we must not remove the header, otherwise further xmit requests will fail ! //hdr=(NakAckHeader)msg.removeHeader(getName()); switch(hdr.type) { case NakAckHeader.MSG: handleMessage(msg, hdr); return null; // transmitter passes message up for us ! case NakAckHeader.XMIT_REQ: if(hdr.range == null) { if(log.isErrorEnabled()) { log.error("XMIT_REQ: range of xmit msg is null; discarding request from " + msg.getSrc()); } return null; } handleXmitReq(msg.getSrc(), hdr.range.low, hdr.range.high, hdr.sender); return null; case NakAckHeader.XMIT_RSP: handleXmitRsp(msg); return null; default: if(log.isErrorEnabled()) { log.error("NakAck header type " + hdr.type + " not known !"); } return null; } case Event.STABLE: // generated by STABLE layer. Delete stable messages passed in arg stable((Digest)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); } /* --------------------------------- Private Methods --------------------------------------- */ /** * 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 http://jira.jboss.com/jira/browse/JGRP-179). bela Jan 13 2006 */ private void send(Event evt, Message msg) { if(msg == null) throw new NullPointerException("msg is null; event is " + evt); if(!running) { if(log.isTraceEnabled()) log.trace("[" + local_addr + "] discarded message as we're not in the 'running' state, message: " + msg); return; } long msg_id; NakReceiverWindow win=xmit_table.get(local_addr); if(win == null) { // discard message if there is no entry for local_addr if(log.isWarnEnabled() && log_discard_msgs) log.warn(local_addr + ": discarded message from " + local_addr + " with no window, my view is " + view); return; } msg.setSrc(local_addr); // this needs to be done so we can check whether the message sender is the local_addr seqno_lock.lock(); try { try { // incrementing seqno and adding the msg to sent_msgs needs to be atomic msg_id=seqno +1; msg.putHeader(this.id, NakAckHeader.createMessageHeader(msg_id)); if(win.add(msg_id, msg) && !msg.isFlagSet(Message.OOB)) undelivered_msgs.incrementAndGet(); seqno=msg_id; } catch(Throwable t) { throw new RuntimeException("failure adding msg " + msg + " to the retransmit table for " + local_addr, t); } } finally { seqno_lock.unlock(); } try { // moved down_prot.down() out of synchronized clause (bela Sept 7 2006) http://jira.jboss.com/jira/browse/JGRP-300 if(log.isTraceEnabled()) log.trace("sending " + local_addr + "#" + msg_id); down_prot.down(evt); // if this fails, since msg is in sent_msgs, it can be retransmitted } catch(Throwable t) { // eat the exception, don't pass it up the stack if(log.isWarnEnabled()) { log.warn("failure passing message down", t); } } } /** * Finds the corresponding NakReceiverWindow and adds the message to it (according to seqno). Then removes as many * messages as possible from the NRW and passes them up the stack. Discards messages from non-members. */ private void handleMessage(Message msg, NakAckHeader hdr) { Address sender=msg.getSrc(); if(sender == null) { if(log.isErrorEnabled()) log.error("sender of message is null"); return; } if(log.isTraceEnabled()) log.trace(new StringBuilder().append(local_addr).append(": received ").append(sender).append('#').append(hdr.seqno)); NakReceiverWindow win=xmit_table.get(sender); if(win == null) { // discard message if there is no entry for sender if(leaving) return; if(log.isWarnEnabled() && log_discard_msgs) log.warn(local_addr + ": dropped message from " + sender + " (not in xmit_table), keys are " + xmit_table.keySet() +", view=" + view); return; } boolean loopback=local_addr.equals(sender); boolean added_to_window=false; boolean added=loopback || (added_to_window=win.add(hdr.seqno, msg)); if(added_to_window && !msg.isFlagSet(Message.OOB)) undelivered_msgs.incrementAndGet(); // message is passed up if OOB. Later, when remove() is called, we discard it. This affects ordering ! // http://jira.jboss.com/jira/browse/JGRP-379 if(msg.isFlagSet(Message.OOB)) { if(added) { msg=win.get(hdr.seqno); if(msg != null && msg.isFlagSet(Message.OOB)) { if(msg.setTransientFlagIfAbsent(Message.OOB_DELIVERED)) up_prot.up(new Event(Event.MSG, msg)); } } List msgs; while(!(msgs=win.removeOOBMessages()).isEmpty()) { for(Message tmp_msg: msgs) { if(tmp_msg.setTransientFlagIfAbsent(Message.OOB_DELIVERED)) { up_prot.up(new Event(Event.MSG, tmp_msg)); } } } if(!(win.hasMessagesToRemove() && undelivered_msgs.get() > 0)) return; } // 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://jira.jboss.org/jira/browse/JGRP-829). Benefit: fewer threads blocked on the same lock, these threads // can be returned to the thread pool final AtomicBoolean processing=win.getProcessing(); if(!processing.compareAndSet(false, true)) { return; } // 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 threadless 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 NAKACK) says messages need to be delivered in the // order in which they were sent by the sender int num_regular_msgs_removed=0; // 2nd line of defense: in case of an exception, remove() might not be called, therefore processing would never // be set back to false. If we get an exception and released_processing is not true, then we set // processing to false in the finally clause boolean released_processing=false; try { while(true) { // we're removing a msg and set processing to false (if null) *atomically* (wrt to add()) List msgs=win.removeMany(processing, max_msg_batch_size); if(msgs == null || msgs.isEmpty()) { released_processing=true; return; } for(final Message msg_to_deliver: msgs) { // discard OOB msg if it has already been delivered (http://jira.jboss.com/jira/browse/JGRP-379) if(msg_to_deliver.isFlagSet(Message.OOB)) { if(msg_to_deliver.setTransientFlagIfAbsent(Message.OOB_DELIVERED)) { timer.execute(new Runnable() { public void run() { up_prot.up(new Event(Event.MSG, msg_to_deliver)); } }); } continue; } num_regular_msgs_removed++; // Changed by bela Jan 29 2003: not needed (see above) //msg_to_deliver.removeHeader(getName()); try { up_prot.up(new Event(Event.MSG, msg_to_deliver)); } catch(Throwable t) { log.error("couldn't deliver message " + msg_to_deliver, t); } } } } finally { // We keep track of regular messages that we added, but couldn't remove (because of ordering). // When we have such messages pending, then even OOB threads will remove and process them // http://jira.jboss.com/jira/browse/JGRP-781 undelivered_msgs.addAndGet(-num_regular_msgs_removed); // 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); } } /** * 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 first_seqno The first sequence number to be retransmitted (<= last_seqno) * @param last_seqno The last sequence number to be retransmitted (>= first_seqno) * @param original_sender The member who originally sent the messsage. Guaranteed to be non-null */ private void handleXmitReq(Address xmit_requester, long first_seqno, long last_seqno, Address original_sender) { Message msg; if(log.isTraceEnabled()) { StringBuilder sb=new StringBuilder(); sb.append(local_addr).append(": received xmit request from ").append(xmit_requester).append(" for "); sb.append(original_sender).append(" [").append(first_seqno).append(" - ").append(last_seqno).append("]"); log.trace(sb.toString()); } if(first_seqno > last_seqno) return; if(stats) { xmit_reqs_received+=last_seqno - first_seqno +1; updateStats(received, xmit_requester, 1, 0, 0); } if(xmit_time_stats != null) { long key=(System.currentTimeMillis() - xmit_time_stats_start) / 1000; XmitTimeStat stat=xmit_time_stats.get(key); if(stat == null) { stat=new XmitTimeStat(); XmitTimeStat stat2=xmit_time_stats.putIfAbsent(key, stat); if(stat2 != null) stat=stat2; } stat.xmit_reqs_received.addAndGet((int)(last_seqno - first_seqno +1)); stat.xmit_rsps_sent.addAndGet((int)(last_seqno - first_seqno +1)); } NakReceiverWindow win=xmit_table.get(original_sender); if(win == null) { if(log.isErrorEnabled()) { StringBuilder sb=new StringBuilder(); sb.append("(requester=").append(xmit_requester).append(", local_addr=").append(this.local_addr); sb.append(") ").append(original_sender).append(" not found in retransmission table"); // don't print the table unless we are in trace mode because it can be LARGE if (log.isTraceEnabled()) { sb.append(":\n").append(printMessages()); } if(print_stability_history_on_failed_xmit) { sb.append(" (stability history:\n").append(printStabilityHistory()); } log.error(sb.toString()); } return; } for(long i=first_seqno; i <= last_seqno; i++) { msg=win.get(i); if(msg == null) { if(log.isWarnEnabled() && log_not_found_msgs && !local_addr.equals(xmit_requester)) { StringBuilder sb=new StringBuilder(); sb.append("(requester=").append(xmit_requester).append(", local_addr=").append(this.local_addr); sb.append(") message ").append(original_sender).append("::").append(i); sb.append(" not found in retransmission table of ").append(original_sender).append(":\n").append(win); if(print_stability_history_on_failed_xmit) { sb.append(" (stability history:\n").append(printStabilityHistory()); } log.warn(sb.toString()); } continue; } sendXmitRsp(xmit_requester, msg, i); } } private void cancelRebroadcasting() { rebroadcast_lock.lock(); try { rebroadcasting=false; rebroadcast_done.signalAll(); } finally { rebroadcast_lock.unlock(); } } private static void updateStats(ConcurrentMap map, Address key, int req, int rsp, int missing) { StatsEntry entry=map.get(key); if(entry == null) { entry=new StatsEntry(); StatsEntry tmp=map.putIfAbsent(key, entry); if(tmp != null) entry=tmp; } entry.xmit_reqs+=req; entry.xmit_rsps+=rsp; entry.missing_msgs_rcvd+=missing; } /** * 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 * @param seqno */ private void sendXmitRsp(Address dest, Message msg, long seqno) { Buffer buf; if(msg == null) { if(log.isErrorEnabled()) log.error("message is null, cannot send retransmission"); return; } if(stats) { xmit_rsps_sent++; updateStats(sent, dest, 0, 1, 0); } if(use_mcast_xmit) dest=null; if(msg.getSrc() == null) msg.setSrc(local_addr); try { buf=Util.messageToByteBuffer(msg); Message xmit_msg=new Message(dest, null, buf.getBuf(), buf.getOffset(), buf.getLength()); // changed Bela Jan 4 2007: we should not use OOB for retransmitted messages, otherwise we tax the // OOB thread pool too much // xmit_msg.setFlag(Message.OOB); if(msg.isFlagSet(Message.OOB)) // set OOB for the wrapping message if the wrapped message is OOB, too xmit_msg.setFlag(Message.OOB); xmit_msg.putHeader(this.id, NakAckHeader.createXmitResponseHeader()); down_prot.down(new Event(Event.MSG, xmit_msg)); } catch(IOException ex) { log.error("failed marshalling xmit list", ex); } } private void handleXmitRsp(Message msg) { if(msg == null) { if(log.isWarnEnabled()) log.warn("message is null"); return; } try { Message wrapped_msg=Util.byteBufferToMessage(msg.getRawBuffer(), msg.getOffset(), msg.getLength()); if(xmit_time_stats != null) { long key=(System.currentTimeMillis() - xmit_time_stats_start) / 1000; XmitTimeStat stat=xmit_time_stats.get(key); if(stat == null) { stat=new XmitTimeStat(); XmitTimeStat stat2=xmit_time_stats.putIfAbsent(key, stat); if(stat2 != null) stat=stat2; } stat.xmit_rsps_received.incrementAndGet(); } if(stats) { xmit_rsps_received++; updateStats(received, msg.getSrc(), 0, 1, 0); } up(new Event(Event.MSG, wrapped_msg)); if(rebroadcasting) { Digest tmp=getDigest(); boolean cancel_rebroadcasting; rebroadcast_digest_lock.lock(); try { cancel_rebroadcasting=tmp.isGreaterThanOrEqual(rebroadcast_digest); } finally { rebroadcast_digest_lock.unlock(); } if(cancel_rebroadcasting) { cancelRebroadcasting(); } } } catch(Exception ex) { if(log.isErrorEnabled()) { log.error("failed reading retransmitted message", ex); } } } /** * 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. */ private void rebroadcastMessages() { Digest my_digest; Map their_digest; Address sender; Digest.Entry their_entry, my_entry; long their_high, my_high; 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.getSenders(); } finally { rebroadcast_digest_lock.unlock(); } my_digest=getDigest(); boolean xmitted=false; for(Map.Entry entry: their_digest.entrySet()) { sender=entry.getKey(); their_entry=entry.getValue(); my_entry=my_digest.get(sender); if(my_entry == null) continue; their_high=their_entry.getHighest(); my_high=my_entry.getHighest(); if(their_high > my_high) { if(log.isTraceEnabled()) log.trace("sending XMIT request to " + sender + " for messages " + my_high + " - " + their_high); retransmit(my_high, their_high, sender, 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 || my_digest.isGreaterThanOrEqual(rebroadcast_digest)) return; } finally { rebroadcast_digest_lock.unlock(); } rebroadcast_done.await(sleep, TimeUnit.MILLISECONDS); wait_time-=(System.currentTimeMillis() - start); } catch(InterruptedException e) { } } finally { rebroadcast_lock.unlock(); } } } /** * Remove old members from NakReceiverWindows. Essentially removes all entries from xmit_table that are not * in members. This method is not called concurrently multiple times */ private void adjustReceivers(List

new_members) { for(Address member: xmit_table.keySet()) { if(!new_members.contains(member)) { if(local_addr != null && local_addr.equals(member)) continue; NakReceiverWindow win=xmit_table.remove(member); win.destroy(); if(log.isDebugEnabled()) log.debug("removed " + member + " from xmit_table (not member anymore)"); } } } /** * Returns a message digest: for each member P the lowest, highest delivered and highest 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) NakReceiverWindow win=entry.getValue(); // guaranteed to be non-null (CCHM) long low=win.getLowestSeen(), highest_delivered=win.getHighestDelivered(), highest_received=win.getHighestReceived(); map.put(sender, new Digest.Entry(low, highest_delivered, highest_received)); } return new Digest(map); } /** * Creates a NakReceiverWindow for each sender in the digest according to the sender's seqno. If NRW already exists, * reset it. */ private void setDigest(Digest digest) { setDigest(digest, false); } /** * For all members of the digest, adjust the NakReceiverWindows 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 (http://jira.jboss.com/jira/browse/JGRP-699) * if the digest's seqno is greater than the seqno in the window. */ private void mergeDigest(Digest digest) { setDigest(digest, true); } /** * Overwrites existing entries, but does NOT remove entries not found in the digest * @param digest */ private 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(Map.Entry entry: digest.getSenders().entrySet()) { Address sender=entry.getKey(); Digest.Entry val=entry.getValue(); if(sender == null || val == null) continue; long highest_delivered_seqno=val.getHighestDeliveredSeqno(); long low_seqno=val.getLow(); NakReceiverWindow win=xmit_table.get(sender); if(win != null) { win.destroy(); // stops retransmission xmit_table.remove(sender); } win=createNakReceiverWindow(sender, highest_delivered_seqno, low_seqno); xmit_table.put(sender, win); } sb.append("\n").append("resulting digest: " + getDigest()); digest_history.add(sb.toString()); if(log.isDebugEnabled()) log.debug(sb.toString()); } /** * Sets or merges the digest. If there is no entry for a given member in xmit_table, create a new NakReceiverWindow. * 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 */ private void setDigest(Digest digest, boolean merge) { if(digest == null) return; StringBuilder sb=new StringBuilder(merge? "\n[mergeDigest()]\n" : "\n[setDigest()]\n"); sb.append("existing digest: " + getDigest()).append("\nnew digest: " + digest); for(Map.Entry entry: digest.getSenders().entrySet()) { Address sender=entry.getKey(); Digest.Entry val=entry.getValue(); if(sender == null || val == null) continue; long highest_delivered_seqno=val.getHighestDeliveredSeqno(); long low_seqno=val.getLow(); NakReceiverWindow win=xmit_table.get(sender); if(win != 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://jira.jboss.org/jira/browse/JGRP-948, comment 20/Apr/09 03:39 AM) if(!merge || (local_addr != null && local_addr.equals(sender)) // never overwrite our own entry || win.getHighestDelivered() >= highest_delivered_seqno) // my seqno is >= digest's seqno for sender continue; win.destroy(); // stops retransmission xmit_table.remove(sender); } win=createNakReceiverWindow(sender, highest_delivered_seqno, low_seqno); xmit_table.put(sender, win); } sb.append("\n").append("resulting digest: " + getDigest()); digest_history.add(sb.toString()); if(log.isDebugEnabled()) log.debug(sb.toString()); } private NakReceiverWindow createNakReceiverWindow(Address sender, long initial_seqno, long lowest_seqno) { NakReceiverWindow win=new NakReceiverWindow(local_addr, sender, this, initial_seqno, lowest_seqno, timer, use_range_based_retransmitter); if(use_stats_for_retransmission) { win.setRetransmitTimeouts(new ActualInterval(sender)); } else if(exponential_backoff > 0) { win.setRetransmitTimeouts(new ExponentialInterval(exponential_backoff)); } else { win.setRetransmitTimeouts(new StaticInterval(retransmit_timeouts)); } win.setDiscardDeliveredMessages(discard_delivered_msgs); win.setMaxXmitBufSize(this.max_xmit_buf_size); if(stats) win.setListener(this); return win; } private void dumpXmitStats(String filename) throws IOException { Writer out=new FileWriter(filename); try { TreeMap map=new TreeMap(xmit_time_stats); StringBuilder sb; XmitTimeStat stat; out.write("time (secs) gaps-detected xmit-reqs-sent xmit-reqs-received xmit-rsps-sent xmit-rsps-received missing-msgs-received\n\n"); for(Map.Entry entry: map.entrySet()) { sb=new StringBuilder(); stat=entry.getValue(); sb.append(entry.getKey()).append(" "); sb.append(stat.gaps_detected).append(" "); sb.append(stat.xmit_reqs_sent).append(" "); sb.append(stat.xmit_reqs_received).append(" "); sb.append(stat.xmit_rsps_sent).append(" "); sb.append(stat.xmit_rsps_received).append(" "); sb.append(stat.missing_msgs_received).append("\n"); out.write(sb.toString()); } } finally { out.close(); } } /** * 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 * NakReceiverWindow corresponding to P which are <= seqno at digest[P]. */ private void stable(Digest digest) { NakReceiverWindow recv_win; long my_highest_rcvd; // highest seqno received in my digest for a sender P long stability_highest_rcvd; // highest seqno received in the stability vector for a sender P if(members == null || local_addr == null || digest == null) { if(log.isWarnEnabled()) log.warn("members, local_addr or digest are null !"); return; } if(log.isTraceEnabled()) { log.trace("received stable digest " + digest); } stability_msgs.add(digest); Address sender; Digest.Entry val; long high_seqno_delivered, high_seqno_received; for(Map.Entry entry: digest.getSenders().entrySet()) { sender=entry.getKey(); if(sender == null) continue; val=entry.getValue(); high_seqno_delivered=val.getHighestDeliveredSeqno(); high_seqno_received=val.getHighestReceivedSeqno(); // check whether the last seqno received for a sender P in the stability vector is > last seqno // received for P in my digest. if yes, request retransmission (see "Last Message Dropped" topic // in DESIGN) recv_win=xmit_table.get(sender); if(recv_win != null) { my_highest_rcvd=recv_win.getHighestReceived(); stability_highest_rcvd=high_seqno_received; if(stability_highest_rcvd >= 0 && stability_highest_rcvd > my_highest_rcvd) { if(log.isTraceEnabled()) { log.trace("my_highest_rcvd (" + my_highest_rcvd + ") < stability_highest_rcvd (" + stability_highest_rcvd + "): requesting retransmission of " + sender + '#' + stability_highest_rcvd); } retransmit(stability_highest_rcvd, stability_highest_rcvd, sender); } } high_seqno_delivered-=gc_lag; if(high_seqno_delivered < 0) { continue; } if(log.isTraceEnabled()) log.trace("deleting msgs <= " + high_seqno_delivered + " from " + sender); // delete *delivered* msgs that are stable if(recv_win != null) { recv_win.stable(high_seqno_delivered); // delete all messages with seqnos <= seqno } } } /* ---------------------- Interface Retransmitter.RetransmitCommand ---------------------- */ /** * Implementation of Retransmitter.RetransmitCommand. Called by retransmission thread when gap is detected. */ public void retransmit(long first_seqno, long last_seqno, Address sender) { retransmit(first_seqno, last_seqno, sender, false); } protected void retransmit(long first_seqno, long last_seqno, final Address sender, boolean multicast_xmit_request) { NakAckHeader hdr; Message retransmit_msg; Address dest=sender; // to whom do we send the XMIT request ? if(multicast_xmit_request || this.use_mcast_xmit_req) { dest=null; } else { if(xmit_from_random_member && !local_addr.equals(sender)) { Address random_member=(Address)Util.pickRandomElement(members); if(random_member != null && !local_addr.equals(random_member)) { dest=random_member; if(log.isTraceEnabled()) log.trace("picked random member " + dest + " to send XMIT request to"); } } } hdr=NakAckHeader.createXmitRequestHeader(first_seqno, last_seqno, sender); retransmit_msg=new Message(dest, null, null); retransmit_msg.setFlag(Message.OOB); if(log.isTraceEnabled()) log.trace(local_addr + ": sending XMIT_REQ ([" + first_seqno + ", " + last_seqno + "]) to " + dest); retransmit_msg.putHeader(this.id, hdr); ConcurrentMap tmp=xmit_stats.get(sender); if(tmp == null) { tmp=new ConcurrentHashMap(); ConcurrentMap tmp2=xmit_stats.putIfAbsent(sender, tmp); if(tmp2 != null) tmp=tmp2; } for(long seq=first_seqno; seq < last_seqno; seq++) { tmp.putIfAbsent(seq, System.currentTimeMillis()); } if(xmit_time_stats != null) { long key=(System.currentTimeMillis() - xmit_time_stats_start) / 1000; XmitTimeStat stat=xmit_time_stats.get(key); if(stat == null) { stat=new XmitTimeStat(); XmitTimeStat stat2=xmit_time_stats.putIfAbsent(key, stat); if(stat2 != null) stat=stat2; } stat.xmit_reqs_sent.addAndGet((int)(last_seqno - first_seqno +1)); } down_prot.down(new Event(Event.MSG, retransmit_msg)); if(stats) { xmit_reqs_sent+=last_seqno - first_seqno +1; updateStats(sent, sender, 1, 0, 0); } xmit_history.add(sender + ": " + first_seqno + "-" + last_seqno); } /* ------------------- End of Interface Retransmitter.RetransmitCommand -------------------- */ /* ----------------------- Interface NakReceiverWindow.Listener ---------------------- */ public void missingMessageReceived(long seqno, final Address original_sender) { ConcurrentMap tmp=xmit_stats.get(original_sender); if(tmp != null) { Long timestamp=tmp.remove(seqno); if(timestamp != null) { long diff=System.currentTimeMillis() - timestamp; BoundedList list=xmit_times_history.get(original_sender); if(list == null) { list=new BoundedList(xmit_history_max_size); BoundedList list2=xmit_times_history.putIfAbsent(original_sender, list); if(list2 != null) list=list2; } list.add(diff); // compute the smoothed average for retransmission times for original_sender // needs to be synchronized because we rely on the previous value for computation of the next value synchronized(smoothed_avg_xmit_times) { Double smoothed_avg=smoothed_avg_xmit_times.get(original_sender); if(smoothed_avg == null) smoothed_avg=INITIAL_SMOOTHED_AVG; // the smoothed avg takes 90% of the previous value, 100% of the new value and averages them // then, we add 10% to be on the safe side (an xmit value should rather err on the higher than lower side) smoothed_avg=((smoothed_avg * WEIGHT) + diff) / 2; smoothed_avg=smoothed_avg * (2 - WEIGHT); smoothed_avg_xmit_times.put(original_sender, smoothed_avg); } } } if(xmit_time_stats != null) { long key=(System.currentTimeMillis() - xmit_time_stats_start) / 1000; XmitTimeStat stat=xmit_time_stats.get(key); if(stat == null) { stat=new XmitTimeStat(); XmitTimeStat stat2=xmit_time_stats.putIfAbsent(key, stat); if(stat2 != null) stat=stat2; } stat.missing_msgs_received.incrementAndGet(); } if(stats) { missing_msgs_received++; updateStats(received, original_sender, 0, 0, 1); } } /** Called when a message gap is detected */ public void messageGapDetected(long from, long to, Address src) { if(xmit_time_stats != null) { long key=(System.currentTimeMillis() - xmit_time_stats_start) / 1000; XmitTimeStat stat=xmit_time_stats.get(key); if(stat == null) { stat=new XmitTimeStat(); XmitTimeStat stat2=xmit_time_stats.putIfAbsent(key, stat); if(stat2 != null) stat=stat2; } stat.gaps_detected.addAndGet((int)(to - from +1)); } } /* ------------------- End of Interface NakReceiverWindow.Listener ------------------- */ private void reset() { seqno_lock.lock(); try { seqno=0; } finally { seqno_lock.unlock(); } for(NakReceiverWindow win: xmit_table.values()) { win.destroy(); } xmit_table.clear(); undelivered_msgs.set(0); } @ManagedOperation(description="TODO") public String printMessages() { StringBuilder ret=new StringBuilder(local_addr + ":\n"); for(Map.Entry entry: xmit_table.entrySet()) { Address addr=entry.getKey(); NakReceiverWindow win=entry.getValue(); ret.append(addr).append(": ").append(win.toString()).append('\n'); } return ret.toString(); } @ManagedOperation(description="TODO") public String printRetransmissionAvgs() { StringBuilder sb=new StringBuilder(); for(Map.Entry> entry: xmit_times_history.entrySet()) { Address sender=entry.getKey(); BoundedList list=entry.getValue(); long tmp=0; int i=0; for(Long val: list) { tmp+=val; i++; } double avg=i > 0? tmp / i: -1; sb.append(sender).append(": ").append(avg).append("\n"); } return sb.toString(); } @ManagedOperation(description="TODO") public String printSmoothedRetransmissionAvgs() { StringBuilder sb=new StringBuilder(); for(Map.Entry entry: smoothed_avg_xmit_times.entrySet()) { sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } return sb.toString(); } @ManagedOperation(description="TODO") public String printRetransmissionTimes() { StringBuilder sb=new StringBuilder(); for(Map.Entry> entry: xmit_times_history.entrySet()) { Address sender=entry.getKey(); BoundedList list=entry.getValue(); sb.append(sender).append(": ").append(list).append("\n"); } return sb.toString(); } @ManagedOperation(description="Prints the last N retransmission requests") public String printXmitHistory() { StringBuilder sb=new StringBuilder(); for(String req: xmit_history) sb.append(req).append("\n"); return sb.toString(); } @ManagedAttribute public double getTotalAverageRetransmissionTime() { long total=0; int i=0; for(BoundedList list: xmit_times_history.values()) { for(Long val: list) { total+=val; i++; } } return i > 0? total / i: -1; } @ManagedAttribute public double getTotalAverageSmoothedRetransmissionTime() { double total=0.0; int cnt=0; synchronized(smoothed_avg_xmit_times) { for(Double val: smoothed_avg_xmit_times.values()) { if(val != null) { total+=val; cnt++; } } } return cnt > 0? total / cnt : -1; } /** Returns the smoothed average retransmission time for a given sender */ public double getSmoothedAverageRetransmissionTime(Address sender) { synchronized(smoothed_avg_xmit_times) { Double retval=smoothed_avg_xmit_times.get(sender); if(retval == null) { retval=INITIAL_SMOOTHED_AVG; smoothed_avg_xmit_times.put(sender, retval); } return retval; } } // ProbeHandler interface public Map handleProbe(String... keys) { Map retval=new HashMap(); for(String key: keys) { if(key.equals("digest-history")) retval.put(key, printDigestHistory()); if(key.equals("dump-digest")) retval.put(key, "\n" + printMessages()); } return retval; } // ProbeHandler interface public String[] supportedKeys() { return new String[]{"digest-history", "dump-digest"}; } // public static final class LossRate { // private final Set received=new HashSet(); // private final Set missing=new HashSet(); // private double smoothed_loss_rate=0.0; // // public synchronized void addReceived(long seqno) { // received.add(seqno); // missing.remove(seqno); // setSmoothedLossRate(); // } // // public synchronized void addReceived(Long ... seqnos) { // for(int i=0; i < seqnos.length; i++) { // Long seqno=seqnos[i]; // received.add(seqno); // missing.remove(seqno); // } // setSmoothedLossRate(); // } // // public synchronized void addMissing(long from, long to) { // for(long i=from; i <= to; i++) { // if(!received.contains(i)) // missing.add(i); // } // setSmoothedLossRate(); // } // // public synchronized double computeLossRate() { // int num_missing=missing.size(); // if(num_missing == 0) // return 0.0; // int num_received=received.size(); // int total=num_missing + num_received; // return num_missing / (double)total; // } // // public synchronized double getSmoothedLossRate() { // return smoothed_loss_rate; // } // // public synchronized String toString() { // StringBuilder sb=new StringBuilder(); // int num_missing=missing.size(); // int num_received=received.size(); // int total=num_missing + num_received; // sb.append("total=").append(total).append(" (received=").append(received.size()).append(", missing=") // .append(missing.size()).append(", loss rate=").append(computeLossRate()) // .append(", smoothed loss rate=").append(smoothed_loss_rate).append(")"); // return sb.toString(); // } // // /** Set the new smoothed_loss_rate value to 70% of the new value and 30% of the old value */ // private void setSmoothedLossRate() { // double new_loss_rate=computeLossRate(); // if(smoothed_loss_rate == 0) { // smoothed_loss_rate=new_loss_rate; // } // else { // smoothed_loss_rate=smoothed_loss_rate * .3 + new_loss_rate * .7; // } // } // } private static class XmitTimeStat { final AtomicInteger gaps_detected=new AtomicInteger(0); final AtomicInteger xmit_reqs_sent=new AtomicInteger(0); final AtomicInteger xmit_reqs_received=new AtomicInteger(0); final AtomicInteger xmit_rsps_sent=new AtomicInteger(0); final AtomicInteger xmit_rsps_received=new AtomicInteger(0); final AtomicInteger missing_msgs_received=new AtomicInteger(0); } private class ActualInterval implements Interval { private final Address sender; public ActualInterval(Address sender) { this.sender=sender; } public long next() { return (long)getSmoothedAverageRetransmissionTime(sender); } public Interval copy() { return this; } } static class StatsEntry { long xmit_reqs, xmit_rsps, missing_msgs_rcvd; public String toString() { StringBuilder sb=new StringBuilder(); sb.append(xmit_reqs).append(" xmit_reqs").append(", ").append(xmit_rsps).append(" xmit_rsps"); sb.append(", ").append(missing_msgs_rcvd).append(" missing msgs"); return sb.toString(); } } /* ----------------------------- End of Private Methods ------------------------------------ */ }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy