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