您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

ProposalRound.java 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. /*
  2. * Copyright (C) 2016, Google Inc. and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.internal.ketch;
  11. import static org.eclipse.jgit.internal.ketch.Proposal.State.RUNNING;
  12. import java.io.IOException;
  13. import java.time.Duration;
  14. import java.util.ArrayList;
  15. import java.util.Collections;
  16. import java.util.HashMap;
  17. import java.util.HashSet;
  18. import java.util.List;
  19. import java.util.Map;
  20. import java.util.Set;
  21. import java.util.concurrent.TimeoutException;
  22. import java.util.stream.Collectors;
  23. import org.eclipse.jgit.annotations.Nullable;
  24. import org.eclipse.jgit.internal.storage.reftree.Command;
  25. import org.eclipse.jgit.internal.storage.reftree.RefTree;
  26. import org.eclipse.jgit.lib.CommitBuilder;
  27. import org.eclipse.jgit.lib.ObjectId;
  28. import org.eclipse.jgit.lib.ObjectInserter;
  29. import org.eclipse.jgit.lib.PersonIdent;
  30. import org.eclipse.jgit.lib.Ref;
  31. import org.eclipse.jgit.lib.Repository;
  32. import org.eclipse.jgit.revwalk.RevCommit;
  33. import org.eclipse.jgit.revwalk.RevWalk;
  34. import org.eclipse.jgit.transport.ReceiveCommand;
  35. import org.eclipse.jgit.util.time.ProposedTimestamp;
  36. /** A {@link Round} that aggregates and sends user {@link Proposal}s. */
  37. class ProposalRound extends Round {
  38. private final List<Proposal> todo;
  39. private RefTree queuedTree;
  40. ProposalRound(KetchLeader leader, LogIndex head, List<Proposal> todo,
  41. @Nullable RefTree tree) {
  42. super(leader, head);
  43. this.todo = todo;
  44. if (tree != null && canCombine(todo)) {
  45. this.queuedTree = tree;
  46. } else {
  47. leader.roundHoldsReferenceToRefTree = false;
  48. }
  49. }
  50. private static boolean canCombine(List<Proposal> todo) {
  51. Proposal first = todo.get(0);
  52. for (int i = 1; i < todo.size(); i++) {
  53. if (!canCombine(first, todo.get(i))) {
  54. return false;
  55. }
  56. }
  57. return true;
  58. }
  59. private static boolean canCombine(Proposal a, Proposal b) {
  60. String aMsg = nullToEmpty(a.getMessage());
  61. String bMsg = nullToEmpty(b.getMessage());
  62. return aMsg.equals(bMsg) && canCombine(a.getAuthor(), b.getAuthor());
  63. }
  64. private static String nullToEmpty(@Nullable String str) {
  65. return str != null ? str : ""; //$NON-NLS-1$
  66. }
  67. private static boolean canCombine(@Nullable PersonIdent a,
  68. @Nullable PersonIdent b) {
  69. if (a != null && b != null) {
  70. // Same name and email address. Combine timestamp as the two
  71. // proposals are running concurrently and appear together or
  72. // not at all from the point of view of an outside reader.
  73. return a.getName().equals(b.getName())
  74. && a.getEmailAddress().equals(b.getEmailAddress());
  75. }
  76. // If a and b are null, both will be the system identity.
  77. return a == null && b == null;
  78. }
  79. @Override
  80. void start() throws IOException {
  81. for (Proposal p : todo) {
  82. p.notifyState(RUNNING);
  83. }
  84. try {
  85. ObjectId id;
  86. try (Repository git = leader.openRepository();
  87. ProposedTimestamp ts = getSystem().getClock().propose()) {
  88. id = insertProposals(git, ts);
  89. blockUntil(ts);
  90. }
  91. runAsync(id);
  92. } catch (NoOp e) {
  93. for (Proposal p : todo) {
  94. p.success();
  95. }
  96. leader.lock.lock();
  97. try {
  98. leader.nextRound();
  99. } finally {
  100. leader.lock.unlock();
  101. }
  102. } catch (IOException e) {
  103. abort();
  104. throw e;
  105. }
  106. }
  107. private ObjectId insertProposals(Repository git, ProposedTimestamp ts)
  108. throws IOException, NoOp {
  109. ObjectId id;
  110. try (ObjectInserter inserter = git.newObjectInserter()) {
  111. // TODO(sop) Process signed push certificates.
  112. if (queuedTree != null) {
  113. id = insertSingleProposal(git, ts, inserter);
  114. } else {
  115. id = insertMultiProposal(git, ts, inserter);
  116. }
  117. stageCommands = makeStageList(git, inserter);
  118. inserter.flush();
  119. }
  120. return id;
  121. }
  122. private ObjectId insertSingleProposal(Repository git, ProposedTimestamp ts,
  123. ObjectInserter inserter) throws IOException, NoOp {
  124. // Fast path: tree is passed in with all proposals applied.
  125. ObjectId treeId = queuedTree.writeTree(inserter);
  126. queuedTree = null;
  127. leader.roundHoldsReferenceToRefTree = false;
  128. if (!ObjectId.zeroId().equals(acceptedOldIndex)) {
  129. try (RevWalk rw = new RevWalk(git)) {
  130. RevCommit c = rw.parseCommit(acceptedOldIndex);
  131. if (treeId.equals(c.getTree())) {
  132. throw new NoOp();
  133. }
  134. }
  135. }
  136. Proposal p = todo.get(0);
  137. CommitBuilder b = new CommitBuilder();
  138. b.setTreeId(treeId);
  139. if (!ObjectId.zeroId().equals(acceptedOldIndex)) {
  140. b.setParentId(acceptedOldIndex);
  141. }
  142. b.setCommitter(leader.getSystem().newCommitter(ts));
  143. b.setAuthor(p.getAuthor() != null ? p.getAuthor() : b.getCommitter());
  144. b.setMessage(message(p));
  145. return inserter.insert(b);
  146. }
  147. private ObjectId insertMultiProposal(Repository git, ProposedTimestamp ts,
  148. ObjectInserter inserter) throws IOException, NoOp {
  149. // The tree was not passed in, or there are multiple proposals
  150. // each needing their own commit. Reset the tree and replay each
  151. // proposal in order as individual commits.
  152. ObjectId lastIndex = acceptedOldIndex;
  153. ObjectId oldTreeId;
  154. RefTree tree;
  155. if (ObjectId.zeroId().equals(lastIndex)) {
  156. oldTreeId = ObjectId.zeroId();
  157. tree = RefTree.newEmptyTree();
  158. } else {
  159. try (RevWalk rw = new RevWalk(git)) {
  160. RevCommit c = rw.parseCommit(lastIndex);
  161. oldTreeId = c.getTree();
  162. tree = RefTree.read(rw.getObjectReader(), c.getTree());
  163. }
  164. }
  165. PersonIdent committer = leader.getSystem().newCommitter(ts);
  166. for (Proposal p : todo) {
  167. if (!tree.apply(p.getCommands())) {
  168. // This should not occur, previously during queuing the
  169. // commands were successfully applied to the pending tree.
  170. // Abort the entire round.
  171. throw new IOException(
  172. KetchText.get().queuedProposalFailedToApply);
  173. }
  174. ObjectId treeId = tree.writeTree(inserter);
  175. if (treeId.equals(oldTreeId)) {
  176. continue;
  177. }
  178. CommitBuilder b = new CommitBuilder();
  179. b.setTreeId(treeId);
  180. if (!ObjectId.zeroId().equals(lastIndex)) {
  181. b.setParentId(lastIndex);
  182. }
  183. b.setAuthor(p.getAuthor() != null ? p.getAuthor() : committer);
  184. b.setCommitter(committer);
  185. b.setMessage(message(p));
  186. lastIndex = inserter.insert(b);
  187. }
  188. if (lastIndex.equals(acceptedOldIndex)) {
  189. throw new NoOp();
  190. }
  191. return lastIndex;
  192. }
  193. private String message(Proposal p) {
  194. StringBuilder m = new StringBuilder();
  195. String msg = p.getMessage();
  196. if (msg != null && !msg.isEmpty()) {
  197. m.append(msg);
  198. while (m.length() < 2 || m.charAt(m.length() - 2) != '\n'
  199. || m.charAt(m.length() - 1) != '\n') {
  200. m.append('\n');
  201. }
  202. }
  203. m.append(KetchConstants.TERM.getName())
  204. .append(": ") //$NON-NLS-1$
  205. .append(leader.getTerm());
  206. return m.toString();
  207. }
  208. void abort() {
  209. for (Proposal p : todo) {
  210. p.abort();
  211. }
  212. }
  213. @Override
  214. void success() {
  215. for (Proposal p : todo) {
  216. p.success();
  217. }
  218. }
  219. private List<ReceiveCommand> makeStageList(Repository git,
  220. ObjectInserter inserter) throws IOException {
  221. // For each branch, collapse consecutive updates to only most recent,
  222. // avoiding sending multiple objects in a rapid fast-forward chain, or
  223. // rewritten content.
  224. Map<String, ObjectId> byRef = new HashMap<>();
  225. for (Proposal p : todo) {
  226. for (Command c : p.getCommands()) {
  227. Ref n = c.getNewRef();
  228. if (n != null && !n.isSymbolic()) {
  229. byRef.put(n.getName(), n.getObjectId());
  230. }
  231. }
  232. }
  233. if (byRef.isEmpty()) {
  234. return Collections.emptyList();
  235. }
  236. Set<ObjectId> newObjs = new HashSet<>(byRef.values());
  237. StageBuilder b = new StageBuilder(
  238. leader.getSystem().getTxnStage(),
  239. acceptedNewIndex);
  240. return b.makeStageList(newObjs, git, inserter);
  241. }
  242. private void blockUntil(ProposedTimestamp ts)
  243. throws TimeIsUncertainException {
  244. List<ProposedTimestamp> times = todo.stream()
  245. .flatMap(p -> p.getProposedTimestamps().stream())
  246. .collect(Collectors.toCollection(ArrayList::new));
  247. times.add(ts);
  248. try {
  249. Duration maxWait = getSystem().getMaxWaitForMonotonicClock();
  250. ProposedTimestamp.blockUntil(times, maxWait);
  251. } catch (InterruptedException | TimeoutException e) {
  252. throw new TimeIsUncertainException(e);
  253. }
  254. }
  255. private static class NoOp extends Exception {
  256. private static final long serialVersionUID = 1L;
  257. }
  258. }