/** * Revert a set of keys to the empty state. Will loop on this several times just in case the * memcache write fails - we don't want to leave the cache in a nasty state. */ public void empty(Iterable<Key> keys) { Map<Key, Object> updates = new HashMap<>(); for (Key key : keys) if (cacheControl.getExpirySeconds(key) != null) updates.put(key, null); this.memcacheWithRetry.putAll(updates); }
/** * Gets the Buckets for the specified keys. A bucket is built around an IdentifiableValue so you * can putAll() them without the risk of overwriting other threads' changes. Buckets also hide the * underlying details of storage for negative, empty, and uncacheable results. * * <p>Note that worst case (a cold cache), obtaining each bucket might require three memcache * requests: a getIdentifiable() which returns null, a put(EMPTY), and another getIdentifiable(). * Since there is no batch getIdentifiable(), this is *per key*. * * <p>When keys are uncacheable (per CacheControl) or the memcache is down, you will still get an * empty bucket back. The bucket will have null IdentifiableValue so we can identify it as * uncacheable. * * @return the buckets requested. Buckets will never be null. You will always get a bucket for * every key. */ public Map<Key, Bucket> getAll(Iterable<Key> keys) { Map<Key, Bucket> result = new HashMap<>(); // Sort out the ones that are uncacheable Set<Key> potentials = new HashSet<>(); for (Key key : keys) { if (cacheControl.getExpirySeconds(key) == null) result.put(key, new Bucket(key)); else potentials.add(key); } Map<Key, IdentifiableValue> ivs; try { ivs = this.memcache.getIdentifiables(potentials); } catch (Exception ex) { // This should really only be a problem if the serialization format for an Entity changes, // or someone put a badly-serializing object in the cache underneath us. log.log(Level.WARNING, "Error obtaining cache for " + potentials, ex); ivs = new HashMap<>(); } // Figure out cold cache values Map<Key, Object> cold = new HashMap<>(); for (Key key : potentials) if (ivs.get(key) == null) cold.put(key, null); if (!cold.isEmpty()) { // The cache is cold for those values, so start them out with nulls that we can make an IV for this.memcache.putAll(cold); try { Map<Key, IdentifiableValue> ivs2 = this.memcache.getIdentifiables(cold.keySet()); ivs.putAll(ivs2); } catch (Exception ex) { // At this point we should just not worry about it, the ivs will be null and uncacheable } } // Now create the remaining buckets for (Key key : keys) { // iv might still be null, which is ok - that means uncacheable IdentifiableValue iv = ivs.get(key); Bucket buck = (iv == null) ? new Bucket(key) : new Bucket(key, iv); result.put(key, buck); if (buck.isEmpty()) this.stats.recordMiss(buck.getKey()); else this.stats.recordHit(buck.getKey()); } return result; }
/** * Put buckets in the cache, checking for cacheability and collisions. * * @return the set of keys that were *successfully* put without collision */ private Set<Key> cachePutIfUntouched(Iterable<Bucket> buckets) { Map<Key, CasValues> payload = new HashMap<>(); for (Bucket buck : buckets) { if (!buck.isCacheable()) continue; Integer expirySeconds = cacheControl.getExpirySeconds(buck.getKey()); if (expirySeconds == null) continue; Expiration expiration = expirySeconds == 0 ? null : Expiration.byDeltaSeconds(expirySeconds); payload.put(buck.getKey(), new CasValues(buck.iv, buck.getNextToStore(), expiration)); } return this.memcache.putIfUntouched(payload); }