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

org.jgroups.raft.util.RequestTable Maven / Gradle / Ivy

There is a newer version: 1.0.13.Final
Show newest version
package org.jgroups.raft.util;


import org.jgroups.raft.Options;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

/**
 * Keeps track of AppendRequest messages and responses. Each AppendEntry request is keyed by the index at which
 * it was inserted at the leader. The values (RequestEntry) contain the responses from followers. When a response
 * is added, and the majority has been reached, add() returns true and the key/value pair will be removed.
 * (subsequent responses will be ignored). On a majority, the commit index is advanced.
 * 

* Only created on leader * @author Bela Ban * @since 0.1 */ public class RequestTable { // maps an index to a set of (response) senders protected ArrayRingBuffer> requests; // Identify the request table was destroyed. // All subsequent requests should complete exceptionally immediately. private Throwable destroyed; public void create(long index, T vote, CompletableFuture future, Supplier majority) { create(index, vote, future, majority, null); } public void create(long index, T vote, CompletableFuture future, Supplier majority, Options opts) { Entry entry=new Entry<>(future, opts); if (requests == null) { requests = new ArrayRingBuffer<>(index); } requests.set(index, entry); entry.add(vote, majority); // In case the leader steps down while still adding elements. if (destroyed != null) entry.notify(destroyed); } /** * Completes all uncommitted requests with the provided exception. * *

* This method should be invoked before setting the instance to null when the leader steps down. * This provides a more responsive completion of requests to the users, instead of having requests time out. * Internal operations, such as membership changes, do not have a timeout associated, which would hang and any * subsequent changes would not complete. *

* * @param t: Throwable to complete the requests exceptionally. */ public void destroy(Throwable t) { // Keep throwable so any entries created *after* destroying also complete exceptionally. destroyed = t; if (requests != null) { requests.forEach((e, ignore) -> { if (!e.committed) { e.notify(t); } }); } } /** * Adds a response to the response set. If the majority has been reached, returns true * @return True if a majority has been reached, false otherwise. Note that this is done exactly once */ public boolean add(long index, T sender, Supplier majority) { // we're getting an ack for index, but we also need to ack entries lower than index (if any, should only // happen on leader change): https://github.com/belaban/jgroups-raft/issues/122 if (requests == null) { return false; } boolean added = false; for (long i = requests.getHeadSequence(); i <= Math.min(index, requests.getTailSequence() - 1); i++) { final Entry entry = requests.get(i); if (entry == null) { continue; } final boolean entryAdded = entry.add(sender, majority); if (i == index && entryAdded) { added = true; } } return added; } /** Whether or not the entry at index is committed */ public boolean isCommitted(long index) { if (requests == null) { return false; } if (index < requests.getHeadSequence()) { return true; } Entry entry=requests.contains(index)? requests.get(index) : null; return entry != null && entry.committed; } /** number of requests being processed */ public int size() { if(requests == null) return 0; return requests.size(false); } public Entry remove(long index) { return requests != null? requests.remove(index) : null; } /** Notifies the CompletableFuture and then removes the entry for index */ public void notifyAndRemove(long index, byte[] response) { Entry entry=remove(index); if(entry != null && entry.client_future != null) entry.client_future.complete(response); } public String toString() { if (requests == null || requests.isEmpty()) { return ""; } StringBuilder sb=new StringBuilder(); requests.forEach((entry, index) -> sb.append(index).append(": ").append(entry).append("\n")); return sb.toString(); } public static class Entry { // the future has been returned to the caller, and needs to be notified when we've reached a majority protected final CompletableFuture client_future; protected final Set votes=new HashSet<>(); protected final Options opts; protected boolean committed; public Entry(CompletableFuture client_future, Options opts) { this.client_future=client_future; this.opts=opts; } public Options options() {return opts;} public boolean add(T vote, final Supplier majority) { return votes.add(vote) && votes.size() >= majority.get() && commit(); } public void notify(byte[] result) { if(client_future != null) client_future.complete(result); } public void notify(Throwable t) { if(client_future != null) client_future.completeExceptionally(t); } // returns true only the first time the entry is committed public boolean commit() { boolean prev_committed = committed; committed=true; return !prev_committed; } @Override public String toString() { return String.format("committed=%b, votes=%s %s", committed, votes, opts != null? opts.toString() : ""); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy