Package: TtlCacheManager
TtlCacheManager
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
TtlCacheManager(Map) |
|
|
|
|
|
||||||||||||||||||||
acquireReadLock() |
|
|
|
|
|
||||||||||||||||||||
acquireWriteLock() |
|
|
|
|
|
||||||||||||||||||||
add(Object, Object, Descriptor) |
|
|
|
|
|
||||||||||||||||||||
close() |
|
|
|
|
|
||||||||||||||||||||
contains(Class, Object, Descriptor) |
|
|
|
|
|
||||||||||||||||||||
evict(Class) |
|
|
|
|
|
||||||||||||||||||||
evict(Class, Object, URI) |
|
|
|
|
|
||||||||||||||||||||
evict(URI) |
|
|
|
|
|
||||||||||||||||||||
evictAll() |
|
|
|
|
|
||||||||||||||||||||
evictInferredObjects() |
|
|
|
|
|
||||||||||||||||||||
get(Class, Object, Descriptor) |
|
|
|
|
|
||||||||||||||||||||
getInferredClasses() |
|
|
|
|
|
||||||||||||||||||||
initSettings(Map) |
|
|
|
|
|
||||||||||||||||||||
releaseCache() |
|
|
|
|
|
||||||||||||||||||||
releaseReadLock() |
|
|
|
|
|
||||||||||||||||||||
releaseWriteLock() |
|
|
|
|
|
||||||||||||||||||||
setInferredClasses(Set) |
|
|
|
|
|
||||||||||||||||||||
static {...} |
|
|
|
|
|
Coverage
1: /*
2: * JOPA
3: * Copyright (C) 2023 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.sessions.cache;
19:
20: import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties;
21: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
22: import cz.cvut.kbss.jopa.sessions.CacheManager;
23: import cz.cvut.kbss.jopa.utils.ErrorUtils;
24: import org.slf4j.Logger;
25: import org.slf4j.LoggerFactory;
26:
27: import java.net.URI;
28: import java.util.*;
29: import java.util.Map.Entry;
30: import java.util.concurrent.Executors;
31: import java.util.concurrent.Future;
32: import java.util.concurrent.ScheduledExecutorService;
33: import java.util.concurrent.TimeUnit;
34: import java.util.concurrent.locks.Lock;
35: import java.util.concurrent.locks.ReentrantReadWriteLock;
36:
37: /**
38: * Manages the second level cache shared by all persistence contexts.
39: * <p>
40: * This implementation of CacheManager uses cache-wide locking, i. e. the whole cache is locked when an entity is being
41: * put in it, no matter that only one context is affected by the change.
42: * <p>
43: * This cache is swept regularly by a dedicated thread, which removes all entries whose time-to-live (TTL) has
44: * expired.
45: */
46: public class TtlCacheManager implements CacheManager {
47:
48: private static final Logger LOG = LoggerFactory.getLogger(TtlCacheManager.class);
49:
50: /**
51: * Default time to live in millis
52: */
53: private static final long DEFAULT_TTL = 60000L;
54: // Initial delay is the sweep rate multiplied by this multiplier
55: private static final int DELAY_MULTIPLIER = 2;
56: /**
57: * Default sweep rate in millis
58: */
59: private static final long DEFAULT_SWEEP_RATE = 30000L;
60:
61: private Set<Class<?>> inferredClasses;
62:
63: private TtlCache cache;
64:
65: // Each repository can have its own lock and they could be acquired by this
66: // instance itself, no need to pass this burden to callers
67: private final Lock readLock;
68: private final Lock writeLock;
69:
70: private final ScheduledExecutorService sweeperScheduler;
71: private Future<?> sweeperFuture;
72: private long initDelay;
73: private long sweepRate;
74: private long timeToLive;
75: private volatile boolean sweepRunning;
76:
77: public TtlCacheManager(Map<String, String> properties) {
78: this.cache = new TtlCache();
79: initSettings(properties);
80: final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
81: this.readLock = lock.readLock();
82: this.writeLock = lock.writeLock();
83: this.sweeperScheduler = Executors.newSingleThreadScheduledExecutor();
84: this.sweeperFuture = sweeperScheduler.scheduleAtFixedRate(new CacheSweeper(), initDelay, sweepRate,
85: TimeUnit.MILLISECONDS);
86: }
87:
88: private void initSettings(Map<String, String> properties) {
89:• if (!properties.containsKey(JOPAPersistenceProperties.CACHE_TTL)) {
90: this.timeToLive = DEFAULT_TTL;
91: } else {
92: final String strCacheTtl = properties.get(JOPAPersistenceProperties.CACHE_TTL);
93: try {
94: // The property is in seconds, we need milliseconds
95: this.timeToLive = Long.parseLong(strCacheTtl) * 1000;
96: } catch (NumberFormatException e) {
97: LOG.warn("Unable to parse cache time to live setting value {}, using default value.", strCacheTtl);
98: this.timeToLive = DEFAULT_TTL;
99: }
100: }
101:• if (!properties.containsKey(JOPAPersistenceProperties.CACHE_SWEEP_RATE)) {
102: this.sweepRate = DEFAULT_SWEEP_RATE;
103: } else {
104: final String strSweepRate = properties
105: .get(JOPAPersistenceProperties.CACHE_SWEEP_RATE);
106: try {
107: // The property is in seconds, we need milliseconds
108: this.sweepRate = Long.parseLong(strSweepRate) * 1000;
109: } catch (NumberFormatException e) {
110: LOG.warn("Unable to parse sweep rate setting value {}, using default value.", strSweepRate);
111: this.sweepRate = DEFAULT_SWEEP_RATE;
112: }
113: }
114: this.initDelay = DELAY_MULTIPLIER * sweepRate;
115: }
116:
117: @Override
118: public void close() {
119:• if (sweeperFuture != null) {
120: LOG.debug("Stopping cache sweeper.");
121: sweeperFuture.cancel(true);
122: sweeperScheduler.shutdown();
123: }
124: this.sweeperFuture = null;
125: evictAll();
126: }
127:
128: @Override
129: public void add(Object primaryKey, Object entity, Descriptor descriptor) {
130: Objects.requireNonNull(primaryKey, ErrorUtils.getNPXMessageSupplier("primaryKey"));
131: Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity"));
132: Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor"));
133:
134: acquireWriteLock();
135: try {
136: cache.put(primaryKey, entity, descriptor);
137: } finally {
138: releaseWriteLock();
139: }
140: }
141:
142: /**
143: * Releases the live object cache.
144: */
145: private void releaseCache() {
146: acquireWriteLock();
147: try {
148: this.cache = new TtlCache();
149: } finally {
150: releaseWriteLock();
151: }
152: }
153:
154: @Override
155: public void evictInferredObjects() {
156: acquireWriteLock();
157: try {
158: getInferredClasses().forEach(cache::evict);
159: } finally {
160: releaseWriteLock();
161: }
162: }
163:
164: @Override
165: public <T> T get(Class<T> cls, Object primaryKey, Descriptor descriptor) {
166:• if (cls == null || primaryKey == null || descriptor == null) {
167: return null;
168: }
169: acquireReadLock();
170: try {
171: return cache.get(cls, primaryKey, descriptor);
172: } finally {
173: releaseReadLock();
174: }
175: }
176:
177: /**
178: * Get the set of inferred classes.
179: * <p>
180: * Inferred classes (i. e. classes with inferred attributes) are tracked separately since they require special
181: * behavior.
182: *
183: * @return Set of inferred classes
184: */
185: public Set<Class<?>> getInferredClasses() {
186:• if (inferredClasses == null) {
187: this.inferredClasses = new HashSet<>();
188: }
189: return inferredClasses;
190: }
191:
192: /**
193: * Set the inferred classes.
194: * <p>
195: * For more information about inferred classes see {@link #getInferredClasses()}.
196: *
197: * @param inferredClasses The set of inferred classes
198: */
199: @Override
200: public void setInferredClasses(Set<Class<?>> inferredClasses) {
201: this.inferredClasses = inferredClasses;
202: }
203:
204: @Override
205: public boolean contains(Class<?> cls, Object primaryKey, Descriptor descriptor) {
206:• if (cls == null || primaryKey == null || descriptor == null) {
207: return false;
208: }
209: acquireReadLock();
210: try {
211: return cache.contains(cls, primaryKey, descriptor);
212: } finally {
213: releaseReadLock();
214: }
215: }
216:
217: @Override
218: public void evict(Class<?> cls) {
219: Objects.requireNonNull(cls);
220:
221: acquireWriteLock();
222: try {
223: cache.evict(cls);
224: } finally {
225: releaseWriteLock();
226: }
227: }
228:
229: @Override
230: public void evict(Class<?> cls, Object identifier, URI context) {
231: Objects.requireNonNull(cls, ErrorUtils.getNPXMessageSupplier("cls"));
232: Objects.requireNonNull(identifier, ErrorUtils.getNPXMessageSupplier("primaryKey"));
233:
234: acquireWriteLock();
235: try {
236: cache.evict(cls, identifier, context);
237: } finally {
238: releaseWriteLock();
239: }
240:
241: }
242:
243: @Override
244: public void evict(URI context) {
245: acquireWriteLock();
246: try {
247: cache.evict(context);
248: } finally {
249: releaseWriteLock();
250: }
251: }
252:
253: @Override
254: public void evictAll() {
255: releaseCache();
256: }
257:
258: private void acquireReadLock() {
259: readLock.lock();
260: }
261:
262: private void releaseReadLock() {
263: readLock.unlock();
264: }
265:
266: private void acquireWriteLock() {
267: writeLock.lock();
268: }
269:
270: private void releaseWriteLock() {
271: writeLock.unlock();
272: }
273:
274: /**
275: * Sweeps the second level cache and removes entities with no more time to live.
276: */
277: private final class CacheSweeper implements Runnable {
278:
279: @Override
280: public void run() {
281: LOG.trace("Running cache sweep.");
282: TtlCacheManager.this.acquireWriteLock();
283: try {
284: if (TtlCacheManager.this.sweepRunning) {
285: return;
286: }
287: TtlCacheManager.this.sweepRunning = true;
288: final long currentTime = System.currentTimeMillis();
289: final List<URI> toEvict = new ArrayList<>();
290: // Mark the objects for eviction (can't evict them now, it would
291: // cause ConcurrentModificationException)
292: for (Entry<URI, Long> e : cache.ttl.entrySet()) {
293: final long lm = e.getValue();
294: if (lm + TtlCacheManager.this.timeToLive < currentTime) {
295: toEvict.add(e.getKey());
296: }
297: }
298: // Evict them
299: toEvict.forEach(TtlCacheManager.this::evict);
300: } finally {
301: TtlCacheManager.this.sweepRunning = false;
302: TtlCacheManager.this.releaseWriteLock();
303: }
304: }
305: }
306:
307: private static final class TtlCache extends EntityCache {
308:
309: private final Map<URI, Long> ttl = new HashMap<>();
310:
311: @Override
312: void put(Object identifier, Object entity, Descriptor descriptor) {
313: if (!isCacheable(descriptor)) {
314: return;
315: }
316: super.put(identifier, entity, descriptor);
317: final URI ctx = descriptor.getSingleContext().orElse(defaultContext);
318: updateTimeToLive(ctx);
319: }
320:
321: @Override
322: <T> T get(Class<T> cls, Object identifier, Descriptor descriptor) {
323: assert cls != null;
324: assert identifier != null;
325:
326: return getInternal(cls, identifier, descriptor, this::updateTimeToLive);
327: }
328:
329: private void updateTimeToLive(URI context) {
330: assert context != null;
331:
332: ttl.put(context, System.currentTimeMillis());
333: }
334:
335: @Override
336: void evict(URI context) {
337: final URI ctx = context != null ? context : defaultContext;
338: super.evict(ctx);
339: ttl.remove(context);
340: }
341:
342: @Override
343: void evict(Class<?> cls) {
344: for (Entry<URI, Map<Object, Map<Class<?>, Object>>> e : repoCache.entrySet()) {
345: final Map<Object, Map<Class<?>, Object>> m = e.getValue();
346: for (Entry<Object, Map<Class<?>, Object>> indNode : m.entrySet()) {
347: final Object instance = indNode.getValue().remove(cls);
348: if (instance != null) {
349: descriptors.remove(instance);
350: }
351: }
352: if (m.isEmpty()) {
353: ttl.remove(e.getKey());
354: }
355: }
356: }
357: }
358: }