package org.jgroups.util; import org.jgroups.Address; import org.jgroups.Global; import org.jgroups.annotations.Immutable; import java.io.DataInput; import java.io.DataOutput; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import static java.lang.Math.max; /** * A message digest, containing senders and ranges of seqnos, where each sender is associated with its highest delivered * and received seqno seen so far.<p/> * <p> April 3 2001 (bela): Added high_seqnos_seen member. It is used to disseminate * information about the last (highest) message M received from a sender P. Since we might be using a * negative acknowledgment message numbering scheme, we would never know if the last message was * lost. Therefore we periodically gossip and include the last message seqno. Members who haven't seen * it (e.g. because msg was dropped) will request a retransmission. See DESIGN for details. * @author Bela Ban */ public class Digest implements Streamable, Iterable<Digest.DigestEntry> { // Stores all members. Random access is seldom, 99% of all use cases is sequential iteration through members protected Address[] members; // Stores highest delivered and received seqnos. This array is double the size of members. We store HD-HR pairs, // so to get the HD seqno for member P at index I --> seqnos[I * 2], to get the HR --> seqnos[i*2+1] protected long[] seqnos; protected Digest(Address[] members, long[] seqnos) { this.members=members; this.seqnos=seqnos; } /** Used for serialization */ public Digest() { } /** Creates a new digest from an existing map by copying the keys and values from map */ public Digest(Map<Address, long[]> map) { createArrays(map); } public Digest(Digest digest) { if(digest == null) return; createArrays(digest.size()); System.arraycopy(digest.members, 0, members, 0, digest.size()); System.arraycopy(digest.seqnos, 0, seqnos, 0, digest.size() *2); } public Digest(Address sender, long highest_delivered, long highest_received) { members=new Address[]{sender}; seqnos=new long[]{highest_delivered,highest_received}; } public Digest(Address sender, long highest_delivered) { this(sender, highest_delivered, highest_delivered); } public boolean contains(Address member) { for(int i=0; i < size(); i++) { Address addr=members[i]; if(addr != null && addr.equals(member)) return true; } return false; } /** * Returns true if our 'members' array contains all of the elements in other.members * @param other * @return */ public boolean containsAll(Digest other) { if(other == null) return false; for(int i=0; i < other.size(); i++) { Address member=other.members[i]; if(!contains(member)) return false; } return true; } /** Not really used, other than in unit tests and by FLUSH (reconciliation phase), so it doesn't need to be * super efficient */ public boolean equals(Object obj) { if(this == obj) return true; Digest other=(Digest)obj; if(!sameSenders(other)) return false; for(DigestEntry entry: this) { Address addr=entry.getMember(); long[] tmp=other.get(addr); if(entry.getHighestDeliveredSeqno() != tmp[0] || entry.getHighestReceivedSeqno() != tmp[1]) return false; } return true; } /** * Returns the highest delivered and received seqnos associated with a member. * This searches the members array sequentially, so use * sparingly * @param member * @return An array of 2 elements: highest_delivered and highest_received seqnos */ public long[] get(Address member) { int index=find(member); if(index < 0) return null; return new long[]{seqnos[index * 2], seqnos[index * 2 +1]}; } public Set<Address> getMembers() { HashSet<Address> retval=new HashSet<Address>(size()); for(int i=0; i < size(); i++) { Address tmp=members[i]; if(tmp != null) retval.add(tmp); } return retval; } /** * Compares two digests and returns true if the senders are the same, otherwise false. * @param other * @return True if senders are the same, otherwise false. */ public boolean sameSenders(Digest other) { return other != null && size() == other.size() && containsAll(other); } public Digest difference(Digest other) { if(other == null) return copy(); if(this.equals(other)) return null; // find intersection and compare their entries Map<Address,long[]> resultMap=new ConcurrentHashMap<Address,long[]>(7); Set<Address> intersection=new TreeSet<Address>(this.getMembers()); intersection.retainAll(other.getMembers()); for(Address address : intersection) { long[] e1=this.get(address); long[] e2=other.get(address); if(e1[0] != e2[0]) { long low=Math.min(e1[0], e2[0]); long high=max(e1[0], e2[0]); long[] r={low, high}; resultMap.put(address, r); } } // any entries left in (this - intersection) ? // if yes, add them to result if(intersection.size() != this.size()) { Set<Address> thisMinusInteresection=new TreeSet<Address>(this.getMembers()); thisMinusInteresection.removeAll(intersection); for(Address address : thisMinusInteresection) { long[] tmp=this.get(address); if(tmp != null) resultMap.put(address, tmp); } } // any entries left in (other - intersection) ? // if yes, add them to result if(intersection.size() != other.size()) { Set<Address> otherMinusInteresection=new TreeSet<Address>(other.getMembers()); otherMinusInteresection.removeAll(intersection); for(Address address : otherMinusInteresection) { long[] tmp=other.get(address); if(tmp != null) resultMap.put(address, tmp); } } return new Digest(resultMap); } public Digest highestSequence(Digest other) { if(other == null) return copy(); if(this.equals(other)) return this; //find intersection and compare their entries Map<Address,long[]> resultMap=new ConcurrentHashMap<Address,long[]>(7); Set<Address> intersection=new TreeSet<Address>(this.getMembers()); intersection.retainAll(other.getMembers()); for(Address address : intersection) { long[] e1=this.get(address); long[] e2=other.get(address); long high=max(e1[0], e2[0]); long[] r={0, high}; resultMap.put(address, r); } // any entries left in (this - intersection)? // if yes, add them to result if(intersection.size() != this.size()) { Set<Address> thisMinusInteresection=new TreeSet<Address>(this.getMembers()); thisMinusInteresection.removeAll(intersection); for(Address address : thisMinusInteresection) { long[] tmp=get(address); if(tmp != null) resultMap.put(address, tmp); } } // any entries left in (other - intersection) ? // if yes, add them to result if(intersection.size() != other.size()) { Set<Address> otherMinusInteresection=new TreeSet<Address>(other.getMembers()); otherMinusInteresection.removeAll(intersection); for(Address address : otherMinusInteresection) { long[] tmp=other.get(address); if(tmp != null) resultMap.put(address, tmp); } } return new Digest(resultMap); } public int size() { return members.length; } public long highestDeliveredSeqnoAt(Address sender) { long[] entry=get(sender); if(entry == null) return -1; return entry[0]; } public long highestReceivedSeqnoAt(Address sender) { long[] entry=get(sender); if(entry == null) return -1; return entry[1]; } /** * Returns true if all senders of the current digest have their seqnos >= the ones from other * @param other * @return */ public boolean isGreaterThanOrEqual(Digest other) { if(other == null) return true; for(DigestEntry entry: this) { Address sender=entry.getMember(); long[] their_entry=other.get(sender); if(their_entry == null) continue; long my_highest=entry.getHighest(); long their_highest=Math.max(their_entry[0], their_entry[1]); if(my_highest < their_highest) return false; } return true; } public Digest copy() { return new Digest(Arrays.copyOf(members, members.length), Arrays.copyOf(seqnos, seqnos.length)); } public String toString() { StringBuilder sb=new StringBuilder(); boolean first=true; if(size() == 0) return "[]"; int count=0, size=size(); for(DigestEntry entry: this) { Address key=entry.getMember(); if(!first) sb.append(", "); else first=false; sb.append(key).append(": ").append('[').append(entry.getHighestDeliveredSeqno()); if(entry.getHighestReceivedSeqno() >= 0) sb.append(" (").append(entry.getHighestReceivedSeqno()).append(")"); sb.append("]"); if(Util.MAX_LIST_PRINT_SIZE > 0 && ++count >= Util.MAX_LIST_PRINT_SIZE) { if(size > count) sb.append(", ..."); break; } } return sb.toString(); } public String toStringSorted() { return toStringSorted(true); } public String toStringSorted(boolean print_highest_received) { StringBuilder sb=new StringBuilder(); boolean first=true; if(size() == 0) return "[]"; TreeMap<Address,long[]> copy=new TreeMap<Address,long[]>(); for(int i=0; i < size(); i++) { Address addr=members[i]; long[] tmp={seqnos[i * 2], seqnos[i * 2 +1]}; copy.put(addr, tmp); } int count=0, size=copy.size(); for(Map.Entry<Address,long[]> entry: copy.entrySet()) { Address key=entry.getKey(); long[] val=entry.getValue(); if(!first) sb.append(", "); else first=false; sb.append(key).append(": ").append('[').append(val[0]); if(print_highest_received) sb.append(" (").append(val[1]).append(")"); sb.append("]"); if(Util.MAX_LIST_PRINT_SIZE > 0 && ++count >= Util.MAX_LIST_PRINT_SIZE) { if(size > count) sb.append(", ..."); break; } } return sb.toString(); } public String printHighestDeliveredSeqnos() { return toStringSorted(false); } public void writeTo(DataOutput out) throws Exception { out.writeShort(size()); for(int i=0; i < size(); i++) Util.writeAddress(members[i], out); for(int i=0; i < size(); i++) Util.writeLongSequence(seqnos[i * 2], seqnos[i * 2 +1], out); } public void readFrom(DataInput in) throws Exception { short size=in.readShort(); createArrays(size); for(int i=0; i < size; i++) { Address addr=Util.readAddress(in); members[i]=addr; } for(int i=0; i < size; i++) { long[] tmp=Util.readLongSequence(in); seqnos[i * 2]=tmp[0]; seqnos[i * 2 +1]=tmp[1]; } } public long serializedSize() { long retval=Global.SHORT_SIZE; // number of elements in 'senders' if(size() > 0) { Address addr=members[0]; retval+=Util.size(addr) * size(); } for(int i=0; i < size() * 2; i+=2) retval+=Util.size(seqnos[i], seqnos[i+1]); return retval; } protected int find(Address member) { for(int i=0; i < size(); i++) { Address addr=members[i]; if(addr != null && addr.equals(member)) return i; } return -1; } protected void createArrays(int size) { members=new Address[size]; seqnos=new long[size * 2]; } protected void createArrays(Map<Address,long[]> map) { createArrays(map.size()); int index=0; for(Map.Entry<Address,long[]> entry: map.entrySet()) { members[index]=entry.getKey(); seqnos[index * 2 ]=entry.getValue()[0]; seqnos[index * 2 +1]=entry.getValue()[1]; index++; } } public Iterator<DigestEntry> iterator() { return new MyIterator(); } protected class MyIterator implements Iterator<DigestEntry> { int index=0; public boolean hasNext() { return index < size(); } public DigestEntry next() { if(index >= size()) throw new NoSuchElementException("index=" + index + ", members.length=" + members.length); DigestEntry entry=new DigestEntry(members[index], seqnos[index * 2], seqnos[index * 2 +1]); index++; return entry; } public void remove() { members[index]=null; // doesn't make much sense, but won't be called anyway } } /** Keeps track of one members plus its highest delivered and received seqnos */ @Immutable public static class DigestEntry { protected final Address member; protected final long highest_delivered; protected final long highest_received; public DigestEntry(Address member, long highest_delivered, long highest_received) { this.member=member; this.highest_delivered=highest_delivered; this.highest_received=highest_received; } public Address getMember() {return member;} public long getHighestDeliveredSeqno() {return highest_delivered;} public long getHighestReceivedSeqno() {return highest_received;} public long getHighest() {return max(highest_delivered, highest_received);} public String toString() { return member + ": [" + highest_delivered + " (" + highest_received + ")]"; } } }