Skip to content

Commit 375cfbd

Browse files
authored
Fix performance issue with GitRepository.history & startup issues (#475)
Motivation: - In a certain case, jGit's `RevWalk` can load the entire repository history, even including those unused, into memory, which may take a lot of time for a repository with a large number of commits. - Central Dogma server can delete the PID file it did not create. - Central Dogma server's default PID file path is usually unwritable by a non-super user. Modifications: - Set `false` to `RevWalk.rewriteParents` so that the excessive amount of commits are loaded into memory. - Do not delete the PID file if not created by the current process. - Change the default PID file path to less restrictive one. Result: - Fixes #472 - Central Dogma server does not delete the existing server's PID file anymore. - Central Dogma server can be launched without an explicit `-pidfile` option even if a user is not a super user.
1 parent 32f4f3d commit 375cfbd

File tree

2 files changed

+43
-19
lines changed

2 files changed

+43
-19
lines changed

server/src/main/java/com/linecorp/centraldogma/server/Main.java

+16-8
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
*/
1616
package com.linecorp.centraldogma.server;
1717

18+
import static com.google.common.base.MoreObjects.firstNonNull;
19+
1820
import java.io.File;
1921
import java.io.IOException;
2022
import java.nio.file.AtomicMoveNotSupportedException;
2123
import java.nio.file.Files;
2224
import java.nio.file.Path;
2325
import java.nio.file.StandardCopyOption;
24-
import java.util.Optional;
2526

2627
import javax.annotation.Nullable;
2728

@@ -57,14 +58,18 @@ enum State {
5758
File.separatorChar + "conf" +
5859
File.separatorChar + "dogma.json");
5960

61+
private static final File DEFAULT_PID_FILE =
62+
new File(System.getProperty("user.dir", ".") +
63+
File.separatorChar + "dogma.pid");
64+
6065
@Nullable
6166
@Parameter(names = "-config", description = "The path to the config file", converter = FileConverter.class)
6267
private File configFile;
6368

6469
@Nullable
6570
@Parameter(names = "-pidfile",
6671
description = "The path to the file containing the pid of the server" +
67-
" (defaults to /var/run/centraldogma.pid)",
72+
" (defaults to ./dogma.pid)",
6873
converter = FileConverter.class)
6974
private File pidFile;
7075

@@ -81,6 +86,7 @@ enum State {
8186
private CentralDogma dogma;
8287
@Nullable
8388
private PidFile procIdFile;
89+
private boolean procIdFileCreated;
8490

8591
private Main(String[] args) {
8692
final JCommander commander = new JCommander(this);
@@ -90,8 +96,7 @@ private Main(String[] args) {
9096
if (help != null && help) {
9197
commander.usage();
9298
} else {
93-
procIdFile = new PidFile(Optional.ofNullable(pidFile)
94-
.orElseGet(() -> new File("/var/run/centraldogma.pid")));
99+
procIdFile = new PidFile(firstNonNull(pidFile, DEFAULT_PID_FILE));
95100
state = State.INITIALIZED;
96101
}
97102
}
@@ -126,6 +131,7 @@ synchronized void start() throws Exception {
126131
// because the state has been updated.
127132
assert procIdFile != null;
128133
procIdFile.create();
134+
procIdFileCreated = true;
129135
}
130136

131137
@Nullable
@@ -174,10 +180,12 @@ void destroy() {
174180
}
175181

176182
assert procIdFile != null;
177-
try {
178-
procIdFile.destroy();
179-
} catch (IOException e) {
180-
logger.warn("Failed to destroy the PID file:", e);
183+
if (procIdFileCreated) {
184+
try {
185+
procIdFile.destroy();
186+
} catch (IOException e) {
187+
logger.warn("Failed to destroy the PID file:", e);
188+
}
181189
}
182190

183191
state = State.DESTROYED;

server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/GitRepository.java

+27-11
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ private Map<String, Entry<?>> blockingFind(
459459
readLock();
460460
try (ObjectReader reader = jGitRepository.newObjectReader();
461461
TreeWalk treeWalk = new TreeWalk(reader);
462-
RevWalk revWalk = new RevWalk(reader)) {
462+
RevWalk revWalk = newRevWalk(reader)) {
463463

464464
// Query on a non-exist revision will return empty result.
465465
final Revision headRevision = cachedHeadRevision();
@@ -566,13 +566,12 @@ private List<Commit> blockingHistory(Revision from, Revision to, String pathPatt
566566

567567
// At this point, we are sure: from.major >= to.major
568568
readLock();
569-
try (RevWalk revWalk = new RevWalk(jGitRepository)) {
569+
try (RevWalk revWalk = newRevWalk()) {
570570
final ObjectId fromCommitId = commitIdDatabase.get(descendingRange.from());
571571
final ObjectId toCommitId = commitIdDatabase.get(descendingRange.to());
572572

573573
// Walk through the commit tree to get the corresponding commit information by given filters
574574
revWalk.setTreeFilter(AndTreeFilter.create(TreeFilter.ANY_DIFF, PathPatternFilter.of(pathPattern)));
575-
576575
revWalk.markStart(revWalk.parseCommit(fromCommitId));
577576
final RevCommit toCommit = revWalk.parseCommit(toCommitId);
578577
if (toCommit.getParentCount() != 0) {
@@ -599,7 +598,7 @@ private List<Commit> blockingHistory(Revision from, Revision to, String pathPatt
599598
// If the pathPattern does not contain "/**", the caller wants commits only with the specific path,
600599
// so skip the empty commit.
601600
if (needsLastCommit && pathPattern.contains(ALL_PATH)) {
602-
try (RevWalk tmpRevWalk = new RevWalk(jGitRepository)) {
601+
try (RevWalk tmpRevWalk = newRevWalk()) {
603602
final RevCommit lastRevCommit = tmpRevWalk.parseCommit(toCommitId);
604603
final Revision lastCommitRevision =
605604
CommitUtil.extractRevision(lastRevCommit.getFullMessage());
@@ -665,7 +664,7 @@ public CompletableFuture<Map<String, Change<?>>> diff(Revision from, Revision to
665664

666665
final RevisionRange range = normalizeNow(from, to).toAscending();
667666
readLock();
668-
try (RevWalk rw = new RevWalk(jGitRepository)) {
667+
try (RevWalk rw = newRevWalk()) {
669668
final RevTree treeA = rw.parseTree(commitIdDatabase.get(range.from()));
670669
final RevTree treeB = rw.parseTree(commitIdDatabase.get(range.to()));
671670

@@ -709,7 +708,7 @@ private Map<String, Change<?>> blockingPreviewDiff(Revision baseRevision, Iterab
709708

710709
readLock();
711710
try (ObjectReader reader = jGitRepository.newObjectReader();
712-
RevWalk revWalk = new RevWalk(reader);
711+
RevWalk revWalk = newRevWalk(reader);
713712
DiffFormatter diffFormatter = new DiffFormatter(null)) {
714713

715714
final ObjectId baseTreeId = toTree(revWalk, baseRevision);
@@ -882,7 +881,7 @@ private CommitResult commit0(@Nullable Revision prevRevision, Revision nextRevis
882881

883882
try (ObjectInserter inserter = jGitRepository.newObjectInserter();
884883
ObjectReader reader = jGitRepository.newObjectReader();
885-
RevWalk revWalk = new RevWalk(reader)) {
884+
RevWalk revWalk = newRevWalk(reader)) {
886885

887886
final ObjectId prevTreeId = prevRevision != null ? toTree(revWalk, prevRevision) : null;
888887

@@ -1025,8 +1024,8 @@ private int applyChanges(@Nullable Revision baseRevision, @Nullable ObjectId bas
10251024
}
10261025
break;
10271026
case RENAME: {
1028-
final String newPath = ((String) change.content()).substring(
1029-
1); // Strip the leading '/'.
1027+
final String newPath =
1028+
((String) change.content()).substring(1); // Strip the leading '/'.
10301029

10311030
if (dirCache.getEntry(newPath) != null) {
10321031
throw new ChangeConflictException("a file exists at the target path: " + change);
@@ -1299,7 +1298,7 @@ private Revision blockingFindLatestRevision(Revision lastKnownRevision, String p
12991298
// Convert the revisions to Git trees.
13001299
final List<DiffEntry> diffEntries;
13011300
readLock();
1302-
try (RevWalk revWalk = new RevWalk(jGitRepository)) {
1301+
try (RevWalk revWalk = newRevWalk()) {
13031302
final RevTree treeA = toTree(revWalk, range.from());
13041303
final RevTree treeB = toTree(revWalk, range.to());
13051304
diffEntries = blockingCompareTrees(treeA, treeB);
@@ -1442,7 +1441,7 @@ private Revision cachedHeadRevision() {
14421441
* Returns the current revision.
14431442
*/
14441443
private Revision uncachedHeadRevision() {
1445-
try (RevWalk revWalk = new RevWalk(jGitRepository)) {
1444+
try (RevWalk revWalk = newRevWalk()) {
14461445
final ObjectId headRevisionId = jGitRepository.resolve(R_HEADS_MASTER);
14471446
if (headRevisionId != null) {
14481447
final RevCommit revCommit = revWalk.parseCommit(headRevisionId);
@@ -1466,6 +1465,23 @@ private RevTree toTree(RevWalk revWalk, Revision revision) {
14661465
}
14671466
}
14681467

1468+
private RevWalk newRevWalk() {
1469+
final RevWalk revWalk = new RevWalk(jGitRepository);
1470+
configureRevWalk(revWalk);
1471+
return revWalk;
1472+
}
1473+
1474+
private static RevWalk newRevWalk(ObjectReader reader) {
1475+
final RevWalk revWalk = new RevWalk(reader);
1476+
configureRevWalk(revWalk);
1477+
return revWalk;
1478+
}
1479+
1480+
private static void configureRevWalk(RevWalk revWalk) {
1481+
// Disable rewriteParents because otherwise `RevWalk` will load every commit into memory.
1482+
revWalk.setRewriteParents(false);
1483+
}
1484+
14691485
private void readLock() {
14701486
rwLock.readLock().lock();
14711487
if (closePending.get() != null) {

0 commit comments

Comments
 (0)