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

com.gemstone.org.jgroups.protocols.obsolete.FC.txt Maven / Gradle / Ivy

There is a newer version: 2.0-BETA
Show newest version
// $Id: FC.java.txt,v 1.1 2005/08/16 12:58:58 belaban Exp $

package org.jgroups.protocols;

import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
import org.jgroups.*;
import org.jgroups.stack.Protocol;
import org.jgroups.util.BoundedList;
import org.jgroups.util.CondVar;
import org.jgroups.util.Streamable;

import java.io.*;
import java.util.*;

/**
 * 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.
 * 

* 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(). * @author Bela Ban * @version $Revision: 1.1 $ */ public class FC extends Protocol { /** My own address */ Address local_addr=null; /** HashMap: keys are members, values are credits left. For each send, the * number of credits is decremented by the message size */ final Map sent=new HashMap(11); // final Map sent=new ConcurrentHashMap(11); /** HashMap: keys are members, values are credits left (in bytes). * For each receive, the credits for the sender are decremented by the size of the received message. * When the credits are 0, we refill and send a CREDIT message to the sender. Sender blocks until CREDIT * is received after reaching min_credits credits. */ final Map received=new ConcurrentReaderHashMap(11); // final Map received=new ConcurrentHashMap(11); /** We cache the membership */ final Vector members=new Vector(11); /** List of members from whom we expect credits */ final Vector creditors=new Vector(11); /** Max number of bytes to send per receiver until an ack must * be received before continuing sending */ private long max_credits=50000; /** Max time (in milliseconds) to block. If credit hasn't been received after max_block_time, we send * a REPLENISHMENT request to the members from which we expect credits. A value <= 0 means to * wait forever. */ private long max_block_time=5000; /** If credits fall below this limit, we send more credits to the sender. (We also send when * credits are exhausted (0 credits left)) */ double min_threshold=0.25; /** Computed as max_credits times min_theshold. If explicitly set, this will * override the above computation */ private long min_credits=0; /** Current blocking. True if blocking, else false */ private final CondVar blocking=new CondVar("blocking", Boolean.FALSE); static final String name="FC"; private long start_blocking=0, stop_blocking=0; private int num_blockings=0, num_replenishments=0, num_credit_requests=0; private long total_time_blocking=0; final BoundedList last_blockings=new BoundedList(50); final static FcHeader REPLENISH_HDR=new FcHeader(FcHeader.REPLENISH); final static FcHeader CREDIT_REQUEST_HDR=new FcHeader(FcHeader.CREDIT_REQUEST); public String getName() { return name; } public void resetStats() { super.resetStats(); num_blockings=num_replenishments=num_credit_requests=0; total_time_blocking=0; last_blockings.removeAll(); } public long getMaxCredits() { return max_credits; } public void setMaxCredits(long max_credits) { this.max_credits=max_credits; } public double getMinThreshold() { return min_threshold; } public void setMinThreshold(double min_threshold) { this.min_threshold=min_threshold; } public long getMinCredits() { return min_credits; } public void setMinCredits(long min_credits) { this.min_credits=min_credits; } public boolean isBlocked() { Object obj=blocking.get(); return obj != null && obj instanceof Boolean && ((Boolean)obj).booleanValue(); } public int getNumberOfBlockings() { return num_blockings; } public long getTotalTimeBlocked() { return total_time_blocking; } public double getAverageTimeBlocked() { return num_blockings == 0? num_blockings : total_time_blocking / num_blockings; } public int getNumberOfReplenishmentsReceived() { return num_replenishments; } public int getNumberOfCreditRequests() { return num_credit_requests; } public String printSenderCredits() { return printMap(sent); } public String printReceiverCredits() { return printMap(received); } public String printCredits() { StringBuffer sb=new StringBuffer(); sb.append("senders:\n").append(printMap(sent)).append("\n\nreceivers:\n").append(printMap(received)); return sb.toString(); } public Map dumpStats() { Map retval=super.dumpStats(); if(retval == null) retval=new HashMap(); retval.put("senders", printMap(sent)); retval.put("receivers", printMap(received)); retval.put("num_blockings", new Integer(this.num_blockings)); retval.put("avg_time_blocked", new Double(getAverageTimeBlocked())); retval.put("num_replenishments", new Integer(this.num_replenishments)); return retval; } public String showLastBlockingTimes() { return last_blockings.toString(); } public void unblock() { unblockSender(); } public boolean setProperties(Properties props) { String str; boolean min_credits_set=false; super.setProperties(props); str=props.getProperty("max_credits"); if(str != null) { max_credits=Long.parseLong(str); props.remove("max_credits"); } str=props.getProperty("min_threshold"); if(str != null) { min_threshold=Double.parseDouble(str); props.remove("min_threshold"); } str=props.getProperty("min_credits"); if(str != null) { min_credits=Long.parseLong(str); props.remove("min_credits"); min_credits_set=true; } if(!min_credits_set) min_credits=(long)((double)max_credits * min_threshold); str=props.getProperty("max_block_time"); if(str != null) { max_block_time=Long.parseLong(str); props.remove("max_block_time"); } if(props.size() > 0) { log.error("FC.setProperties(): the following properties are not recognized: " + props); return false; } return true; } public void stop() { super.stop(); unblock(); } /** * We need to receive view changes concurrent to messages on the down events: a message might blocks, e.g. * because we don't have enough credits to send to member P. However, if member P crashed, we need to unblock ! * @param evt */ protected void receiveDownEvent(Event evt) { if(evt.getType() == Event.VIEW_CHANGE) { View v=(View)evt.getArg(); Vector mbrs=v.getMembers(); handleViewChange(mbrs); } super.receiveDownEvent(evt); } public void down(Event evt) { switch(evt.getType()) { case Event.MSG: handleDownMessage(evt); return; } passDown(evt); // this could potentially use the lower protocol's thread which may block } private synchronized void handleDownMessage(Event evt) { if(Boolean.TRUE.equals(blocking.get())) { // blocked waitUntilEnoughCreditsAvailable(); } else { // not blocked boolean rc; synchronized(sent) { // 'sent' is the same lock as blocking.getLock()... rc=decrMessage((Message)evt.getArg()); if(rc == false) { if(trace) log.trace("blocking due to insufficient credits"); blocking.set(Boolean.TRUE); start_blocking=System.currentTimeMillis(); num_blockings++; } } if(rc == false) { waitUntilEnoughCreditsAvailable(); } } passDown(evt); } public void up(Event evt) { switch(evt.getType()) { case Event.SET_LOCAL_ADDRESS: local_addr=(Address)evt.getArg(); break; case Event.VIEW_CHANGE: handleViewChange(((View)evt.getArg()).getMembers()); break; case Event.MSG: Message msg=(Message)evt.getArg(); FcHeader hdr=(FcHeader)msg.removeHeader(name); if(hdr != null) { switch(hdr.type) { case FcHeader.REPLENISH: num_replenishments++; handleCredit(msg.getSrc()); break; case FcHeader.CREDIT_REQUEST: num_credit_requests++; Address sender=msg.getSrc(); if(trace) log.trace("received credit request from " + sender + ": sending credits"); received.put(sender, new Long(max_credits)); sendCredit(sender); break; default: log.error("header type " + hdr.type + " not known"); break; } return; // don't pass message up } else { adjustCredit(msg); } break; } passUp(evt); } private void handleCredit(Address sender) { if(sender == null) return; StringBuffer sb=null; boolean unblock=false; if(trace) { Long old_credit=(Long)sent.get(sender); sb=new StringBuffer(); sb.append("received credit from ").append(sender).append(", old credit was "). append(old_credit).append(", new credits are ").append(max_credits). append(".\nCreditors before are: ").append(creditors); } synchronized(sent) { sent.put(sender, new Long(max_credits)); if(creditors.size() > 0) { // we are blocked because we expect credit from one or more members removeCreditor(sender); if(trace) { sb.append("\nCreditors after removal of ").append(sender).append(" are: ").append(creditors); log.trace(sb.toString()); } if(creditors.size() == 0) { unblock=true; } } else { // no creditors, but still blocking: we need to unblock if(Boolean.TRUE.equals(blocking.get())) unblock=true; } } if(unblock) // moved this outside of the 'sent' synchronized block unblockSender(); } /** * Check whether sender has enough credits left. If not, send him some more * @param msg */ private void adjustCredit(Message msg) { Address src=msg.getSrc(); long size=Math.max(24, msg.getLength()); if(src == null) { if(log.isErrorEnabled()) log.error("src is null"); return; } if(decrementCredit(received, src, size, min_credits) == false) { received.put(src, new Long(max_credits)); if(trace) log.trace("sending replenishment message to " + src); sendCredit(src); } } private void sendCredit(Address dest) { Message msg=new Message(dest, null, null); msg.putHeader(name, REPLENISH_HDR); passDown(new Event(Event.MSG, msg)); } private void sendCreditRequest(final Address dest) { Message msg=new Message(dest, null, null); msg.putHeader(name, CREDIT_REQUEST_HDR); passDown(new Event(Event.MSG, msg)); } /** * Checks whether enough credits are available to send message. If not, blocks until enough credits * are available * @param evt Guaranteed to be a Message * @return */ private void waitUntilEnoughCreditsAvailable() { while(true) { try { blocking.waitUntilWithTimeout(Boolean.FALSE, max_block_time); // waits on 'sent' break; } catch(TimeoutException e) { List tmp=new ArrayList(creditors); if(trace) log.trace("timeout occurred waiting for credits; sending credit request to " + tmp + ", creditors are " + creditors); Address mbr; for(Iterator it=tmp.iterator(); it.hasNext();) { mbr=(Address)it.next(); sendCreditRequest(mbr); } } } } /** * Try to decrement the credits needed for this message and return true if successful, or false otherwise. * For unicast destinations, the credits required are subtracted from the unicast destination member, for * multicast messages the credits are subtracted from all current members in the group. * @param msg * @return false: will block, true: will not block */ private boolean decrMessage(Message msg) { Address dest; long size; boolean success=true; // ****************************************************************************************************** // this method is called by waitUntilEnoughCredits() which syncs on 'sent', so we don't need to sync here // ****************************************************************************************************** if(msg == null) { if(log.isErrorEnabled()) log.error("msg is null"); return true; // don't block ! } dest=msg.getDest(); size=Math.max(24, msg.getLength()); if(dest != null && !dest.isMulticastAddress()) { // unicast destination if(decrementCredit(sent, dest, size, 0)) { return true; } else { addCreditor(dest); return false; } } else { // multicast destination for(Iterator it=members.iterator(); it.hasNext();) { dest=(Address)it.next(); if(decrementCredit(sent, dest, size, 0) == false) { addCreditor(dest); success=false; } } } return success; } /** If message queueing is enabled, sends queued messages and unlocks sender (if successful) */ private void unblockSender() { if(start_blocking > 0) { stop_blocking=System.currentTimeMillis(); long diff=stop_blocking - start_blocking; total_time_blocking+=diff; last_blockings.add(new Long(diff)); stop_blocking=start_blocking=0; if(trace) log.trace("setting blocking=false, blocking time was " + diff + "ms"); } if(trace) log.trace("setting blocking=false"); blocking.set(Boolean.FALSE); } private void addCreditor(Address mbr) { if(mbr != null && !creditors.contains(mbr)) creditors.add(mbr); } private void removeCreditor(Address mbr) { creditors.remove(mbr); } /** * Find the credits associated with dest and decrement its credits by credits_required. If the remaining * value is less than or equal to 0, return false, else return true. Note that we will always subtract the credits. * @param map * @param dest * @param credits_required Number of bytes required * @param minimal_credits For the receiver: add minimal credits to check whether credits need to be sent * @return Whether the required credits could successfully be subtracted from the credits left */ private boolean decrementCredit(Map map, Address dest, long credits_required, long minimal_credits) { long credits_left, new_credits_left; Long tmp=(Long)map.get(dest); boolean success; if(tmp == null) return true; credits_left=tmp.longValue(); success=credits_left > (credits_required + minimal_credits); new_credits_left=Math.max(0, credits_left - credits_required); map.put(dest, new Long(new_credits_left)); if(success) { return true; } else { if(trace) { StringBuffer sb=new StringBuffer(); sb.append("not enough credits left for ").append(dest).append(": left=").append(new_credits_left); sb.append(", required+min_credits=").append((credits_required +min_credits)).append(", required="); sb.append(credits_required).append(", min_credits=").append(min_credits); log.trace(sb.toString()); } return false; } } void handleViewChange(Vector mbrs) { Address addr; if(mbrs == null) return; if(trace) log.trace("new membership: " + mbrs); members.clear(); members.addAll(mbrs); synchronized(received) { // add members not in membership to received hashmap (with full credits) for(int i=0; i < mbrs.size(); i++) { addr=(Address) mbrs.elementAt(i); if(!received.containsKey(addr)) received.put(addr, new Long(max_credits)); } // remove members that left for(Iterator it=received.keySet().iterator(); it.hasNext();) { addr=(Address) it.next(); if(!mbrs.contains(addr)) it.remove(); } } boolean unblock=false; synchronized(sent) { // add members not in membership to sent hashmap (with full credits) for(int i=0; i < mbrs.size(); i++) { addr=(Address) mbrs.elementAt(i); if(!sent.containsKey(addr)) sent.put(addr, new Long(max_credits)); } // remove members that left for(Iterator it=sent.keySet().iterator(); it.hasNext();) { addr=(Address)it.next(); if(!mbrs.contains(addr)) it.remove(); // modified the underlying map } // remove all creditors which are not in the new view for(int i=0; i < creditors.size(); i++) { Address creditor=(Address)creditors.elementAt(i); if(!mbrs.contains(creditor)) creditors.remove(creditor); } if(trace) log.trace("creditors are " + creditors); if(creditors.size() == 0) unblock=true; } if(unblock) unblockSender(); } private static String printMap(Map m) { Map.Entry entry; StringBuffer sb=new StringBuffer(); for(Iterator it=m.entrySet().iterator(); it.hasNext();) { entry=(Map.Entry)it.next(); sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } return sb.toString(); } public static class FcHeader extends Header implements Streamable { public static final byte REPLENISH = 1; public static final byte CREDIT_REQUEST = 2; // the sender of the message is the requester byte type = REPLENISH; public FcHeader() { } public FcHeader(byte type) { this.type=type; } public long size() { return Global.BYTE_SIZE; } public void writeExternal(ObjectOutput out) throws IOException { out.writeByte(type); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { type=in.readByte(); } public void writeTo(DataOutputStream out) throws IOException { out.writeByte(type); } public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { type=in.readByte(); } public String toString() { switch(type) { case REPLENISH: return "REPLENISH"; case CREDIT_REQUEST: return "CREDIT_REQUEST"; default: return ""; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy