|
|
@ -69,11 +69,15 @@ import org.slf4j.LoggerFactory; |
|
|
|
/** |
|
|
|
/** |
|
|
|
* A leader managing consensus across remote followers. |
|
|
|
* A leader managing consensus across remote followers. |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* A leader instance starts up in {@link State#CANDIDATE} and tries to begin a |
|
|
|
* A leader instance starts up in |
|
|
|
* new term by sending an {@link ElectionRound} to all replicas. Its term starts |
|
|
|
* {@link org.eclipse.jgit.internal.ketch.KetchLeader.State#CANDIDATE} and tries |
|
|
|
* if a majority of replicas have accepted this leader instance for the term. |
|
|
|
* to begin a new term by sending an |
|
|
|
|
|
|
|
* {@link org.eclipse.jgit.internal.ketch.ElectionRound} to all replicas. Its |
|
|
|
|
|
|
|
* term starts if a majority of replicas have accepted this leader instance for |
|
|
|
|
|
|
|
* the term. |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* Once elected by a majority the instance enters {@link State#LEADER} and runs |
|
|
|
* Once elected by a majority the instance enters |
|
|
|
|
|
|
|
* {@link org.eclipse.jgit.internal.ketch.KetchLeader.State#LEADER} and runs |
|
|
|
* proposals offered to {@link #queueProposal(Proposal)}. This continues until |
|
|
|
* proposals offered to {@link #queueProposal(Proposal)}. This continues until |
|
|
|
* the leader is timed out for inactivity, or is deposed by a competing leader |
|
|
|
* the leader is timed out for inactivity, or is deposed by a competing leader |
|
|
|
* gaining its own majority. |
|
|
|
* gaining its own majority. |
|
|
@ -81,36 +85,42 @@ import org.slf4j.LoggerFactory; |
|
|
|
* Once timed out or deposed this {@code KetchLeader} instance should be |
|
|
|
* Once timed out or deposed this {@code KetchLeader} instance should be |
|
|
|
* discarded, and a new instance takes over. |
|
|
|
* discarded, and a new instance takes over. |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* Each leader instance coordinates a group of {@link KetchReplica}s. Replica |
|
|
|
* Each leader instance coordinates a group of |
|
|
|
* instances are owned by the leader instance and must be discarded when the |
|
|
|
* {@link org.eclipse.jgit.internal.ketch.KetchReplica}s. Replica instances are |
|
|
|
* leader is discarded. |
|
|
|
* owned by the leader instance and must be discarded when the leader is |
|
|
|
|
|
|
|
* discarded. |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* In Ketch all push requests are issued through the leader. The steps are as |
|
|
|
* In Ketch all push requests are issued through the leader. The steps are as |
|
|
|
* follows (see {@link KetchPreReceive} for an example): |
|
|
|
* follows (see {@link org.eclipse.jgit.internal.ketch.KetchPreReceive} for an |
|
|
|
|
|
|
|
* example): |
|
|
|
* <ul> |
|
|
|
* <ul> |
|
|
|
* <li>Create a {@link Proposal} with the |
|
|
|
* <li>Create a {@link org.eclipse.jgit.internal.ketch.Proposal} with the |
|
|
|
* {@link org.eclipse.jgit.transport.ReceiveCommand}s that represent the push. |
|
|
|
* {@link org.eclipse.jgit.transport.ReceiveCommand}s that represent the push. |
|
|
|
* <li>Invoke {@link #queueProposal(Proposal)} on the leader instance. |
|
|
|
* <li>Invoke {@link #queueProposal(Proposal)} on the leader instance. |
|
|
|
* <li>Wait for consensus with {@link Proposal#await()}. |
|
|
|
* <li>Wait for consensus with |
|
|
|
* <li>To examine the status of the push, check {@link Proposal#getCommands()}, |
|
|
|
* {@link org.eclipse.jgit.internal.ketch.Proposal#await()}. |
|
|
|
* looking at |
|
|
|
* <li>To examine the status of the push, check |
|
|
|
|
|
|
|
* {@link org.eclipse.jgit.internal.ketch.Proposal#getCommands()}, looking at |
|
|
|
* {@link org.eclipse.jgit.internal.storage.reftree.Command#getResult()}. |
|
|
|
* {@link org.eclipse.jgit.internal.storage.reftree.Command#getResult()}. |
|
|
|
* </ul> |
|
|
|
* </ul> |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* The leader gains consensus by first pushing the needed objects and a |
|
|
|
* The leader gains consensus by first pushing the needed objects and a |
|
|
|
* {@link RefTree} representing the desired target repository state to the |
|
|
|
* {@link org.eclipse.jgit.internal.storage.reftree.RefTree} representing the |
|
|
|
* {@code refs/txn/accepted} branch on each of the replicas. Once a majority has |
|
|
|
* desired target repository state to the {@code refs/txn/accepted} branch on |
|
|
|
* succeeded, the leader commits the state by either pushing the |
|
|
|
* each of the replicas. Once a majority has succeeded, the leader commits the |
|
|
|
* {@code refs/txn/accepted} value to {@code refs/txn/committed} (for |
|
|
|
* state by either pushing the {@code refs/txn/accepted} value to |
|
|
|
* Ketch-aware replicas) or by pushing updates to {@code refs/heads/master}, |
|
|
|
* {@code refs/txn/committed} (for Ketch-aware replicas) or by pushing updates |
|
|
|
* etc. for stock Git replicas. |
|
|
|
* to {@code refs/heads/master}, etc. for stock Git replicas. |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* Internally, the actual transport to replicas is performed on background |
|
|
|
* Internally, the actual transport to replicas is performed on background |
|
|
|
* threads via the {@link KetchSystem}'s executor service. For performance, the |
|
|
|
* threads via the {@link org.eclipse.jgit.internal.ketch.KetchSystem}'s |
|
|
|
* {@link KetchLeader}, {@link KetchReplica} and {@link Proposal} objects share |
|
|
|
* executor service. For performance, the |
|
|
|
* some state, and may invoke each other's methods on different threads. This |
|
|
|
* {@link org.eclipse.jgit.internal.ketch.KetchLeader}, |
|
|
|
* access is protected by the leader's {@link #lock} object. Care must be taken |
|
|
|
* {@link org.eclipse.jgit.internal.ketch.KetchReplica} and |
|
|
|
* to prevent concurrent access by correctly obtaining the leader's lock. |
|
|
|
* {@link org.eclipse.jgit.internal.ketch.Proposal} objects share some state, |
|
|
|
|
|
|
|
* and may invoke each other's methods on different threads. This access is |
|
|
|
|
|
|
|
* protected by the leader's {@link #lock} object. Care must be taken to prevent |
|
|
|
|
|
|
|
* concurrent access by correctly obtaining the leader's lock. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public abstract class KetchLeader { |
|
|
|
public abstract class KetchLeader { |
|
|
|
private static final Logger log = LoggerFactory.getLogger(KetchLeader.class); |
|
|
|
private static final Logger log = LoggerFactory.getLogger(KetchLeader.class); |
|
|
@ -295,7 +305,7 @@ public abstract class KetchLeader { |
|
|
|
* The caller will close the repository. |
|
|
|
* The caller will close the repository. |
|
|
|
* |
|
|
|
* |
|
|
|
* @return opened repository for use by the leader thread. |
|
|
|
* @return opened repository for use by the leader thread. |
|
|
|
* @throws IOException |
|
|
|
* @throws java.io.IOException |
|
|
|
* cannot reopen the repository for the leader. |
|
|
|
* cannot reopen the repository for the leader. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
protected abstract Repository openRepository() throws IOException; |
|
|
|
protected abstract Repository openRepository() throws IOException; |
|
|
@ -307,16 +317,17 @@ public abstract class KetchLeader { |
|
|
|
* checked to look for risks of conflicts, and then submitted into the queue |
|
|
|
* checked to look for risks of conflicts, and then submitted into the queue |
|
|
|
* for distribution as soon as possible. |
|
|
|
* for distribution as soon as possible. |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* Callers must use {@link Proposal#await()} to see if the proposal is done. |
|
|
|
* Callers must use {@link org.eclipse.jgit.internal.ketch.Proposal#await()} |
|
|
|
|
|
|
|
* to see if the proposal is done. |
|
|
|
* |
|
|
|
* |
|
|
|
* @param proposal |
|
|
|
* @param proposal |
|
|
|
* the proposed reference updates to queue for consideration. |
|
|
|
* the proposed reference updates to queue for consideration. |
|
|
|
* Once execution is complete the individual reference result |
|
|
|
* Once execution is complete the individual reference result |
|
|
|
* fields will be populated with the outcome. |
|
|
|
* fields will be populated with the outcome. |
|
|
|
* @throws InterruptedException |
|
|
|
* @throws java.lang.InterruptedException |
|
|
|
* current thread was interrupted. The proposal may have been |
|
|
|
* current thread was interrupted. The proposal may have been |
|
|
|
* aborted if it was not yet queued for execution. |
|
|
|
* aborted if it was not yet queued for execution. |
|
|
|
* @throws IOException |
|
|
|
* @throws java.io.IOException |
|
|
|
* unrecoverable error preventing proposals from being attempted |
|
|
|
* unrecoverable error preventing proposals from being attempted |
|
|
|
* by this leader. |
|
|
|
* by this leader. |
|
|
|
*/ |
|
|
|
*/ |
|
|
@ -577,7 +588,11 @@ public abstract class KetchLeader { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** @return snapshot this leader. */ |
|
|
|
/** |
|
|
|
|
|
|
|
* Snapshot this leader |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @return snapshot of this leader |
|
|
|
|
|
|
|
*/ |
|
|
|
public LeaderSnapshot snapshot() { |
|
|
|
public LeaderSnapshot snapshot() { |
|
|
|
lock.lock(); |
|
|
|
lock.lock(); |
|
|
|
try { |
|
|
|
try { |
|
|
@ -599,7 +614,9 @@ public abstract class KetchLeader { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** Gracefully shutdown this leader and cancel outstanding operations. */ |
|
|
|
/** |
|
|
|
|
|
|
|
* Gracefully shutdown this leader and cancel outstanding operations. |
|
|
|
|
|
|
|
*/ |
|
|
|
public void shutdown() { |
|
|
|
public void shutdown() { |
|
|
|
lock.lock(); |
|
|
|
lock.lock(); |
|
|
|
try { |
|
|
|
try { |
|
|
@ -617,6 +634,7 @@ public abstract class KetchLeader { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** {@inheritDoc} */ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String toString() { |
|
|
|
public String toString() { |
|
|
|
return snapshot().toString(); |
|
|
|
return snapshot().toString(); |
|
|
|