Skip to content

Package: TtlCacheManager$TtlCache

TtlCacheManager$TtlCache

nameinstructionbranchcomplexitylinemethod
TtlCacheManager.TtlCache()
M: 0 C: 8
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
evict(Class)
M: 6 C: 46
88%
M: 1 C: 7
88%
M: 1 C: 4
80%
M: 1 C: 10
91%
M: 0 C: 1
100%
evict(URI)
M: 0 C: 16
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 4
100%
M: 0 C: 1
100%
get(Class, Object, Descriptor)
M: 8 C: 14
64%
M: 2 C: 2
50%
M: 2 C: 1
33%
M: 0 C: 3
100%
M: 0 C: 1
100%
put(Object, Object, Descriptor)
M: 1 C: 20
95%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 1 C: 5
83%
M: 0 C: 1
100%
static {...}
M: 0 C: 1
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
updateTimeToLive(URI)
M: 4 C: 11
73%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 0 C: 3
100%
M: 0 C: 1
100%

Coverage

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