org.jgroups.raft.util.CommitTable Maven / Gradle / Ivy
package org.jgroups.raft.util;
import org.jgroups.Address;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
* Keeps track of next_index and match_index for each cluster member (excluding this leader).
* Used to (1) compute the commit_index and (2) to resend log entries to members which haven't yet seen them.
* Only created on the leader
* @author Bela Ban
* @since 0.1
*/
public class CommitTable {
protected final ConcurrentMap map=new ConcurrentHashMap<>();
public CommitTable(List members, long next_index) {
adjust(members, next_index);
}
public Set keys() {return map.keySet();}
public Entry get(Address a) {return map.get(a);}
public void adjust(List members, long next_index) {
map.keySet().retainAll(members);
// entry is only created if mbr is not in map, reducing unneeded creations
members.forEach(mbr -> map.computeIfAbsent(mbr, k -> new Entry(next_index)));
}
public CommitTable update(Address member, long match_index, long next_index, long commit_index, boolean single_resend) {
return update(member, match_index, next_index, commit_index, single_resend, false);
}
public CommitTable update(Address member, long match_index, long next_index, long commit_index,
boolean single_resend, boolean overwrite) {
Entry e=map.get(member);
if(e == null)
return this;
e.match_index=overwrite? match_index : Math.max(match_index, e.match_index);
e.next_index=Math.max(1, next_index);
e.commit_index=Math.max(e.commit_index, commit_index);
e.send_single_msg=single_resend;
e.assertInvariant();
return this;
}
/** Applies a function to all elements of the commit table */
public void forEach(BiConsumer function) {
for(Map.Entry entry: map.entrySet()) {
Entry val=entry.getValue();
function.accept(entry.getKey(), val);
}
}
@Override
public String toString() {
return map.entrySet().stream().map(e -> String.format("%s: %s", e.getKey(), e.getValue()))
.collect(Collectors.joining("\n"));
}
public static class Entry {
protected long commit_index; // the commit index of the given member
protected long match_index; // the index of the highest entry known to be replicated to the member
protected long next_index; // the next index to send; initialized to last_appended +1
// set to true when next_index was decremented, so we only send a single entry on the next resend interval;
// set to false when we receive an AppendEntries(true) response
protected boolean send_single_msg;
public Entry(long next_index) {this.next_index=next_index;}
public long commitIndex() {return commit_index;}
public Entry commitIndex(long idx) {this.commit_index=idx; return this;}
public long matchIndex() {return match_index;}
public Entry matchIndex(long idx) {this.match_index=idx; return this;}
public long nextIndex() {return next_index;}
public Entry nextIndex(long idx) {next_index=idx; return this;}
public boolean sendSingleMessage() {return send_single_msg;}
public Entry sendSingleMessage(boolean flag) {this.send_single_msg=flag; return this;}
public void assertInvariant() {
assert commit_index <= match_index && match_index <= next_index : this;
}
@Override public String toString() {
StringBuilder sb=new StringBuilder("commit-index=").append(commit_index)
.append(", match-index=").append(match_index).append(", next-index=").append(next_index);
if(send_single_msg)
sb.append(" [send-single-msg]");
return sb.toString();
}
}
}