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

org.jgroups.protocols.raft.state.RaftState Maven / Gradle / Ivy

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

import java.util.Objects;
import java.util.function.Consumer;

import org.jgroups.Address;
import org.jgroups.protocols.raft.Log;
import org.jgroups.protocols.raft.RAFT;

import net.jcip.annotations.ThreadSafe;

/**
 * Keep track of part of the algorithm state. The state here is tightly synchronized and needs to be updated atomically.
 * 

* Throughout the RAFT execution, the node updates the values for term, leader, and the election vote. The values need * to be updated atomically, following the term transition. This synchronization is necessary for clients to perceive * the same leaders and terms. *

* Utilizing both Ongaro's dissertation1 and the original RAFT2 article as the base. The relation * of all these variables in the state may not be clear. Starting with the assertion that a term only starts through a * new election. In other words, when updating the term, the leader is unknown. The term increases monotonically, and * some nodes might skip it due to connectivity issues. *

* All messages carry the sender's current term. On receiving a message, nodes verify and update. Messages with older * terms are discarded. A higher term causes the node to advance its term, set both the leader and vote to null, and * proceed with the message processing. *

* The leader value only updates to the elected leader (null -> new leader) or to step down (leader -> null). Updating * the leader directly to another throws an exception. The election vote follows the same logic. *

* In summary, the logic follows that updating to a received higher term sets the leader and vote to null. Later, when * the leader is known, the update within the same term is accepted since the current term leader is still null. * * @author José Bolina * @since 1.0.12 * @see 1: Ongaro's dissertation * @see 2: RAFT paper */ @ThreadSafe public class RaftState { private final RAFT raft; private final Consumer

onLeaderUpdate; private Address leader; private long term; private Address votedFor; public RaftState(RAFT raft, Consumer
onLeaderUpdate) { this.raft = raft; this.onLeaderUpdate = onLeaderUpdate; reload(); } public synchronized Address leader() { return leader; } public synchronized long currentTerm() { return term; } public synchronized Address votedFor() { return votedFor; } /** * Advances the term for leader election. *

* This automatically sets the {@link #leader} and {@link #votedFor} to null. *

* * @return The new term. * @see #tryAdvanceTerm(long) */ public long advanceTermForElection() { while (true) { long current = currentTerm(); if (tryAdvanceTerm(current + 1) == 1) { return currentTerm(); } } } /** * Try advancing the term, and if succeeding, the leader is null. * * @param newTerm: The term to advance to. * @return Has the same result as Long.compare(newTerm, currentTerm());. * @see #tryAdvanceTermAndLeader(long, Address) */ public int tryAdvanceTerm(long newTerm) { return tryAdvanceTermAndLeader(newTerm, null); } /** * Try to advance the current term and set the leader. *

* A lower term has no effect. A higher term updates the node's current term to {@param newTerm}, and the leader to * {@param newLeader} and vote to null. The term and vote are written to disk. * * @param newTerm: New term to update to. * @param newLeader: The leader to update in case the term advances. * @return Has the same result as Long.compare(newTerm, currentTerm());. That is, -1 if the * newTerm < currentTerm(), 0 if both are equal, and 1 if currentTerm() is higher. */ public int tryAdvanceTermAndLeader(long newTerm, Address newLeader) { boolean saveVote = false; boolean saveTerm = false; synchronized (this) { // Step down uses the term `0`. It does not update the term, only the leader to null. if (newTerm > 0 && newTerm < term) return -1; if (newTerm == term && newLeader == null) return 0; if (newTerm > term) { raft.getLog().trace("%s: changed term from %d -> %d", raft.addr(), term, newTerm); term = newTerm; saveTerm = true; saveVote = setVotedFor(null, false); // To not invoke the leader change listener twice. this.leader = null; } setLeader(newLeader); } Log log = raft.log(); if (log != null) { if (saveTerm) log.currentTerm(currentTerm()); if (saveVote) log.votedFor(votedFor()); } return saveTerm ? 1 : 0; } /** * Update the leader in the current term. *

* A leader can not change within the same term. The update only happens when a new leader is elected * (null -> new leader) or when the leader steps down (current leader -> null). * * @param newLeader: The new leader to update to. * @throws IllegalStateException: If trying to update between different leaders in the same term. */ public synchronized void setLeader(Address newLeader) { if (this.leader != null && newLeader != null && !Objects.equals(this.leader, newLeader)) throw new IllegalStateException(String.format("Changing leader %s to %s illegally", this.leader, newLeader)); boolean updated = newLeader == null || this.leader == null; this.leader = newLeader; // We only should invoke the listener when a new leader is set/step down. if (updated) { raft.getLog().trace("%s: change leader from %s -> %s", raft.addr(), leader, newLeader); onLeaderUpdate.accept(this.leader); } } /** * Set the vote in the current term. *

* Follows the same logic as {@link #setLeader(Address)}. The vote is also written to disk. * * @param votedFor: The vote in the current term. * @throws IllegalStateException: If trying to update between different votes within the same term. */ public void setVotedFor(Address votedFor) { Objects.requireNonNull(votedFor, "Voted for must not be null"); setVotedFor(votedFor, true); } /** * Read the state information from the {@link RAFT}'s log. */ public void reload() { Log log = raft.log(); if (log != null) { synchronized (this) { term = log.currentTerm(); votedFor = log.votedFor(); } } } private boolean setVotedFor(Address votedFor, boolean save) { synchronized (this) { if (votedFor != null && this.votedFor != null && !Objects.equals(votedFor, this.votedFor)) throw new IllegalStateException(String.format("Changing vote %s to %s illegally", this.votedFor, votedFor)); // If it is the same object, returns null as there is no need to flush to disk. if (Objects.equals(this.votedFor, votedFor)) return false; this.votedFor = votedFor; } if (votedFor != null && raft.getLog().isTraceEnabled()) { raft.getLog().trace("%s: voted for %s in term %d", raft.addr(), votedFor, currentTerm()); } if (save) { Log log = raft.log(); if (log != null) log.votedFor(votedFor()); } return true; } @Override public String toString() { return "[leader=" + leader + ", term=" + term + ", voted=" + votedFor + "]"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy