summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNasser Grainawi <nasser.grainawi@linaro.org>2023-04-05 15:54:42 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2023-04-05 15:54:42 +0000
commit448a84e8dd4e6caa9acbff4ca000aae5be4aee56 (patch)
tree8df7d826afabc7286bef652f7b3e540552b84e9c
parente62dc9d4219baeb9496bb536ae536a019daa74c9 (diff)
parent5f5d9793eebe39f024090ad3bb462b2a23c4e5e8 (diff)
Merge "Add a new core.usePerRequestRefCache setting" into stable-3.8
-rw-r--r--Documentation/config-gerrit.txt10
-rw-r--r--java/com/google/gerrit/server/cache/PerThreadRefDbCache.java45
-rw-r--r--java/com/google/gerrit/server/git/DynamicRefDbRepository.java83
-rw-r--r--java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java10
4 files changed, 147 insertions, 1 deletions
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 520855c34a..3726e54470 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2059,6 +2059,16 @@ Values can be specified using standard time unit abbreviations (`ms`, `sec`,
+
Default is 1 hour.
+[[core.usePerRequestRefCache]]core.usePerRequestRefCache::
++
+Use a per request (currently per request thread) ref cache. The ref
+cache uses JGit's SnapshottingRefDirectory to ensure that packed
+refs are checked and potentially read at least once per request
+(lazily) if needed. This helps reduce the overhead of checking if
+the packed-refs file is outdated.
++
+Default is true.
+
[[dashboard]]
=== Section dashboard
diff --git a/java/com/google/gerrit/server/cache/PerThreadRefDbCache.java b/java/com/google/gerrit/server/cache/PerThreadRefDbCache.java
new file mode 100644
index 0000000000..a0526e1d17
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/PerThreadRefDbCache.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.server.cache.PerThreadCache;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import org.eclipse.jgit.internal.storage.file.RefDirectory;
+import org.eclipse.jgit.lib.RefDatabase;
+
+/** A per request thread cache of RefDatabases by directory (Project). */
+public class PerThreadRefDbCache {
+ protected static final PerThreadCache.Key<PerThreadRefDbCache> REFDB_CACHE_KEY =
+ PerThreadCache.Key.create(PerThreadRefDbCache.class);
+
+ public static RefDatabase getRefDatabase(File path, RefDatabase refDb) {
+ if (PerThreadCache.get() != null) {
+ return PerThreadCache.get()
+ .get(REFDB_CACHE_KEY, PerThreadRefDbCache::new)
+ .computeIfAbsent(path, p -> ((RefDirectory) refDb).createSnapshottingRefDirectory());
+ }
+ return refDb;
+ }
+
+ protected final Map<File, RefDatabase> refDbByRefsDir = new HashMap<>();
+
+ public RefDatabase computeIfAbsent(
+ File path, Function<? super File, ? extends RefDatabase> mappingFunction) {
+ return refDbByRefsDir.computeIfAbsent(path, mappingFunction);
+ }
+}
diff --git a/java/com/google/gerrit/server/git/DynamicRefDbRepository.java b/java/com/google/gerrit/server/git/DynamicRefDbRepository.java
new file mode 100644
index 0000000000..2e81ad4c28
--- /dev/null
+++ b/java/com/google/gerrit/server/git/DynamicRefDbRepository.java
@@ -0,0 +1,83 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.function.BiFunction;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
+import org.eclipse.jgit.util.FS;
+
+/** A FileRepository with a dynamic RefDatabase supplied via a BiFunction. */
+public class DynamicRefDbRepository extends FileRepository {
+ public static class FileKey extends RepositoryCache.FileKey {
+ private BiFunction<File, RefDatabase, RefDatabase> refDatabaseSupplier;
+
+ public static FileKey lenient(
+ File directory, FS fs, BiFunction<File, RefDatabase, RefDatabase> refDatabaseSupplier) {
+ final File gitdir = resolve(directory, fs);
+ return new FileKey(gitdir != null ? gitdir : directory, fs, refDatabaseSupplier);
+ }
+
+ private final FS fs;
+
+ /**
+ * @param directory exact location of the repository.
+ * @param fs the file system abstraction which will be necessary to perform certain file system
+ * operations.
+ */
+ public FileKey(
+ File directory, FS fs, BiFunction<File, RefDatabase, RefDatabase> refDatabaseSupplier) {
+ super(canonical(directory), fs);
+ this.fs = fs;
+ this.refDatabaseSupplier = refDatabaseSupplier;
+ }
+
+ @Override
+ public Repository open(boolean mustExist) throws IOException {
+ if (mustExist && !isGitRepository(getFile(), fs))
+ throw new RepositoryNotFoundException(getFile());
+ return new DynamicRefDbRepository(getFile(), refDatabaseSupplier);
+ }
+
+ private static File canonical(File path) {
+ try {
+ return path.getCanonicalFile();
+ } catch (IOException e) {
+ return path.getAbsoluteFile();
+ }
+ }
+ }
+
+ private final File path;
+ private final BiFunction<File, RefDatabase, RefDatabase> refDatabaseSupplier;
+
+ public DynamicRefDbRepository(
+ File path, BiFunction<File, RefDatabase, RefDatabase> refDatabaseSupplier)
+ throws IOException {
+ super(path);
+ this.path = path;
+ this.refDatabaseSupplier = refDatabaseSupplier;
+ }
+
+ @Override
+ public RefDatabase getRefDatabase() {
+ return refDatabaseSupplier.apply(path, super.getRefDatabase());
+ }
+}
diff --git a/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index 57d37fae08..5cf7de52fd 100644
--- a/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -112,6 +112,7 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
private final Path basePath;
private final Map<Project.NameKey, FileKey> fileKeyByProject = new ConcurrentHashMap<>();
+ private final boolean usePerRequestRefCache;
@Inject
LocalDiskRepositoryManager(SitePaths site, @GerritServerConfig Config cfg) {
@@ -119,6 +120,7 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
if (basePath == null) {
throw new IllegalStateException("gerrit.basePath must be configured");
}
+ usePerRequestRefCache = cfg.getBoolean("core", null, "usePerRequestRefCache", true);
}
/**
@@ -168,7 +170,13 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
if (isUnreasonableName(name)) {
throw new RepositoryNotFoundException("Invalid name: " + name);
}
- FileKey location = FileKey.lenient(getBasePath(name).resolve(name.get()).toFile(), FS.DETECTED);
+ FileKey location =
+ usePerRequestRefCache
+ ? DynamicRefDbRepository.FileKey.lenient(
+ getBasePath(name).resolve(name.get()).toFile(),
+ FS.DETECTED,
+ (path, refDb) -> PerThreadRefDbCache.getRefDatabase(path, refDb))
+ : FileKey.lenient(getBasePath(name).resolve(name.get()).toFile(), FS.DETECTED);
try {
Repository repo = RepositoryCache.open(location);
fileKeyByProject.put(name, location);