blob: a51fd3665c75162e1d57df9b3fcbd10a669efd42 [file] [log] [blame]
// 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.annotations.VisibleForTesting;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.cache.MemoryCacheFactory;
import com.google.gerrit.server.cache.PersistentCacheBaseFactory;
import com.google.gerrit.server.cache.PersistentCacheDef;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.logging.LoggingContextAwareExecutorService;
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.File;
import java.io.IOException;
import java.io.UncheckedIOException;
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 net.openhft.chronicle.map.ChronicleMap;
import org.eclipse.jgit.lib.Config;
@Singleton
class ChronicleMapCacheFactory extends PersistentCacheBaseFactory implements LifecycleListener {
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 LoggingContextAwareExecutorService storePersistenceExecutor;
@Inject
ChronicleMapCacheFactory(
MemoryCacheFactory memCacheFactory,
@GerritServerConfig Config cfg,
SitePaths site,
ChronicleMapCacheConfig.Factory configFactory,
DynamicMap<Cache<?, ?>> cacheMap,
MetricMaker metricMaker) {
super(memCacheFactory, cfg, site);
this.configFactory = configFactory;
this.metricMaker = metricMaker;
this.caches = new LinkedList<>();
this.cacheMap = cacheMap;
this.cleanup =
new LoggingContextAwareScheduledExecutorService(
Executors.newScheduledThreadPool(
1,
new ThreadFactoryBuilder()
.setNameFormat("ChronicleMap-Prune-%d")
.setDaemon(true)
.build()));
this.storePersistenceExecutor =
new LoggingContextAwareExecutorService(
Executors.newFixedThreadPool(
1, new ThreadFactoryBuilder().setNameFormat("ChronicleMap-Store-%d").build()));
}
@Override
public <K, V> Cache<K, V> buildImpl(PersistentCacheDef<K, V> in, long limit) {
ChronicleMapCacheConfig config =
configFactory.create(
in.configKey(),
fileName(cacheDir, in.name(), in.version()),
in.expireAfterWrite(),
in.refreshAfterWrite());
return build(in, config, metricMaker);
}
@SuppressWarnings("unchecked")
@VisibleForTesting
<K, V> Cache<K, V> build(
PersistentCacheDef<K, V> in, ChronicleMapCacheConfig config, MetricMaker metricMaker) {
ChronicleMapCacheDefProxy<K, V> def = new ChronicleMapCacheDefProxy<>(in);
ChronicleMapCacheImpl<K, V> cache;
try {
ChronicleMap<KeyWrapper<K>, TimedValue<V>> store =
ChronicleMapCacheImpl.createOrRecoverStore(in, config);
ChronicleMapCacheLoader<K, V> memLoader =
new ChronicleMapCacheLoader<>(
storePersistenceExecutor, store, config.getExpireAfterWrite());
LoadingCache<K, TimedValue<V>> mem =
(LoadingCache<K, TimedValue<V>>)
memCacheFactory.build(def, (CacheLoader<K, V>) memLoader);
cache =
new ChronicleMapCacheImpl<>(
in,
config,
metricMaker,
memLoader,
new InMemoryCacheLoadingFromStoreImpl<>(mem, false),
store);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
synchronized (caches) {
caches.add(cache);
}
return cache;
}
@Override
public <K, V> LoadingCache<K, V> buildImpl(
PersistentCacheDef<K, V> in, CacheLoader<K, V> loader, long limit) {
ChronicleMapCacheConfig config =
configFactory.create(
in.configKey(),
fileName(cacheDir, in.name(), in.version()),
in.expireAfterWrite(),
in.refreshAfterWrite());
return build(in, loader, config, metricMaker);
}
@SuppressWarnings("unchecked")
@VisibleForTesting
public <K, V> LoadingCache<K, V> build(
PersistentCacheDef<K, V> in,
CacheLoader<K, V> loader,
ChronicleMapCacheConfig config,
MetricMaker metricMaker) {
ChronicleMapCacheImpl<K, V> cache;
ChronicleMapCacheDefProxy<K, V> def = new ChronicleMapCacheDefProxy<>(in);
try {
ChronicleMap<KeyWrapper<K>, TimedValue<V>> store =
ChronicleMapCacheImpl.createOrRecoverStore(in, config);
ChronicleMapCacheLoader<K, V> memLoader =
new ChronicleMapCacheLoader<>(
storePersistenceExecutor, store, loader, config.getExpireAfterWrite());
LoadingCache<K, TimedValue<V>> mem =
(LoadingCache<K, TimedValue<V>>)
memCacheFactory.build(def, (CacheLoader<K, V>) memLoader);
cache =
new ChronicleMapCacheImpl<>(
in,
config,
metricMaker,
memLoader,
new InMemoryCacheLoadingFromStoreImpl<>(mem, true),
store);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
synchronized (caches) {
caches.add(cache);
}
return cache;
}
@Override
public void onStop(String plugin) {
synchronized (caches) {
for (Map.Entry<String, Provider<Cache<?, ?>>> entry : cacheMap.byPlugin(plugin).entrySet()) {
Cache<?, ?> cache = entry.getValue().get();
if (caches.remove(cache)) {
((ChronicleMapCacheImpl<?, ?>) cache).close();
}
}
}
}
@Override
public void start() {
for (ChronicleMapCacheImpl<?, ?> cache : caches) {
cleanup.scheduleWithFixedDelay(cache::prune, 30, 30, TimeUnit.SECONDS);
}
}
@Override
public void stop() {
cleanup.shutdownNow();
}
public static File fileName(Path cacheDir, String name, Integer version) {
return cacheDir.resolve(String.format("%s_%s.dat", name, version)).toFile();
}
protected static Path getCacheDir(SitePaths site, String name) {
return site.resolve(name);
}
}