package org.openslx.bwlp.sat.thrift.cache; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.thrift.TException; /** * Class that caches an instance of a given class for 10 minutes. * If the cache expired and a fresh instance cannot be acquired, * the old instance will be returned. * * @param The class to cache */ public abstract class CacheBase { private static final Logger LOGGER = LogManager.getLogger(CacheBase.class); /** Keep cached data for 10 minutes at a time */ private static final int TIMEOUT = 600_000; private T cachedInstance = null; /** Deadline after which cache is considered stale */ private long cacheTimeout = 0; /** For timed waiting on new data */ private CountDownLatch latch = null; protected abstract T getCallback() throws TException; protected T getInternal() { boolean doFetch = false; final CountDownLatch localLatch; synchronized (this) { if (cachedInstance != null && System.currentTimeMillis() < cacheTimeout) return cachedInstance; if (latch == null) { latch = new CountDownLatch(1); doFetch = true; } localLatch = latch; // Fetch a local reference while still synchronized } // Need update if (doFetch) { // This invocation found latch == null, which means it is // responsible for triggering the actual update. // Do not use QuickTimer, as we might already be running in it // and would only deadlock ourselves new Thread(getClass().getSimpleName() + "-Update") { @Override public void run() { T freshInstance = null; try { freshInstance = getCallback(); } catch (TException e) { LOGGER.warn("Could not retrieve fresh instance of " + getClass().getSimpleName(), e); } finally { synchronized (CacheBase.this) { if (freshInstance != null) { cachedInstance = freshInstance; cacheTimeout = System.currentTimeMillis() + TIMEOUT; } latch = null; } localLatch.countDown(); } } }.start(); } // Now just wait for latch, regardless of whether we triggered the update or not boolean ok = false; try { // In case the cache is still empty, we wait for long. Otherwise, bail out after // one second so we'll rather use stale data than blocking the caller for too long. int waitTime = cachedInstance == null ? 600 : 1; ok = localLatch.await(waitTime, TimeUnit.SECONDS); } catch (InterruptedException e) { } if (!ok) { LOGGER.warn("CacheUpdate for " + getClass().getSimpleName() + " timed out, using old data."); } synchronized (this) { return cachedInstance; } } }