org.jgroups.util.CreditMap Maven / Gradle / Ivy
package org.jgroups.util;
import org.jgroups.Address;
import org.jgroups.annotations.GuardedBy;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Maintains credits for senders, when credits fall below 0, a sender blocks until new credits have been received.
* @author Bela Ban
*/
public class CreditMap {
protected final long max_credits;
@GuardedBy("lock")
protected final Map credits=new HashMap<>();
protected long min_credits;
protected long accumulated_credits;
protected final Lock lock=new ReentrantLock();
protected final Condition credits_available=lock.newCondition();
protected int num_blockings;
protected final Average avg_block_time=new Average(); // in ns
public CreditMap(long max_credits) {
this.max_credits=max_credits;
min_credits=max_credits;
}
public long getAccumulatedCredits() {return accumulated_credits;}
public long getMinCredits() {return min_credits;}
public int getNumBlockings() {return num_blockings;}
public double getAverageBlockTime() {return avg_block_time.getAverage() / 1000000.0;} // in ms
public Set keys() {
lock.lock();
try {
return credits.keySet();
}
finally {
lock.unlock();
}
}
public Long get(Address member) {
lock.lock();
try {
return credits.get(member);
}
finally {
lock.unlock();
}
}
public Long remove(Address key) {
lock.lock();
try {
Long retval=credits.remove(key);
flushAccumulatedCredits();
long new_min=computeLowestCredit();
if(new_min > min_credits) {
min_credits=new_min;
credits_available.signalAll();
}
return retval;
}
finally {
lock.unlock();
}
}
public Long putIfAbsent(Address key) {
lock.lock();
try {
flushAccumulatedCredits();
Long val=credits.get(key);
return val != null? val : credits.put(key, max_credits);
}
finally {
lock.unlock();
}
}
public List getMembersWithInsufficientCredits(long credit_needed) {
List retval=new LinkedList<>();
lock.lock();
try {
if(credit_needed > min_credits) {
flushAccumulatedCredits();
credits.entrySet().stream().filter(entry -> entry.getValue() < credit_needed)
.forEach(entry -> retval.add(entry.getKey()));
}
return retval;
}
finally {
lock.unlock();
}
}
public List> getMembersWithCreditsLessThan(long min_credits) {
List> retval=new LinkedList<>();
lock.lock();
try {
flushAccumulatedCredits();
credits.entrySet().stream().filter(entry -> entry.getValue() <= min_credits)
.forEach(entry -> retval.add(new Tuple<>(entry.getKey(), entry.getValue())));
return retval;
}
finally {
lock.unlock();
}
}
/**
* Decrements credits bytes from all. Returns true if successful, or false if not. Blocks for timeout ms
* (if greater than 0).
*
* @param credits Number of bytes to decrement from all members
* @param timeout Number of milliseconds to wait until more credits have been received
* @return True if decrementing credits bytes succeeded, false otherwise
*/
public boolean decrement(long credits, long timeout) {
lock.lock();
try {
if(decrement(credits))
return true;
if(timeout <= 0)
return false;
long start=System.nanoTime();
try {
credits_available.await(timeout, TimeUnit.MILLISECONDS);
}
catch(InterruptedException e) {
}
finally {
num_blockings++;
avg_block_time.add(System.nanoTime() - start);
}
return decrement(credits);
}
finally {
lock.unlock();
}
}
public void replenish(Address sender, long new_credits) {
if(sender == null)
return;
lock.lock();
try {
Long val=credits.get(sender);
if(val == null)
return;
boolean potential_update=val - accumulated_credits <= min_credits;
decrementAndAdd(sender, new_credits);
if(potential_update) {
long new_min=computeLowestCredit();
if(new_min > min_credits) {
min_credits=new_min;
credits_available.signalAll();
}
}
}
finally {
lock.unlock();
}
}
public void replenishAll() {
lock.lock();
try {
flushAccumulatedCredits();
for(Map.Entry entry: credits.entrySet())
entry.setValue(max_credits);
min_credits=computeLowestCredit();
credits_available.signalAll();
}
finally {
lock.unlock();
}
}
public void clear() {
lock.lock();
try {
num_blockings=0;
avg_block_time.clear();
credits.clear();
credits_available.signalAll();
}
finally {
lock.unlock();
}
}
public void reset() {
lock.lock();
try {
num_blockings=0;
avg_block_time.clear();
}
finally {
lock.unlock();
}
}
public String toString() {
StringBuilder sb=new StringBuilder();
lock.lock();
try {
for(Map.Entry entry: credits.entrySet()) {
sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
}
sb.append("min_credits=" + min_credits + ", accumulated=" + accumulated_credits);
}
finally {
lock.unlock();
}
return sb.toString();
}
// need to be called with lock held
protected boolean decrement(long credits) {
if(credits <= min_credits) {
accumulated_credits+=credits;
min_credits-=credits;
return true;
}
return false;
}
/** Needs to be called with lock held */
protected long computeLowestCredit() {
long lowest=max_credits;
for(long cred: credits.values())
lowest=Math.min(cred, lowest);
return lowest;
}
public long computeLowestCreditWithAccumulated() {
long lowest=max_credits;
for(long cred: credits.values())
lowest=Math.min(cred, lowest);
return lowest - accumulated_credits;
}
/**
* Decrements credits bytes from all elements and add new_credits to member (if non null).
* The lowest credit needs to be greater than min_credits. Needs to be called with lock held
* @param member The member to which new_credits are added. NOP if null
* @param new_credits Number of bytes to add to member. NOP if 0.
*/
protected void decrementAndAdd(Address member, long new_credits) {
boolean replenish=member != null && new_credits > 0;
if(accumulated_credits > 0) {
for(Map.Entry entry: this.credits.entrySet()) {
entry.setValue(Math.max(0,entry.getValue() - accumulated_credits));
if(replenish) {
Address tmp=entry.getKey();
if(tmp.equals(member))
entry.setValue(Math.min(max_credits,entry.getValue() + new_credits));
}
}
accumulated_credits=0;
}
else {
if(replenish) {
Long val=this.credits.get(member);
if(val != null)
this.credits.put(member, Math.min(max_credits,val + new_credits));
}
}
}
// Called with lock held
protected void flushAccumulatedCredits() {
if(accumulated_credits > 0) {
for(Map.Entry entry: this.credits.entrySet()) {
entry.setValue(Math.max(0,entry.getValue() - accumulated_credits));
}
accumulated_credits=0;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy