Change-Id: I72be0a0d5ac7d3e957597803bd103dbbec141ba4tags/v4.10.0.201712302008-r
@@ -45,7 +45,9 @@ package org.eclipse.jgit.internal.ketch; | |||
import org.eclipse.jgit.revwalk.FooterKey; | |||
/** Frequently used constants in a Ketch system. */ | |||
/** | |||
* Frequently used constants in a Ketch system. | |||
*/ | |||
public class KetchConstants { | |||
/** | |||
* Default reference namespace holding {@link #ACCEPTED} and |
@@ -69,11 +69,15 @@ import org.slf4j.LoggerFactory; | |||
/** | |||
* A leader managing consensus across remote followers. | |||
* <p> | |||
* A leader instance starts up in {@link State#CANDIDATE} and tries to begin a | |||
* new term by sending an {@link ElectionRound} to all replicas. Its term starts | |||
* if a majority of replicas have accepted this leader instance for the term. | |||
* A leader instance starts up in | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader.State#CANDIDATE} and tries | |||
* 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> | |||
* 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 | |||
* the leader is timed out for inactivity, or is deposed by a competing leader | |||
* gaining its own majority. | |||
@@ -81,36 +85,42 @@ import org.slf4j.LoggerFactory; | |||
* Once timed out or deposed this {@code KetchLeader} instance should be | |||
* discarded, and a new instance takes over. | |||
* <p> | |||
* Each leader instance coordinates a group of {@link KetchReplica}s. Replica | |||
* instances are owned by the leader instance and must be discarded when the | |||
* leader is discarded. | |||
* Each leader instance coordinates a group of | |||
* {@link org.eclipse.jgit.internal.ketch.KetchReplica}s. Replica instances are | |||
* owned by the leader instance and must be discarded when the leader is | |||
* discarded. | |||
* <p> | |||
* 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> | |||
* <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. | |||
* <li>Invoke {@link #queueProposal(Proposal)} on the leader instance. | |||
* <li>Wait for consensus with {@link Proposal#await()}. | |||
* <li>To examine the status of the push, check {@link Proposal#getCommands()}, | |||
* looking at | |||
* <li>Wait for consensus with | |||
* {@link org.eclipse.jgit.internal.ketch.Proposal#await()}. | |||
* <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()}. | |||
* </ul> | |||
* <p> | |||
* The leader gains consensus by first pushing the needed objects and a | |||
* {@link RefTree} representing the desired target repository state to the | |||
* {@code refs/txn/accepted} branch on each of the replicas. Once a majority has | |||
* succeeded, the leader commits the state by either pushing the | |||
* {@code refs/txn/accepted} value to {@code refs/txn/committed} (for | |||
* Ketch-aware replicas) or by pushing updates to {@code refs/heads/master}, | |||
* etc. for stock Git replicas. | |||
* {@link org.eclipse.jgit.internal.storage.reftree.RefTree} representing the | |||
* desired target repository state to the {@code refs/txn/accepted} branch on | |||
* each of the replicas. Once a majority has succeeded, the leader commits the | |||
* state by either pushing the {@code refs/txn/accepted} value to | |||
* {@code refs/txn/committed} (for Ketch-aware replicas) or by pushing updates | |||
* to {@code refs/heads/master}, etc. for stock Git replicas. | |||
* <p> | |||
* Internally, the actual transport to replicas is performed on background | |||
* threads via the {@link KetchSystem}'s executor service. For performance, the | |||
* {@link KetchLeader}, {@link KetchReplica} and {@link 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. | |||
* threads via the {@link org.eclipse.jgit.internal.ketch.KetchSystem}'s | |||
* executor service. For performance, the | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader}, | |||
* {@link org.eclipse.jgit.internal.ketch.KetchReplica} and | |||
* {@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 { | |||
private static final Logger log = LoggerFactory.getLogger(KetchLeader.class); | |||
@@ -295,7 +305,7 @@ public abstract class KetchLeader { | |||
* The caller will close the repository. | |||
* | |||
* @return opened repository for use by the leader thread. | |||
* @throws IOException | |||
* @throws java.io.IOException | |||
* cannot reopen the repository for the leader. | |||
*/ | |||
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 | |||
* for distribution as soon as possible. | |||
* <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 | |||
* the proposed reference updates to queue for consideration. | |||
* Once execution is complete the individual reference result | |||
* fields will be populated with the outcome. | |||
* @throws InterruptedException | |||
* @throws java.lang.InterruptedException | |||
* current thread was interrupted. The proposal may have been | |||
* aborted if it was not yet queued for execution. | |||
* @throws IOException | |||
* @throws java.io.IOException | |||
* unrecoverable error preventing proposals from being attempted | |||
* 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() { | |||
lock.lock(); | |||
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() { | |||
lock.lock(); | |||
try { | |||
@@ -617,6 +634,7 @@ public abstract class KetchLeader { | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public String toString() { | |||
return snapshot().toString(); |
@@ -82,7 +82,7 @@ public class KetchLeaderCache { | |||
* @param repo | |||
* repository to get the leader for. | |||
* @return the leader instance for the repository. | |||
* @throws URISyntaxException | |||
* @throws java.net.URISyntaxException | |||
* remote configuration contains an invalid URL. | |||
*/ | |||
public KetchLeader get(Repository repo) |
@@ -64,9 +64,10 @@ import org.slf4j.LoggerFactory; | |||
/** | |||
* PreReceiveHook for handling push traffic in a Ketch system. | |||
* <p> | |||
* Install an instance on {@link ReceivePack} to capture the commands and other | |||
* connection state and relay them through the {@link KetchLeader}, allowing the | |||
* leader to gain consensus about the new reference state. | |||
* Install an instance on {@link org.eclipse.jgit.transport.ReceivePack} to | |||
* capture the commands and other connection state and relay them through the | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader}, allowing the leader to | |||
* gain consensus about the new reference state. | |||
*/ | |||
public class KetchPreReceive implements PreReceiveHook { | |||
private static final Logger log = LoggerFactory.getLogger(KetchPreReceive.class); | |||
@@ -74,7 +75,8 @@ public class KetchPreReceive implements PreReceiveHook { | |||
private final KetchLeader leader; | |||
/** | |||
* Construct a hook executing updates through a {@link KetchLeader}. | |||
* Construct a hook executing updates through a | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader}. | |||
* | |||
* @param leader | |||
* leader for this repository. | |||
@@ -83,6 +85,7 @@ public class KetchPreReceive implements PreReceiveHook { | |||
this.leader = leader; | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> cmds) { | |||
cmds = ReceiveCommand.filter(cmds, NOT_ATTEMPTED); |
@@ -82,26 +82,32 @@ import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
/** | |||
* A Ketch replica, either {@link LocalReplica} or {@link RemoteGitReplica}. | |||
* A Ketch replica, either {@link org.eclipse.jgit.internal.ketch.LocalReplica} | |||
* or {@link org.eclipse.jgit.internal.ketch.RemoteGitReplica}. | |||
* <p> | |||
* Replicas can be either a stock Git replica, or a Ketch-aware replica. | |||
* <p> | |||
* A stock Git replica has no special knowledge of Ketch and simply stores | |||
* objects and references. Ketch communicates with the stock Git replica using | |||
* the Git push wire protocol. The {@link KetchLeader} commits an agreed upon | |||
* the Git push wire protocol. The | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader} commits an agreed upon | |||
* state by pushing all references to the Git replica, for example | |||
* {@code "refs/heads/master"} is pushed during commit. Stock Git replicas use | |||
* {@link CommitMethod#ALL_REFS} to record the final state. | |||
* {@link org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod#ALL_REFS} to | |||
* record the final state. | |||
* <p> | |||
* Ketch-aware replicas understand the {@code RefTree} sent during the proposal | |||
* and during commit are able to update their own reference space to match the | |||
* state represented by the {@code RefTree}. Ketch-aware replicas typically use | |||
* a {@link org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase} and | |||
* {@link CommitMethod#TXN_COMMITTED} to record the final state. | |||
* {@link org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod#TXN_COMMITTED} | |||
* to record the final state. | |||
* <p> | |||
* KetchReplica instances are tightly coupled with a single {@link KetchLeader}. | |||
* Some state may be accessed by the leader thread and uses the leader's own | |||
* {@link KetchLeader#lock} to protect shared data. | |||
* KetchReplica instances are tightly coupled with a single | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader}. Some state may be | |||
* accessed by the leader thread and uses the leader's own | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader#lock} to protect shared | |||
* data. | |||
*/ | |||
public abstract class KetchReplica { | |||
static final Logger log = LoggerFactory.getLogger(KetchReplica.class); | |||
@@ -223,37 +229,65 @@ public abstract class KetchReplica { | |||
this.queued = new ArrayList<>(4); | |||
} | |||
/** @return system configuration. */ | |||
/** | |||
* Get system configuration. | |||
* | |||
* @return system configuration. | |||
*/ | |||
public KetchSystem getSystem() { | |||
return getLeader().getSystem(); | |||
} | |||
/** @return leader instance this replica follows. */ | |||
/** | |||
* Get leader instance this replica follows. | |||
* | |||
* @return leader instance this replica follows. | |||
*/ | |||
public KetchLeader getLeader() { | |||
return leader; | |||
} | |||
/** @return unique-ish name for debugging. */ | |||
/** | |||
* Get unique-ish name for debugging. | |||
* | |||
* @return unique-ish name for debugging. | |||
*/ | |||
public String getName() { | |||
return replicaName; | |||
} | |||
/** @return description of this replica for error/debug logging purposes. */ | |||
/** | |||
* Get description of this replica for error/debug logging purposes. | |||
* | |||
* @return description of this replica for error/debug logging purposes. | |||
*/ | |||
protected String describeForLog() { | |||
return getName(); | |||
} | |||
/** @return how the replica participates in this Ketch system. */ | |||
/** | |||
* Get how the replica participates in this Ketch system. | |||
* | |||
* @return how the replica participates in this Ketch system. | |||
*/ | |||
public Participation getParticipation() { | |||
return participation; | |||
} | |||
/** @return how Ketch will commit to the repository. */ | |||
/** | |||
* Get how Ketch will commit to the repository. | |||
* | |||
* @return how Ketch will commit to the repository. | |||
*/ | |||
public CommitMethod getCommitMethod() { | |||
return commitMethod; | |||
} | |||
/** @return when Ketch will commit to the repository. */ | |||
/** | |||
* Get when Ketch will commit to the repository. | |||
* | |||
* @return when Ketch will commit to the repository. | |||
*/ | |||
public CommitSpeed getCommitSpeed() { | |||
return commitSpeed; | |||
} | |||
@@ -264,7 +298,8 @@ public abstract class KetchReplica { | |||
* Default implementation cancels any scheduled retry. Subclasses may add | |||
* additional logic before or after calling {@code super.shutdown()}. | |||
* <p> | |||
* Called with {@link KetchLeader#lock} held by caller. | |||
* Called with {@link org.eclipse.jgit.internal.ketch.KetchLeader#lock} held | |||
* by caller. | |||
*/ | |||
protected void shutdown() { | |||
Future<?> f = retryFuture; | |||
@@ -541,8 +576,8 @@ public abstract class KetchReplica { | |||
/** | |||
* Begin executing a single push. | |||
* <p> | |||
* This method must move processing onto another thread. | |||
* Called with {@link KetchLeader#lock} held by caller. | |||
* This method must move processing onto another thread. Called with | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader#lock} held by caller. | |||
* | |||
* @param req | |||
* the request to send to the replica. | |||
@@ -666,20 +701,21 @@ public abstract class KetchReplica { | |||
/** | |||
* Fetch objects from the remote using the calling thread. | |||
* <p> | |||
* Called without {@link KetchLeader#lock}. | |||
* Called without {@link org.eclipse.jgit.internal.ketch.KetchLeader#lock}. | |||
* | |||
* @param repo | |||
* local repository to fetch objects into. | |||
* @param req | |||
* the request to fetch from a replica. | |||
* @throws IOException | |||
* @throws java.io.IOException | |||
* communication with the replica was not possible. | |||
*/ | |||
protected abstract void blockingFetch(Repository repo, | |||
ReplicaFetchRequest req) throws IOException; | |||
/** | |||
* Build a list of commands to commit {@link CommitMethod#ALL_REFS}. | |||
* Build a list of commands to commit | |||
* {@link org.eclipse.jgit.internal.ketch.KetchReplica.CommitMethod#ALL_REFS}. | |||
* | |||
* @param git | |||
* local leader repository to read committed state from. | |||
@@ -689,7 +725,7 @@ public abstract class KetchReplica { | |||
* @param committed | |||
* state being pushed to {@code refs/txn/committed}. | |||
* @return commands to update during commit. | |||
* @throws IOException | |||
* @throws java.io.IOException | |||
* cannot read the committed state. | |||
*/ | |||
protected Collection<ReceiveCommand> prepareCommit(Repository git, |
@@ -81,12 +81,17 @@ import org.slf4j.LoggerFactory; | |||
* Full scale installations are expected to subclass and override methods to | |||
* provide consistent configuration across all managed repositories. | |||
* <p> | |||
* Servers should configure their own {@link ScheduledExecutorService}. | |||
* Servers should configure their own | |||
* {@link java.util.concurrent.ScheduledExecutorService}. | |||
*/ | |||
public class KetchSystem { | |||
private static final Random RNG = new Random(); | |||
/** @return default executor, one thread per available processor. */ | |||
/** | |||
* Get default executor, one thread per available processor. | |||
* | |||
* @return default executor, one thread per available processor. | |||
*/ | |||
public static ScheduledExecutorService defaultExecutor() { | |||
return DefaultExecutorHolder.I; | |||
} | |||
@@ -98,7 +103,9 @@ public class KetchSystem { | |||
private final String txnCommitted; | |||
private final String txnStage; | |||
/** Create a default system with a thread pool of 1 thread per CPU. */ | |||
/** | |||
* Create a default system with a thread pool of 1 thread per CPU. | |||
*/ | |||
public KetchSystem() { | |||
this(defaultExecutor(), new MonotonicSystemClock(), DEFAULT_TXN_NAMESPACE); | |||
} | |||
@@ -125,17 +132,29 @@ public class KetchSystem { | |||
this.txnStage = txnNamespace + STAGE; | |||
} | |||
/** @return executor to perform background operations. */ | |||
/** | |||
* Get executor to perform background operations. | |||
* | |||
* @return executor to perform background operations. | |||
*/ | |||
public ScheduledExecutorService getExecutor() { | |||
return executor; | |||
} | |||
/** @return clock to obtain timestamps from. */ | |||
/** | |||
* Get clock to obtain timestamps from. | |||
* | |||
* @return clock to obtain timestamps from. | |||
*/ | |||
public MonotonicClock getClock() { | |||
return clock; | |||
} | |||
/** | |||
* Get how long the leader will wait for the {@link #getClock()}'s | |||
* {@code ProposedTimestamp} used in commits proposed to the RefTree graph | |||
* ({@link #getTxnAccepted()}) | |||
* | |||
* @return how long the leader will wait for the {@link #getClock()}'s | |||
* {@code ProposedTimestamp} used in commits proposed to the RefTree | |||
* graph ({@link #getTxnAccepted()}). Defaults to 5 seconds. | |||
@@ -145,8 +164,12 @@ public class KetchSystem { | |||
} | |||
/** | |||
* @return true if elections should require monotonically increasing commit | |||
* timestamps. This requires a very good {@link MonotonicClock}. | |||
* Whether elections should require monotonically increasing commit | |||
* timestamps | |||
* | |||
* @return {@code true} if elections should require monotonically increasing | |||
* commit timestamps. This requires a very good | |||
* {@link org.eclipse.jgit.util.time.MonotonicClock}. | |||
*/ | |||
public boolean requireMonotonicLeaderElections() { | |||
return false; | |||
@@ -161,22 +184,36 @@ public class KetchSystem { | |||
return txnNamespace; | |||
} | |||
/** @return name of the accepted RefTree graph. */ | |||
/** | |||
* Get name of the accepted RefTree graph. | |||
* | |||
* @return name of the accepted RefTree graph. | |||
*/ | |||
public String getTxnAccepted() { | |||
return txnAccepted; | |||
} | |||
/** @return name of the committed RefTree graph. */ | |||
/** | |||
* Get name of the committed RefTree graph. | |||
* | |||
* @return name of the committed RefTree graph. | |||
*/ | |||
public String getTxnCommitted() { | |||
return txnCommitted; | |||
} | |||
/** @return prefix for staged objects, e.g. {@code "refs/txn/stage/"}. */ | |||
/** | |||
* Get prefix for staged objects, e.g. {@code "refs/txn/stage/"}. | |||
* | |||
* @return prefix for staged objects, e.g. {@code "refs/txn/stage/"}. | |||
*/ | |||
public String getTxnStage() { | |||
return txnStage; | |||
} | |||
/** | |||
* Create new committer {@code PersonIdent} for ketch system | |||
* | |||
* @param time | |||
* timestamp for the committer. | |||
* @return identity line for the committer header of a RefTreeGraph. | |||
@@ -220,7 +257,7 @@ public class KetchSystem { | |||
* @param repo | |||
* local repository stored by the leader. | |||
* @return leader instance. | |||
* @throws URISyntaxException | |||
* @throws java.net.URISyntaxException | |||
* a follower configuration contains an unsupported URI. | |||
*/ | |||
public KetchLeader createLeader(final Repository repo) | |||
@@ -246,7 +283,7 @@ public class KetchSystem { | |||
* @param repo | |||
* repository to get the replicas of. | |||
* @return collection of replicas for the specified repository. | |||
* @throws URISyntaxException | |||
* @throws java.net.URISyntaxException | |||
* a configured URI is invalid. | |||
*/ | |||
protected List<KetchReplica> createReplicas(KetchLeader leader, |
@@ -46,9 +46,15 @@ package org.eclipse.jgit.internal.ketch; | |||
import org.eclipse.jgit.nls.NLS; | |||
import org.eclipse.jgit.nls.TranslationBundle; | |||
/** Translation bundle for the Ketch implementation. */ | |||
/** | |||
* Translation bundle for the Ketch implementation. | |||
*/ | |||
public class KetchText extends TranslationBundle { | |||
/** @return instance of this translation bundle. */ | |||
/** | |||
* Get an instance of this translation bundle. | |||
* | |||
* @return instance of this translation bundle. | |||
*/ | |||
public static KetchText get() { | |||
return NLS.getBundleFor(KetchText.class); | |||
} |
@@ -86,6 +86,7 @@ class LagCheck implements AutoCloseable { | |||
rw.setRetainBody(false); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public void close() { | |||
if (rw != null) { |
@@ -53,7 +53,9 @@ import java.util.List; | |||
import org.eclipse.jgit.annotations.Nullable; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
/** A snapshot of a leader and its view of the world. */ | |||
/** | |||
* A snapshot of a leader and its view of the world. | |||
*/ | |||
public class LeaderSnapshot { | |||
final List<ReplicaSnapshot> replicas = new ArrayList<>(); | |||
KetchLeader.State state; | |||
@@ -65,17 +67,28 @@ public class LeaderSnapshot { | |||
LeaderSnapshot() { | |||
} | |||
/** @return unmodifiable view of configured replicas. */ | |||
/** | |||
* Get unmodifiable view of configured replicas. | |||
* | |||
* @return unmodifiable view of configured replicas. | |||
*/ | |||
public Collection<ReplicaSnapshot> getReplicas() { | |||
return Collections.unmodifiableList(replicas); | |||
} | |||
/** @return current state of the leader. */ | |||
/** | |||
* Get current state of the leader. | |||
* | |||
* @return current state of the leader. | |||
*/ | |||
public KetchLeader.State getState() { | |||
return state; | |||
} | |||
/** | |||
* Whether the leader is not running a round to reach consensus, and has no | |||
* rounds queued. | |||
* | |||
* @return {@code true} if the leader is not running a round to reach | |||
* consensus, and has no rounds queued. | |||
*/ | |||
@@ -84,14 +97,19 @@ public class LeaderSnapshot { | |||
} | |||
/** | |||
* Get term of this leader | |||
* | |||
* @return term of this leader. Valid only if {@link #getState()} is | |||
* currently {@link KetchLeader.State#LEADER}. | |||
* currently | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader.State#LEADER}. | |||
*/ | |||
public long getTerm() { | |||
return term; | |||
} | |||
/** | |||
* Get end of the leader's log | |||
* | |||
* @return end of the leader's log; null if leader hasn't started up enough | |||
* to begin its own election. | |||
*/ | |||
@@ -101,6 +119,9 @@ public class LeaderSnapshot { | |||
} | |||
/** | |||
* Get state the leader knows is committed on a majority of participant | |||
* replicas | |||
* | |||
* @return state the leader knows is committed on a majority of participant | |||
* replicas. Null until the leader instance has committed a log | |||
* index within its own term. | |||
@@ -110,6 +131,7 @@ public class LeaderSnapshot { | |||
return committedIndex; | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public String toString() { | |||
StringBuilder s = new StringBuilder(); |
@@ -67,7 +67,10 @@ import org.eclipse.jgit.transport.ReceiveCommand; | |||
import org.eclipse.jgit.util.time.MonotonicClock; | |||
import org.eclipse.jgit.util.time.ProposedTimestamp; | |||
/** Ketch replica running on the same system as the {@link KetchLeader}. */ | |||
/** | |||
* Ketch replica running on the same system as the | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader}. | |||
*/ | |||
public class LocalReplica extends KetchReplica { | |||
/** | |||
* Configure a local replica. | |||
@@ -83,6 +86,7 @@ public class LocalReplica extends KetchReplica { | |||
super(leader, name, cfg); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
protected String describeForLog() { | |||
return String.format("%s (leader)", getName()); //$NON-NLS-1$ | |||
@@ -116,6 +120,7 @@ public class LocalReplica extends KetchReplica { | |||
getSystem().getTxnCommitted())); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
protected void startPush(final ReplicaPushRequest req) { | |||
getSystem().getExecutor().execute(new Runnable() { | |||
@@ -137,6 +142,7 @@ public class LocalReplica extends KetchReplica { | |||
}); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
protected void blockingFetch(Repository repo, ReplicaFetchRequest req) | |||
throws IOException { |
@@ -54,17 +54,18 @@ import org.eclipse.jgit.lib.ObjectId; | |||
* LogIndex provides a performance optimization for Ketch, the same information | |||
* can be obtained from {@link org.eclipse.jgit.revwalk.RevWalk}. | |||
* <p> | |||
* Index values are only valid within a single {@link KetchLeader} instance | |||
* after it has won an election. By restricting scope to a single leader new | |||
* leaders do not need to traverse the entire history to determine the next | |||
* {@code index} for new proposals. This differs from Raft, where leader | |||
* election uses the log index and the term number to determine which replica | |||
* holds a sufficiently up-to-date log. Since Ketch uses Git objects for storage | |||
* of its replicated log, it keeps the term number as Raft does but uses | |||
* standard Git operations to imply the log index. | |||
* Index values are only valid within a single | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader} instance after it has won | |||
* an election. By restricting scope to a single leader new leaders do not need | |||
* to traverse the entire history to determine the next {@code index} for new | |||
* proposals. This differs from Raft, where leader election uses the log index | |||
* and the term number to determine which replica holds a sufficiently | |||
* up-to-date log. Since Ketch uses Git objects for storage of its replicated | |||
* log, it keeps the term number as Raft does but uses standard Git operations | |||
* to imply the log index. | |||
* <p> | |||
* {@link Round#runAsync(AnyObjectId)} bumps the index as each new round is | |||
* constructed. | |||
* {@link org.eclipse.jgit.internal.ketch.Round#runAsync(AnyObjectId)} bumps the | |||
* index as each new round is constructed. | |||
*/ | |||
public class LogIndex extends ObjectId { | |||
static LogIndex unknown(AnyObjectId id) { | |||
@@ -82,7 +83,11 @@ public class LogIndex extends ObjectId { | |||
return new LogIndex(id, index + 1); | |||
} | |||
/** @return index provided by the current leader instance. */ | |||
/** | |||
* Get index provided by the current leader instance. | |||
* | |||
* @return index provided by the current leader instance. | |||
*/ | |||
public long getIndex() { | |||
return index; | |||
} | |||
@@ -103,6 +108,9 @@ public class LogIndex extends ObjectId { | |||
} | |||
/** | |||
* Create string suitable for debug logging containing the log index and | |||
* abbreviated ObjectId. | |||
* | |||
* @return string suitable for debug logging containing the log index and | |||
* abbreviated ObjectId. | |||
*/ | |||
@@ -111,6 +119,7 @@ public class LogIndex extends ObjectId { | |||
return String.format("%5d/%s", index, abbreviate(6).name()); //$NON-NLS-1$ | |||
} | |||
/** {@inheritDoc} */ | |||
@SuppressWarnings("boxing") | |||
@Override | |||
public String toString() { |
@@ -78,10 +78,11 @@ import org.eclipse.jgit.util.time.ProposedTimestamp; | |||
* area under the {@code refs/txn/stage/} namespace. If the proposal succeeds | |||
* then the changes are durable and the leader can commit the proposal. | |||
* <p> | |||
* Proposals are executed by {@link KetchLeader#queueProposal(Proposal)}, which | |||
* runs them asynchronously in the background. Proposals are thread-safe futures | |||
* allowing callers to {@link #await()} for results or be notified by callback | |||
* using {@link #addListener(Runnable)}. | |||
* Proposals are executed by | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader#queueProposal(Proposal)}, | |||
* which runs them asynchronously in the background. Proposals are thread-safe | |||
* futures allowing callers to {@link #await()} for results or be notified by | |||
* callback using {@link #addListener(Runnable)}. | |||
*/ | |||
public class Proposal { | |||
/** Current state of the proposal. */ | |||
@@ -146,9 +147,9 @@ public class Proposal { | |||
* walker to assist in preparing commands. | |||
* @param cmds | |||
* list of pending commands. | |||
* @throws MissingObjectException | |||
* @throws org.eclipse.jgit.errors.MissingObjectException | |||
* newId of a command is not found locally. | |||
* @throws IOException | |||
* @throws java.io.IOException | |||
* local objects cannot be accessed. | |||
*/ | |||
public Proposal(RevWalk rw, Collection<ReceiveCommand> cmds) | |||
@@ -166,12 +167,20 @@ public class Proposal { | |||
return Collections.unmodifiableList(commands); | |||
} | |||
/** @return commands from this proposal. */ | |||
/** | |||
* Get commands from this proposal. | |||
* | |||
* @return commands from this proposal. | |||
*/ | |||
public Collection<Command> getCommands() { | |||
return commands; | |||
} | |||
/** @return optional author of the proposal. */ | |||
/** | |||
* Get optional author of the proposal. | |||
* | |||
* @return optional author of the proposal. | |||
*/ | |||
@Nullable | |||
public PersonIdent getAuthor() { | |||
return author; | |||
@@ -189,7 +198,11 @@ public class Proposal { | |||
return this; | |||
} | |||
/** @return optional message for the commit log of the RefTree. */ | |||
/** | |||
* Get optional message for the commit log of the RefTree. | |||
* | |||
* @return optional message for the commit log of the RefTree. | |||
*/ | |||
@Nullable | |||
public String getMessage() { | |||
return message; | |||
@@ -207,7 +220,11 @@ public class Proposal { | |||
return this; | |||
} | |||
/** @return optional certificate signing the references. */ | |||
/** | |||
* Get optional certificate signing the references. | |||
* | |||
* @return optional certificate signing the references. | |||
*/ | |||
@Nullable | |||
public PushCertificate getPushCertificate() { | |||
return pushCert; | |||
@@ -226,6 +243,8 @@ public class Proposal { | |||
} | |||
/** | |||
* Get timestamps that Ketch must block for. | |||
* | |||
* @return timestamps that Ketch must block for. These may have been used as | |||
* commit times inside the objects involved in the proposal. | |||
*/ | |||
@@ -240,6 +259,7 @@ public class Proposal { | |||
* Request the proposal to wait for the affected timestamps to resolve. | |||
* | |||
* @param ts | |||
* a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object. | |||
* @return {@code this}. | |||
*/ | |||
public Proposal addProposedTimestamp(ProposedTimestamp ts) { | |||
@@ -253,9 +273,11 @@ public class Proposal { | |||
/** | |||
* Add a callback to be invoked when the proposal is done. | |||
* <p> | |||
* A proposal is done when it has entered either {@link State#EXECUTED} or | |||
* {@link State#ABORTED} state. If the proposal is already done | |||
* {@code callback.run()} is immediately invoked on the caller's thread. | |||
* A proposal is done when it has entered either | |||
* {@link org.eclipse.jgit.internal.ketch.Proposal.State#EXECUTED} or | |||
* {@link org.eclipse.jgit.internal.ketch.Proposal.State#ABORTED} state. If | |||
* the proposal is already done {@code callback.run()} is immediately | |||
* invoked on the caller's thread. | |||
* | |||
* @param callback | |||
* method to run after the proposal is done. The callback may be | |||
@@ -291,12 +313,18 @@ public class Proposal { | |||
notifyState(ABORTED); | |||
} | |||
/** @return read the current state of the proposal. */ | |||
/** | |||
* Read the current state of the proposal. | |||
* | |||
* @return read the current state of the proposal. | |||
*/ | |||
public State getState() { | |||
return state.get(); | |||
} | |||
/** | |||
* Whether the proposal was attempted | |||
* | |||
* @return {@code true} if the proposal was attempted. A true value does not | |||
* mean consensus was reached, only that the proposal was considered | |||
* and will not be making any more progress beyond its current | |||
@@ -309,7 +337,7 @@ public class Proposal { | |||
/** | |||
* Wait for the proposal to be attempted and {@link #isDone()} to be true. | |||
* | |||
* @throws InterruptedException | |||
* @throws java.lang.InterruptedException | |||
* caller was interrupted before proposal executed. | |||
*/ | |||
public void await() throws InterruptedException { | |||
@@ -328,7 +356,7 @@ public class Proposal { | |||
* @param unit | |||
* unit describing the wait time. | |||
* @return true if the proposal is done; false if the method timed out. | |||
* @throws InterruptedException | |||
* @throws java.lang.InterruptedException | |||
* caller was interrupted before proposal executed. | |||
*/ | |||
public boolean await(long wait, TimeUnit unit) throws InterruptedException { | |||
@@ -351,7 +379,7 @@ public class Proposal { | |||
* @param unit | |||
* unit describing the wait time. | |||
* @return true if the proposal exited the state; false on time out. | |||
* @throws InterruptedException | |||
* @throws java.lang.InterruptedException | |||
* caller was interrupted before proposal executed. | |||
*/ | |||
public boolean awaitStateChange(State notIn, long wait, TimeUnit unit) | |||
@@ -378,6 +406,7 @@ public class Proposal { | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public String toString() { | |||
StringBuilder s = new StringBuilder(); |
@@ -80,7 +80,8 @@ import org.eclipse.jgit.transport.URIish; | |||
/** | |||
* Representation of a Git repository on a remote replica system. | |||
* <p> | |||
* {@link KetchLeader} will contact the replica using the Git wire protocol. | |||
* {@link org.eclipse.jgit.internal.ketch.KetchLeader} will contact the replica | |||
* using the Git wire protocol. | |||
* <p> | |||
* The remote replica may be fully Ketch-aware, or a standard Git server. | |||
*/ | |||
@@ -110,22 +111,32 @@ public class RemoteGitReplica extends KetchReplica { | |||
this.remoteConfig = rc; | |||
} | |||
/** @return URI to contact the remote peer repository. */ | |||
/** | |||
* Get URI to contact the remote peer repository. | |||
* | |||
* @return URI to contact the remote peer repository. | |||
*/ | |||
public URIish getURI() { | |||
return uri; | |||
} | |||
/** @return optional configuration describing how to contact the peer. */ | |||
/** | |||
* Get optional configuration describing how to contact the peer. | |||
* | |||
* @return optional configuration describing how to contact the peer. | |||
*/ | |||
@Nullable | |||
protected RemoteConfig getRemoteConfig() { | |||
return remoteConfig; | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
protected String describeForLog() { | |||
return String.format("%s @ %s", getName(), getURI()); //$NON-NLS-1$ | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
protected void startPush(final ReplicaPushRequest req) { | |||
getSystem().getExecutor().execute(new Runnable() { | |||
@@ -240,6 +251,7 @@ public class RemoteGitReplica extends KetchReplica { | |||
ReceiveCommand.abort(tmp); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
protected void blockingFetch(Repository repo, ReplicaFetchRequest req) | |||
throws NotSupportedException, TransportException { |
@@ -65,7 +65,9 @@ import org.eclipse.jgit.internal.ketch.KetchReplica.CommitSpeed; | |||
import org.eclipse.jgit.internal.ketch.KetchReplica.Participation; | |||
import org.eclipse.jgit.lib.Config; | |||
/** Configures a {@link KetchReplica}. */ | |||
/** | |||
* Configures a {@link org.eclipse.jgit.internal.ketch.KetchReplica}. | |||
*/ | |||
public class ReplicaConfig { | |||
/** | |||
* Read a configuration from a config block. | |||
@@ -86,17 +88,29 @@ public class ReplicaConfig { | |||
private long minRetry = SECONDS.toMillis(5); | |||
private long maxRetry = MINUTES.toMillis(1); | |||
/** @return participation of the replica in the system. */ | |||
/** | |||
* Get participation of the replica in the system. | |||
* | |||
* @return participation of the replica in the system. | |||
*/ | |||
public Participation getParticipation() { | |||
return participation; | |||
} | |||
/** @return how Ketch should apply committed changes. */ | |||
/** | |||
* Get how Ketch should apply committed changes. | |||
* | |||
* @return how Ketch should apply committed changes. | |||
*/ | |||
public CommitMethod getCommitMethod() { | |||
return commitMethod; | |||
} | |||
/** @return how quickly should Ketch commit. */ | |||
/** | |||
* Get how quickly should Ketch commit. | |||
* | |||
* @return how quickly should Ketch commit. | |||
*/ | |||
public CommitSpeed getCommitSpeed() { | |||
return commitSpeed; | |||
} |
@@ -50,7 +50,9 @@ import org.eclipse.jgit.annotations.Nullable; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.Ref; | |||
/** A fetch request to obtain objects from a replica, and its result. */ | |||
/** | |||
* A fetch request to obtain objects from a replica, and its result. | |||
*/ | |||
public class ReplicaFetchRequest { | |||
private final Set<String> wantRefs; | |||
private final Set<ObjectId> wantObjects; | |||
@@ -70,23 +72,37 @@ public class ReplicaFetchRequest { | |||
this.wantObjects = wantObjects; | |||
} | |||
/** @return references to be fetched. */ | |||
/** | |||
* Get references to be fetched. | |||
* | |||
* @return references to be fetched. | |||
*/ | |||
public Set<String> getWantRefs() { | |||
return wantRefs; | |||
} | |||
/** @return objects to be fetched. */ | |||
/** | |||
* Get objects to be fetched. | |||
* | |||
* @return objects to be fetched. | |||
*/ | |||
public Set<ObjectId> getWantObjects() { | |||
return wantObjects; | |||
} | |||
/** @return remote references, usually from the advertisement. */ | |||
/** | |||
* Get remote references, usually from the advertisement. | |||
* | |||
* @return remote references, usually from the advertisement. | |||
*/ | |||
@Nullable | |||
public Map<String, Ref> getRefs() { | |||
return refs; | |||
} | |||
/** | |||
* Set references observed from the replica. | |||
* | |||
* @param refs | |||
* references observed from the replica. | |||
*/ |
@@ -54,8 +54,8 @@ import org.eclipse.jgit.transport.ReceiveCommand; | |||
/** | |||
* A push request sending objects to a replica, and its result. | |||
* <p> | |||
* Implementors of {@link KetchReplica} must populate the command result fields, | |||
* {@link #setRefs(Map)}, and call one of | |||
* Implementors of {@link org.eclipse.jgit.internal.ketch.KetchReplica} must | |||
* populate the command result fields, {@link #setRefs(Map)}, and call one of | |||
* {@link #setException(Repository, Throwable)} or {@link #done(Repository)} to | |||
* finish processing. | |||
*/ | |||
@@ -80,18 +80,28 @@ public class ReplicaPushRequest { | |||
this.commands = commands; | |||
} | |||
/** @return commands to be executed, and their results. */ | |||
/** | |||
* Get commands to be executed, and their results. | |||
* | |||
* @return commands to be executed, and their results. | |||
*/ | |||
public Collection<ReceiveCommand> getCommands() { | |||
return commands; | |||
} | |||
/** @return remote references, usually from the advertisement. */ | |||
/** | |||
* Get remote references, usually from the advertisement. | |||
* | |||
* @return remote references, usually from the advertisement. | |||
*/ | |||
@Nullable | |||
public Map<String, Ref> getRefs() { | |||
return refs; | |||
} | |||
/** | |||
* Set references observed from the replica. | |||
* | |||
* @param refs | |||
* references observed from the replica. | |||
*/ | |||
@@ -99,7 +109,11 @@ public class ReplicaPushRequest { | |||
this.refs = refs; | |||
} | |||
/** @return exception thrown, if any. */ | |||
/** | |||
* Get exception thrown, if any. | |||
* | |||
* @return exception thrown, if any. | |||
*/ | |||
@Nullable | |||
public Throwable getException() { | |||
return exception; |
@@ -65,31 +65,50 @@ public class ReplicaSnapshot { | |||
this.replica = replica; | |||
} | |||
/** @return the replica this snapshot describes the state of. */ | |||
/** | |||
* Get the replica this snapshot describes the state of | |||
* | |||
* @return the replica this snapshot describes the state of | |||
*/ | |||
public KetchReplica getReplica() { | |||
return replica; | |||
} | |||
/** @return current state of the replica. */ | |||
/** | |||
* Get current state of the replica | |||
* | |||
* @return current state of the replica | |||
*/ | |||
public KetchReplica.State getState() { | |||
return state; | |||
} | |||
/** @return last known Git commit at {@code refs/txn/accepted}. */ | |||
/** | |||
* Get last known Git commit at {@code refs/txn/accepted} | |||
* | |||
* @return last known Git commit at {@code refs/txn/accepted} | |||
*/ | |||
@Nullable | |||
public ObjectId getAccepted() { | |||
return accepted; | |||
} | |||
/** @return last known Git commit at {@code refs/txn/committed}. */ | |||
/** | |||
* Get last known Git commit at {@code refs/txn/committed} | |||
* | |||
* @return last known Git commit at {@code refs/txn/committed} | |||
*/ | |||
@Nullable | |||
public ObjectId getCommitted() { | |||
return committed; | |||
} | |||
/** | |||
* @return if {@link #getState()} == {@link KetchReplica.State#OFFLINE} an | |||
* optional human-readable message from the transport system | |||
* Get error message | |||
* | |||
* @return if {@link #getState()} == | |||
* {@link org.eclipse.jgit.internal.ketch.KetchReplica.State#OFFLINE} | |||
* an optional human-readable message from the transport system | |||
* explaining the failure. | |||
*/ | |||
@Nullable | |||
@@ -98,6 +117,9 @@ public class ReplicaSnapshot { | |||
} | |||
/** | |||
* Get when the leader will retry communication with the offline or lagging | |||
* replica | |||
* | |||
* @return time (usually in the future) when the leader will retry | |||
* communication with the offline or lagging replica; null if no | |||
* retry is scheduled or necessary. |
@@ -66,7 +66,9 @@ import org.eclipse.jgit.treewalk.EmptyTreeIterator; | |||
import org.eclipse.jgit.treewalk.TreeWalk; | |||
import org.eclipse.jgit.treewalk.filter.TreeFilter; | |||
/** Constructs a set of commands to stage content during a proposal. */ | |||
/** | |||
* Constructs a set of commands to stage content during a proposal. | |||
*/ | |||
public class StageBuilder { | |||
/** | |||
* Acceptable number of references to send in a single stage transaction. | |||
@@ -119,14 +121,15 @@ public class StageBuilder { | |||
* from. | |||
* @param oldTree | |||
* accepted RefTree on the replica ({@code refs/txn/accepted}). | |||
* Use {@link ObjectId#zeroId()} if the remote does not have any | |||
* ref tree, e.g. a new replica catching up. | |||
* Use {@link org.eclipse.jgit.lib.ObjectId#zeroId()} if the | |||
* remote does not have any ref tree, e.g. a new replica catching | |||
* up. | |||
* @param newTree | |||
* RefTree being sent to the replica. The trees will be compared. | |||
* @return list of commands to create {@code "refs/txn/stage/..."} | |||
* references on replicas anchoring new objects into the repository | |||
* while a transaction gains consensus. | |||
* @throws IOException | |||
* @throws java.io.IOException | |||
* {@code git} cannot be accessed to compare {@code oldTree} and | |||
* {@code newTree} to build the object set. | |||
*/ | |||
@@ -172,7 +175,7 @@ public class StageBuilder { | |||
* @return list of commands to create {@code "refs/txn/stage/..."} | |||
* references on replicas anchoring {@code newObjs} into the | |||
* repository while a transaction gains consensus. | |||
* @throws IOException | |||
* @throws java.io.IOException | |||
* {@code git} cannot be accessed to perform minification of | |||
* {@code newObjs}. | |||
*/ |