Delegate to memory cache factory for non persistent caches
Restore the same contract compared to H2 based persistent caches: if
diskLimit is zero or negative, the cache is considered to be in memory
only and currently installed memory cache factory should be used.
Feature: Issue 13941
Change-Id: If911621de8ca9676d29850e449f14c684f73710e
diff --git a/BUILD b/BUILD
index 1ffe0a7..e6869e1 100644
--- a/BUILD
+++ b/BUILD
@@ -40,6 +40,7 @@
deps = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
":cache-chroniclemap__plugin",
"@chronicle-bytes//jar",
+ ":chroniclemap-test-lib",
],
)
@@ -49,5 +50,13 @@
labels = ["server"],
deps = [
":cache-chroniclemap__plugin",
+ ":chroniclemap-test-lib",
],
)
+
+java_library(
+ name = "chroniclemap-test-lib",
+ testonly = True,
+ srcs = ["src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TestPersistentCacheDef.java"],
+ deps = PLUGIN_DEPS,
+)
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheFactory.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheFactory.java
index 174ba03..fafcd42 100644
--- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheFactory.java
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheFactory.java
@@ -22,39 +22,54 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.cache.CacheBackend;
+import com.google.gerrit.server.cache.MemoryCacheFactory;
import com.google.gerrit.server.cache.PersistentCacheDef;
import com.google.gerrit.server.cache.PersistentCacheFactory;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.logging.LoggingContextAwareScheduledExecutorService;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import org.eclipse.jgit.lib.Config;
@Singleton
class ChronicleMapCacheFactory implements PersistentCacheFactory, LifecycleListener {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private final MemoryCacheFactory memCacheFactory;
+ private final Config config;
private final ChronicleMapCacheConfig.Factory configFactory;
private final MetricMaker metricMaker;
private final DynamicMap<Cache<?, ?>> cacheMap;
private final List<ChronicleMapCacheImpl<?, ?>> caches;
private final ScheduledExecutorService cleanup;
+ private final Path cacheDir;
@Inject
ChronicleMapCacheFactory(
+ MemoryCacheFactory memCacheFactory,
+ @GerritServerConfig Config cfg,
+ SitePaths site,
ChronicleMapCacheConfig.Factory configFactory,
DynamicMap<Cache<?, ?>> cacheMap,
MetricMaker metricMaker) {
+ this.memCacheFactory = memCacheFactory;
+ this.config = cfg;
this.configFactory = configFactory;
this.metricMaker = metricMaker;
this.caches = new LinkedList<>();
+ this.cacheDir = getCacheDir(site, cfg.getString("cache", null, "directory"));
this.cacheMap = cacheMap;
this.cleanup =
new LoggingContextAwareScheduledExecutorService(
@@ -66,9 +81,11 @@
.build()));
}
- @SuppressWarnings({"unchecked"})
@Override
public <K, V> Cache<K, V> build(PersistentCacheDef<K, V> in, CacheBackend backend) {
+ if (isInMemoryCache(in)) {
+ return memCacheFactory.build(in, backend);
+ }
ChronicleMapCacheConfig config =
configFactory.create(
in.name(),
@@ -77,7 +94,7 @@
in.expireAfterWrite(),
in.refreshAfterWrite(),
in.version());
- ChronicleMapCacheImpl<K, V> cache = null;
+ ChronicleMapCacheImpl<K, V> cache;
try {
cache = new ChronicleMapCacheImpl<>(in, config, null, metricMaker);
} catch (IOException e) {
@@ -89,10 +106,12 @@
return cache;
}
- @SuppressWarnings("unchecked")
@Override
public <K, V> LoadingCache<K, V> build(
PersistentCacheDef<K, V> in, CacheLoader<K, V> loader, CacheBackend backend) {
+ if (isInMemoryCache(in)) {
+ return memCacheFactory.build(in, loader, backend);
+ }
ChronicleMapCacheConfig config =
configFactory.create(
in.name(),
@@ -101,7 +120,7 @@
in.expireAfterWrite(),
in.refreshAfterWrite(),
in.version());
- ChronicleMapCacheImpl<K, V> cache = null;
+ ChronicleMapCacheImpl<K, V> cache;
try {
cache = new ChronicleMapCacheImpl<>(in, config, loader, metricMaker);
} catch (IOException e) {
@@ -125,6 +144,11 @@
}
}
+ private <K, V> boolean isInMemoryCache(PersistentCacheDef<K, V> in) {
+ return cacheDir == null
+ || config.getLong("cache", in.configKey(), "diskLimit", in.diskLimit()) <= 0;
+ }
+
@Override
public void start() {
for (ChronicleMapCacheImpl<?, ?> cache : caches) {
@@ -136,4 +160,25 @@
public void stop() {
cleanup.shutdownNow();
}
+
+ private static Path getCacheDir(SitePaths site, String name) {
+ if (name == null) {
+ return null;
+ }
+ Path loc = site.resolve(name);
+ if (!Files.exists(loc)) {
+ try {
+ Files.createDirectories(loc);
+ } catch (IOException e) {
+ logger.atWarning().log("Can't create disk cache: %s", loc.toAbsolutePath());
+ return null;
+ }
+ }
+ if (!Files.isWritable(loc)) {
+ logger.atWarning().log("Can't write to disk cache: %s", loc.toAbsolutePath());
+ return null;
+ }
+ logger.atInfo().log("Enabling disk cache %s", loc.toAbsolutePath());
+ return loc;
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java
index f3f6483..2a21121 100644
--- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java
@@ -84,17 +84,12 @@
mapBuilder.maxBloatFactor(config.getMaxBloatFactor());
- if (config.getPersistedFile() == null || config.getDiskLimit() < 0) {
- store = mapBuilder.create();
- } else {
- logger.atWarning().log(
- "disk storage is enabled for '%s', however chronicle-map "
- + "cannot honour the diskLimit of %s bytes, since the file size "
- + "is pre-allocated rather than being a function of the number"
- + "of entries in the cache",
- def.name(), def.diskLimit());
- store = mapBuilder.createOrRecoverPersistedTo(config.getPersistedFile());
- }
+ logger.atWarning().log(
+ "chronicle-map cannot honour the diskLimit of %s bytes for the %s "
+ + "cache, since the file size is pre-allocated rather than being "
+ + "a function of the number of entries in the cache",
+ def.diskLimit(), def.name());
+ store = mapBuilder.createOrRecoverPersistedTo(config.getPersistedFile());
logger.atInfo().log(
"Initialized '%s'|version: %s|avgKeySize: %s bytes|avgValueSize:"
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheIT.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheIT.java
index 69f0845..df6fc38 100644
--- a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheIT.java
+++ b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheIT.java
@@ -16,15 +16,19 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
+import com.google.common.cache.Cache;
import com.google.common.truth.Truth8;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.accounts.AccountInput;
+import com.google.gerrit.server.cache.CacheBackend;
import com.google.gerrit.server.cache.PersistentCacheFactory;
import com.google.inject.Inject;
import org.junit.Test;
+@UseLocalDisk
public class ChronicleMapCacheIT extends AbstractDaemonTest {
@Inject PersistentCacheFactory persistentCacheFactory;
@@ -40,6 +44,25 @@
}
@Test
+ public void shouldBuildInMemoryCacheWhenDiskLimitIsNegative() {
+ final int negativeDiskLimit = -1;
+ final Cache<String, String> cache =
+ persistentCacheFactory.build(
+ new TestPersistentCacheDef("foo", negativeDiskLimit), CacheBackend.CAFFEINE);
+
+ assertThat(cache.getClass().getSimpleName()).isEqualTo("CaffeinatedGuavaCache");
+ }
+
+ @Test
+ public void shouldBuildInMemoryCacheWhenDiskLimitIsPositive() {
+ final int positiveDiskLimit = 1024;
+ assertThat(
+ persistentCacheFactory.build(
+ new TestPersistentCacheDef("foo", positiveDiskLimit), CacheBackend.CAFFEINE))
+ .isInstanceOf(ChronicleMapCacheImpl.class);
+ }
+
+ @Test
public void shouldCacheNewProject() throws Exception {
String newProjectName = name("newProject");
RestResponse r = adminRestSession.put("/projects/" + newProjectName);
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java
index e8e6236..844212a 100644
--- a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java
+++ b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java
@@ -19,22 +19,17 @@
import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.Weigher;
import com.google.gerrit.acceptance.WaitUtil;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.metrics.DisabledMetricMaker;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
-import com.google.gerrit.server.cache.PersistentCacheDef;
-import com.google.gerrit.server.cache.serialize.CacheSerializer;
import com.google.gerrit.server.cache.serialize.StringCacheSerializer;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
-import com.google.inject.TypeLiteral;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
@@ -67,6 +62,9 @@
new FileBasedConfig(
sitePaths.resolve("etc").resolve("gerrit.config").toFile(), FS.DETECTED);
gerritConfig.load();
+ String cacheDirectory = "cache-dir";
+ gerritConfig.setString("cache", null, "directory", cacheDirectory);
+ gerritConfig.save();
setupMetrics();
}
@@ -547,89 +545,4 @@
assertWithMessage(name).that(gauge).isNotNull();
return gauge;
}
-
- public static class TestPersistentCacheDef implements PersistentCacheDef<String, String> {
-
- private final String loadedValue;
-
- TestPersistentCacheDef(String loadedValue) {
-
- this.loadedValue = loadedValue;
- }
-
- @Override
- public long diskLimit() {
- return 0;
- }
-
- @Override
- public int version() {
- return 0;
- }
-
- @Override
- public CacheSerializer<String> keySerializer() {
- return StringCacheSerializer.INSTANCE;
- }
-
- @Override
- public CacheSerializer<String> valueSerializer() {
- return StringCacheSerializer.INSTANCE;
- }
-
- @Override
- public String name() {
- return loadedValue;
- }
-
- @Override
- public String configKey() {
- return name();
- }
-
- @Override
- public TypeLiteral<String> keyType() {
- return new TypeLiteral<String>() {};
- }
-
- @Override
- public TypeLiteral<String> valueType() {
- return new TypeLiteral<String>() {};
- }
-
- @Override
- public long maximumWeight() {
- return 0;
- }
-
- @Override
- public Duration expireAfterWrite() {
- return Duration.ZERO;
- }
-
- @Override
- public Duration expireFromMemoryAfterAccess() {
- return Duration.ZERO;
- }
-
- @Override
- public Duration refreshAfterWrite() {
- return Duration.ZERO;
- }
-
- @Override
- public Weigher<String, String> weigher() {
- return (s, s2) -> 0;
- }
-
- @Override
- public CacheLoader<String, String> loader() {
- return new CacheLoader<String, String>() {
- @Override
- public String load(String s) {
- return loadedValue != null ? loadedValue : UUID.randomUUID().toString();
- }
- };
- }
- }
}
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TestPersistentCacheDef.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TestPersistentCacheDef.java
new file mode 100644
index 0000000..b81baec
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TestPersistentCacheDef.java
@@ -0,0 +1,136 @@
+// Copyright (C) 2020 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.googlesource.gerrit.modules.cache.chroniclemap;
+
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.Weigher;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.server.cache.PersistentCacheDef;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.cache.serialize.StringCacheSerializer;
+import com.google.inject.TypeLiteral;
+import java.time.Duration;
+import java.util.UUID;
+
+public class TestPersistentCacheDef implements PersistentCacheDef<String, String> {
+
+ private static final Integer DEFAULT_DISK_LIMIT = 1024;
+
+ private final String loadedValue;
+ private final Duration expireAfterWrite;
+ private final Duration refreshAfterWrite;
+ private final Integer diskLimit;
+
+ public TestPersistentCacheDef(
+ String loadedValue,
+ @Nullable Duration expireAfterWrite,
+ @Nullable Duration refreshAfterWrite) {
+
+ this.loadedValue = loadedValue;
+ this.expireAfterWrite = expireAfterWrite;
+ this.refreshAfterWrite = refreshAfterWrite;
+ this.diskLimit = DEFAULT_DISK_LIMIT;
+ }
+
+ public TestPersistentCacheDef(String loadedValue, Integer diskLimit) {
+
+ this.loadedValue = loadedValue;
+ this.expireAfterWrite = null;
+ this.refreshAfterWrite = null;
+ this.diskLimit = diskLimit;
+ }
+
+ public TestPersistentCacheDef(String loadedValue) {
+
+ this.loadedValue = loadedValue;
+ this.expireAfterWrite = Duration.ZERO;
+ this.refreshAfterWrite = Duration.ZERO;
+ this.diskLimit = DEFAULT_DISK_LIMIT;
+ }
+
+ @Override
+ public long diskLimit() {
+ return diskLimit;
+ }
+
+ @Override
+ public int version() {
+ return 0;
+ }
+
+ @Override
+ public CacheSerializer<String> keySerializer() {
+ return StringCacheSerializer.INSTANCE;
+ }
+
+ @Override
+ public CacheSerializer<String> valueSerializer() {
+ return StringCacheSerializer.INSTANCE;
+ }
+
+ @Override
+ public String name() {
+ return loadedValue;
+ }
+
+ @Override
+ public String configKey() {
+ return name();
+ }
+
+ @Override
+ public TypeLiteral<String> keyType() {
+ return new TypeLiteral<String>() {};
+ }
+
+ @Override
+ public TypeLiteral<String> valueType() {
+ return new TypeLiteral<String>() {};
+ }
+
+ @Override
+ public long maximumWeight() {
+ return 0;
+ }
+
+ @Override
+ public Duration expireAfterWrite() {
+ return expireAfterWrite;
+ }
+
+ @Override
+ public Duration expireFromMemoryAfterAccess() {
+ return Duration.ZERO;
+ }
+
+ @Override
+ public Duration refreshAfterWrite() {
+ return refreshAfterWrite;
+ }
+
+ @Override
+ public Weigher<String, String> weigher() {
+ return (s, s2) -> 0;
+ }
+
+ @Override
+ public CacheLoader<String, String> loader() {
+ return new CacheLoader<String, String>() {
+ @Override
+ public String load(String s) {
+ return loadedValue != null ? loadedValue : UUID.randomUUID().toString();
+ }
+ };
+ }
+}