Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.server.cache.h2;
16 :
17 : import com.google.common.cache.Cache;
18 : import com.google.common.cache.CacheLoader;
19 : import com.google.common.cache.LoadingCache;
20 : import com.google.common.flogger.FluentLogger;
21 : import com.google.common.util.concurrent.ThreadFactoryBuilder;
22 : import com.google.gerrit.extensions.events.LifecycleListener;
23 : import com.google.gerrit.extensions.registration.DynamicMap;
24 : import com.google.gerrit.server.cache.MemoryCacheFactory;
25 : import com.google.gerrit.server.cache.PersistentCacheBaseFactory;
26 : import com.google.gerrit.server.cache.PersistentCacheDef;
27 : import com.google.gerrit.server.cache.h2.H2CacheImpl.SqlStore;
28 : import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
29 : import com.google.gerrit.server.config.GerritServerConfig;
30 : import com.google.gerrit.server.config.SitePaths;
31 : import com.google.gerrit.server.logging.LoggingContextAwareExecutorService;
32 : import com.google.gerrit.server.logging.LoggingContextAwareScheduledExecutorService;
33 : import com.google.inject.Inject;
34 : import com.google.inject.Provider;
35 : import com.google.inject.Singleton;
36 : import java.util.ArrayList;
37 : import java.util.List;
38 : import java.util.Map;
39 : import java.util.concurrent.ExecutorService;
40 : import java.util.concurrent.Executors;
41 : import java.util.concurrent.Future;
42 : import java.util.concurrent.ScheduledExecutorService;
43 : import java.util.concurrent.TimeUnit;
44 : import org.eclipse.jgit.lib.Config;
45 :
46 : /**
47 : * Creates persistent caches depending on gerrit.config parameters. If the cache.directory property
48 : * is unset, it will fall back to in-memory caches.
49 : */
50 : @Singleton
51 : class H2CacheFactory extends PersistentCacheBaseFactory implements LifecycleListener {
52 151 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
53 :
54 : private final List<H2CacheImpl<?, ?>> caches;
55 : private final DynamicMap<Cache<?, ?>> cacheMap;
56 : private final ExecutorService executor;
57 : private final ScheduledExecutorService cleanup;
58 : private final long h2CacheSize;
59 : private final boolean h2AutoServer;
60 :
61 : @Inject
62 : H2CacheFactory(
63 : MemoryCacheFactory memCacheFactory,
64 : @GerritServerConfig Config cfg,
65 : SitePaths site,
66 : DynamicMap<Cache<?, ?>> cacheMap) {
67 151 : super(memCacheFactory, cfg, site);
68 151 : h2CacheSize = cfg.getLong("cache", null, "h2CacheSize", -1);
69 151 : h2AutoServer = cfg.getBoolean("cache", null, "h2AutoServer", false);
70 151 : caches = new ArrayList<>();
71 151 : this.cacheMap = cacheMap;
72 :
73 151 : if (diskEnabled) {
74 15 : executor =
75 : new LoggingContextAwareExecutorService(
76 15 : Executors.newFixedThreadPool(
77 15 : 1, new ThreadFactoryBuilder().setNameFormat("DiskCache-Store-%d").build()));
78 15 : cleanup =
79 : new LoggingContextAwareScheduledExecutorService(
80 15 : Executors.newScheduledThreadPool(
81 : 1,
82 : new ThreadFactoryBuilder()
83 15 : .setNameFormat("DiskCache-Prune-%d")
84 15 : .setDaemon(true)
85 15 : .build()));
86 : } else {
87 145 : executor = null;
88 145 : cleanup = null;
89 : }
90 151 : }
91 :
92 : @Override
93 : public void start() {
94 150 : if (executor != null) {
95 15 : for (H2CacheImpl<?, ?> cache : caches) {
96 15 : executor.execute(cache::start);
97 : @SuppressWarnings("unused")
98 15 : Future<?> possiblyIgnoredError =
99 15 : cleanup.schedule(() -> cache.prune(cleanup), 30, TimeUnit.SECONDS);
100 15 : }
101 : }
102 150 : }
103 :
104 : @Override
105 : public void stop() {
106 150 : if (executor != null) {
107 : try {
108 15 : cleanup.shutdownNow();
109 :
110 15 : List<Runnable> pending = executor.shutdownNow();
111 15 : if (executor.awaitTermination(15, TimeUnit.MINUTES)) {
112 15 : if (pending != null && !pending.isEmpty()) {
113 15 : logger.atInfo().log("Finishing %d disk cache updates", pending.size());
114 15 : for (Runnable update : pending) {
115 15 : update.run();
116 15 : }
117 : }
118 : } else {
119 0 : logger.atInfo().log("Timeout waiting for disk cache to close");
120 : }
121 0 : } catch (InterruptedException e) {
122 0 : logger.atWarning().log("Interrupted waiting for disk cache to shutdown");
123 15 : }
124 : }
125 150 : synchronized (caches) {
126 150 : for (H2CacheImpl<?, ?> cache : caches) {
127 15 : cache.stop();
128 15 : }
129 150 : }
130 150 : }
131 :
132 : @SuppressWarnings({"unchecked"})
133 : @Override
134 : public <K, V> Cache<K, V> buildImpl(PersistentCacheDef<K, V> in, long limit) {
135 15 : H2CacheDefProxy<K, V> def = new H2CacheDefProxy<>(in);
136 15 : SqlStore<K, V> store = newSqlStore(def, limit);
137 15 : H2CacheImpl<K, V> cache =
138 : new H2CacheImpl<>(
139 15 : executor, store, def.keyType(), (Cache<K, ValueHolder<V>>) memCacheFactory.build(def));
140 15 : synchronized (caches) {
141 15 : caches.add(cache);
142 15 : }
143 15 : return cache;
144 : }
145 :
146 : @SuppressWarnings({"unchecked"})
147 : @Override
148 : public <K, V> LoadingCache<K, V> buildImpl(
149 : PersistentCacheDef<K, V> in, CacheLoader<K, V> loader, long limit) {
150 15 : H2CacheDefProxy<K, V> def = new H2CacheDefProxy<>(in);
151 15 : SqlStore<K, V> store = newSqlStore(def, limit);
152 15 : Cache<K, ValueHolder<V>> mem =
153 : (Cache<K, ValueHolder<V>>)
154 15 : memCacheFactory.build(
155 : def, (CacheLoader<K, V>) new H2CacheImpl.Loader<>(executor, store, loader));
156 15 : H2CacheImpl<K, V> cache = new H2CacheImpl<>(executor, store, def.keyType(), mem);
157 15 : synchronized (caches) {
158 15 : caches.add(cache);
159 15 : }
160 15 : return cache;
161 : }
162 :
163 : @Override
164 : public void onStop(String plugin) {
165 3 : synchronized (caches) {
166 3 : for (Map.Entry<String, Provider<Cache<?, ?>>> entry : cacheMap.byPlugin(plugin).entrySet()) {
167 0 : Cache<?, ?> cache = entry.getValue().get();
168 0 : if (caches.remove(cache)) {
169 0 : ((H2CacheImpl<?, ?>) cache).stop();
170 : }
171 0 : }
172 3 : }
173 3 : }
174 :
175 : private <V, K> SqlStore<K, V> newSqlStore(PersistentCacheDef<K, V> def, long maxSize) {
176 15 : StringBuilder url = new StringBuilder();
177 15 : url.append("jdbc:h2:").append(cacheDir.resolve(def.name()).toUri());
178 15 : if (h2CacheSize >= 0) {
179 0 : url.append(";CACHE_SIZE=");
180 : // H2 CACHE_SIZE is always given in KB
181 0 : url.append(h2CacheSize / 1024);
182 : }
183 15 : if (h2AutoServer) {
184 0 : url.append(";AUTO_SERVER=TRUE");
185 : }
186 15 : return new SqlStore<>(
187 15 : url.toString(),
188 15 : def.keyType(),
189 15 : def.keySerializer(),
190 15 : def.valueSerializer(),
191 15 : def.version(),
192 : maxSize,
193 15 : def.expireAfterWrite(),
194 15 : def.expireFromMemoryAfterAccess());
195 : }
196 : }
|