package org.jgroups.protocols;
import org.jgroups.Address;
import org.jgroups.Event;
import org.jgroups.Message;
import org.jgroups.annotations.MBean;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.util.CreditMap;
import org.jgroups.util.Tuple;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Simple flow control protocol based on a credit system. Each sender has a number of credits (bytes
* to send). When the credits have been exhausted, the sender blocks. Each receiver also keeps track of
* how many credits it has received from a sender. When credits for a sender fall below a threshold,
* the receiver sends more credits to the sender. Works for both unicast and multicast messages.
* <p/>
* Note that this protocol must be located towards the top of the stack, or all down_threads from JChannel to this
* protocol must be set to false ! This is in order to block JChannel.send()/JChannel.down().
* <br/>This is the second simplified implementation of the same model. The algorithm is sketched out in
* doc/FlowControl.txt
* <br/>
* Changes (Brian) April 2006:
* <ol>
* <li>Receivers now send credits to a sender when more than min_credits have been received (rather than when min_credits
* are left)
* <li>Receivers don't send the full credits (max_credits), but rather the actual number of bytes received
* <ol/>
* @author Bela Ban
*/
@MBean(description="Simple flow control protocol based on a credit system")
public class MFC extends FlowControl {
/* --------------------------------------------- Fields ------------------------------------------------------ */
/** Maintains credits per member */
protected CreditMap credits;
/** Last time a credit request was sent. Used to prevent credit request storms */
protected long last_credit_request=0;
/** Allows to unblock a blocked sender from an external program, e.g. JMX */
@ManagedOperation(description="Unblock a sender")
public void unblock() {
if(log.isTraceEnabled())
log.trace("unblocking the sender and replenishing all members");
credits.replenishAll();
}
@ManagedOperation(description="Print credits")
public String printCredits() {
return super.printCredits() + "\nsenders min credits: " + credits.computeLowestCreditWithAccumulated();
}
@ManagedOperation(description="Print sender credits")
public String printSenderCredits() {
return credits.toString();
}
@ManagedAttribute(description="Number of times flow control blocks sender")
public int getNumberOfBlockings() {
return credits.getNumBlockings();
}
@ManagedAttribute(description="Total time (ms) spent in flow control block")
public long getTotalTimeBlocked() {
return credits.getTotalBlockTime();
}
protected boolean handleMulticastMessage() {
return true;
}
public void init() throws Exception {
super.init();
credits=new CreditMap(max_credits);
}
public void stop() {
super.stop();
credits.clear();
}
protected Object handleDownMessage(final Event evt, final Message msg, Address dest, int length) {
if(dest != null) { // 2nd line of defense, not really needed
log.error(getClass().getSimpleName() + " doesn't handle unicast messages; passing message down");
return down_prot.down(evt);
}
long block_time=max_block_times != null? getMaxBlockTime(length) : max_block_time;
while(running) {
boolean rc=credits.decrement(length, block_time);
if(rc || max_block_times != null || !running)
break;
if(needToSendCreditRequest()) {
List<Tuple<Address,Long>> targets=credits.getMembersWithCreditsLessThan(min_credits);
for(Tuple<Address,Long> tuple: targets)
sendCreditRequest(tuple.getVal1(), Math.min(max_credits, max_credits - tuple.getVal2()));
}
}
// send message - either after regular processing, or after blocking (when enough credits are available again)
return down_prot.down(evt);
}
protected synchronized boolean needToSendCreditRequest() {
long curr_time=System.currentTimeMillis();
long wait_time=curr_time - last_credit_request;
if(wait_time >= max_block_time) {
last_credit_request=curr_time;
return true;
}
return false;
}
protected void handleCredit(Address sender, long increase) {
credits.replenish(sender, increase);
if(log.isTraceEnabled()) {
StringBuilder sb=new StringBuilder();
sb.append("received " + increase + " credits from ").append(sender).append(", new credits for " + sender + " : ")
.append(credits.get(sender) + ", min_credits=" + credits.getMinCredits());
log.trace(sb);
}
}
protected void handleViewChange(List<Address> mbrs) {
super.handleViewChange(mbrs);
Set<Address> keys=new HashSet<Address>(credits.keys());
for(Address key: keys) {
if(!mbrs.contains(key))
credits.remove(key);
}
for(Address key: mbrs)
credits.putIfAbsent(key);
}
}