/** * Called immediately after an element has been put into the cache and the element already existed * in the cache. This is thus an update. * * <p>The {@link net.sf.ehcache.Cache#put(net.sf.ehcache.Element)} method will block until this * method returns. * * <p>Implementers may wish to have access to the Element's fields, including value, so the * element is provided. Implementers should be careful not to modify the element. The effect of * any modifications is undefined. * * @param cache the cache emitting the notification * @param element the element which was just put into the cache. */ public final void notifyElementUpdated(final Ehcache cache, final Element element) throws CacheException { if (notAlive()) { return; } if (!replicateUpdates) { return; } if (replicateUpdatesViaCopy) { if (!element.isSerializable()) { if (LOG.isWarnEnabled()) { LOG.warn( "Object with key " + element.getObjectKey() + " is not Serializable and cannot be updated via copy."); } return; } addToReplicationQueue(new CacheEventMessage(EventMessage.PUT, cache, element, null)); } else { if (!element.isKeySerializable()) { if (LOG.isWarnEnabled()) { LOG.warn( "Object with key " + element.getObjectKey() + " does not have a Serializable key and cannot be replicated via invalidate."); } return; } addToReplicationQueue( new CacheEventMessage(EventMessage.REMOVE, cache, null, element.getKey())); } }
public void testSimultaneousPutRemove() throws InterruptedException { cacheName = SAMPLE_CACHE2; // Synced one Ehcache cache1 = manager1.getEhcache(cacheName); Ehcache cache2 = manager2.getEhcache(cacheName); Serializable key = "1"; Serializable value = new Date(); Element element = new Element(key, value); // Put cache1.put(element); Thread.currentThread().sleep(1000); cache2.remove(element.getKey()); Thread.currentThread().sleep(1000); assertNull(cache1.get(element.getKey())); manager1.clearAll(); Thread.currentThread().sleep(1000); cache2.put(element); cache2.remove(element.getKey()); Thread.currentThread().sleep(1000); cache1.put(element); Thread.currentThread().sleep(1000); assertNotNull(cache2.get(element.getKey())); manager1.clearAll(); Thread.currentThread().sleep(1000); }
private boolean evict() { Element remove = null; writeLock().lock(); try { Element evict = nextExpiredOrToEvict(null); if (evict != null) { remove = remove(evict.getKey(), hash(evict.getKey().hashCode()), null); } } finally { writeLock().unlock(); } notifyEvictionOrExpiry(remove); return remove != null; }
private boolean internalPut(final StorePutCommand putCommand) { final Element element = putCommand.getElement(); boolean isNull; if (element == null) { return true; } XATransactionContext context = getOrCreateTransactionContext(); // In case this key is currently being updated... isNull = underlyingStore.get(element.getKey()) == null; if (isNull) { isNull = context.get(element.getKey()) == null; } context.addCommand(putCommand, element); return isNull; }
@Test public void testRefreshElement() throws Exception { final IncrementingCacheEntryFactory factory = new IncrementingCacheEntryFactory(); selfPopulatingCache = new SelfPopulatingCache(cache, factory); Element e1 = selfPopulatingCache.get("key1"); Element e2 = selfPopulatingCache.get("key2"); assertEquals(2, selfPopulatingCache.getSize()); assertEquals(2, factory.getCount()); assertEquals(Integer.valueOf(1), e1.getValue()); assertEquals(Integer.valueOf(2), e2.getValue()); // full refresh selfPopulatingCache.refresh(); e1 = selfPopulatingCache.get("key1"); e2 = selfPopulatingCache.get("key2"); assertEquals(2, selfPopulatingCache.getSize()); assertEquals(4, factory.getCount()); // we cannot be sure which order key1 or key2 gets refreshed, // as the implementation makes no guarantee over the sequence // of the refresh; all we can be sure of is that between // them key1&2 must have the values 3 & 4 int e1i = ((Integer) e1.getValue()).intValue(); int e2i = ((Integer) e2.getValue()).intValue(); assertTrue(((e1i == 3) && (e2i == 4)) || ((e1i == 4) && (e2i == 3))); // single element refresh selfPopulatingCache.get("key2"); Element e2r = selfPopulatingCache.refresh("key2"); assertEquals(2, selfPopulatingCache.getSize()); assertEquals(5, factory.getCount()); assertNotNull(e2r); assertEquals("key2", e2r.getKey()); assertEquals(Integer.valueOf(5), e2r.getValue()); // additional element Element e3 = selfPopulatingCache.get("key3"); assertEquals(3, selfPopulatingCache.getSize()); assertEquals(6, factory.getCount()); assertNotNull(e3); assertEquals("key3", e3.getKey()); assertEquals(Integer.valueOf(6), e3.getValue()); // full refresh selfPopulatingCache.refresh(); assertEquals(3, selfPopulatingCache.getSize()); assertEquals(9, factory.getCount()); }
@Test public void testSimultaneousPutRemove() throws InterruptedException { LOG.info("START TEST"); // Synced one cacheName = SAMPLE_CACHE2; Ehcache cache1 = manager1.getEhcache(cacheName); Ehcache cache2 = manager2.getEhcache(cacheName); Serializable key = "1"; Serializable value = new Date(); Element element = new Element(key, value); // Put cache1.put(element); // Wait up to 2 seconds for the caches to become coherent CacheTestUtilities.waitForReplication(1, MAX_WAIT_TIME, cache2); cache2.remove(element.getKey()); // Wait up to 2 seconds for the caches to become coherent CacheTestUtilities.waitForReplication(0, MAX_WAIT_TIME, cache1); assertNull(cache1.get(element.getKey())); manager1.clearAll(); // Wait up to 2 seconds for the caches to become coherent CacheTestUtilities.waitForReplication(0, MAX_WAIT_TIME, cache2); cache2.put(element); cache2.remove(element.getKey()); // Wait up to 2 seconds for the caches to become coherent CacheTestUtilities.waitForReplication(0, MAX_WAIT_TIME, cache2); cache1.put(element); // Wait up to 2 seconds for the caches to become coherent CacheTestUtilities.waitForReplication(1, MAX_WAIT_TIME, cache2); assertNotNull(cache2.get(element.getKey())); manager1.clearAll(); LOG.info("END TEST"); }
/** {@inheritDoc} */ public Element replace(Element element) throws NullPointerException { LOG.debug("cache {} replace1 {}", cache.getName(), element); XATransactionContext context = getOrCreateTransactionContext(); Element previous = getCurrentElement(element.getKey(), context); if (previous != null) { Element oldElement = getQuietFromUnderlyingStore(element.getObjectKey()); Element elementForWrite = copyElementForWrite(element); context.addCommand(new StorePutCommand(oldElement, elementForWrite), elementForWrite); } return copyElementForRead(previous); }
/** {@inheritDoc} */ public boolean replace(Element old, Element element, ElementValueComparator comparator) throws NullPointerException, IllegalArgumentException { LOG.debug("cache {} replace2 {}", cache.getName(), element); XATransactionContext context = getOrCreateTransactionContext(); Element previous = getCurrentElement(element.getKey(), context); boolean replaced = false; if (previous != null && comparator.equals(previous, copyElementForWrite(old))) { Element oldElement = getQuietFromUnderlyingStore(element.getObjectKey()); Element elementForWrite = copyElementForWrite(element); context.addCommand(new StorePutCommand(oldElement, elementForWrite), elementForWrite); replaced = true; } return replaced; }
private void flushDependenciesOfPath( Cache depCache, Set<String> flushed, String path, boolean propageToOtherClusterNodes) { if (logger.isDebugEnabled()) { logger.debug("Flushing dependencies for path : " + path); } Element element = !flushed.contains(path) ? depCache.get(path) : null; if (element != null) { flushed.add(path); if (logger.isDebugEnabled()) { logger.debug("Flushing path : " + path); } cacheProvider.invalidate(path, propageToOtherClusterNodes); depCache.remove(element.getKey()); } }
/** {@inheritDoc} */ public Element removeElement(Element element, ElementValueComparator comparator) throws NullPointerException { LOG.debug("cache {} removeElement {}", cache.getName(), element); XATransactionContext context = getOrCreateTransactionContext(); Element previous = getCurrentElement(element.getKey(), context); Element elementForWrite = copyElementForWrite(element); if (previous != null && comparator.equals(previous, elementForWrite)) { Element oldElement = getQuietFromUnderlyingStore(element.getObjectKey()); context.addCommand( new StoreRemoveCommand(element.getObjectKey(), oldElement), elementForWrite); return copyElementForRead(previous); } return null; }
/** * Called immediately after an element has been removed. The remove method will block until this * method returns. * * <p>This implementation queues the removal notification for in order replication to peers. * * @param cache the cache emitting the notification * @param element just deleted */ public final void notifyElementRemoved(final Ehcache cache, final Element element) throws CacheException { if (!replicateRemovals) { return; } if (!element.isKeySerializable()) { if (LOG.isWarnEnabled()) { LOG.warn( "Key " + element.getObjectKey() + " is not Serializable and cannot be replicated."); } return; } addToReplicationQueue( new CacheEventMessage(EventMessage.REMOVE, cache, null, element.getKey())); }
/** * Constructor which takes an Ehcache core Element. * * <p>The {@link #mimeType} and {@link #value} are stored in the core Ehcache <code>value</code> * field using {@link net.sf.ehcache.MimeTypeByteArray} * * <p>If the MIME Type is not set, an attempt is made to set a sensible value. The rules for * setting the Mime Type are: * * <ol> * <li>If the value in element is null, the <code>mimeType</code> is set to null. * <li>If we stored the mimeType in ehcache, then <code>mimeType</code> is set with it. * <li>If no mimeType was set and the value is a <code>byte[]</code> the <code>mimeType</code> * is set to "application/octet-stream". * <li>If no mimeType was set and the value is a <code>String</code> the <code>mimeType</code> * is set to "text/plain". * </ol> * * @param element the ehcache core Element * @throws CacheException if an Exception occurred in the underlying cache. */ public Element(net.sf.ehcache.Element element) throws CacheException { key = element.getKey(); expirationDate = element.getExpirationTime(); Object ehcacheValue = element.getObjectValue(); if (ehcacheValue == null) { this.value = null; this.mimeType = null; } if (ehcacheValue instanceof MimeTypeByteArray) { // we have Mime Type data to extract mimeType = ((MimeTypeByteArray) ehcacheValue).getMimeType(); if (mimeType == null) { // Jersey is returning */* for these which I think is wrong. Reported to Paul Sandoz. mimeType = "application/octet-stream"; } this.value = ((MimeTypeByteArray) ehcacheValue).getValue(); } else if (ehcacheValue instanceof byte[]) { // already a byte[] this.value = (byte[]) ehcacheValue; mimeType = "application/octet-stream"; } else if (ehcacheValue instanceof String) { // a String such as XML this.value = ((String) ehcacheValue).getBytes(); this.mimeType = "text/plain"; } else { // A type we do not handle therefore serialize using Java Serialization MemoryEfficientByteArrayOutputStream stream = null; try { stream = MemoryEfficientByteArrayOutputStream.serialize(element.getValue()); } catch (IOException e) { throw new CacheException(e); } this.value = stream.getBytes(); this.mimeType = "application/x-java-serialized-object"; } }
@Test public void testRefreshAbsentElement() throws Exception { final IncrementingCacheEntryFactory factory = new IncrementingCacheEntryFactory(); selfPopulatingCache = new SelfPopulatingCache(cache, factory); selfPopulatingCache.get("key1"); selfPopulatingCache.get("key2"); assertEquals(2, selfPopulatingCache.getSize()); assertEquals(2, factory.getCount()); // full refresh selfPopulatingCache.refresh(); assertEquals(2, selfPopulatingCache.getSize()); assertEquals(4, factory.getCount()); // single element refresh which is not in the cache Element e3 = selfPopulatingCache.refresh("key3"); assertEquals(3, selfPopulatingCache.getSize()); assertEquals(5, factory.getCount()); assertNotNull(e3); assertEquals("key3", e3.getKey()); assertEquals(Integer.valueOf(5), e3.getValue()); }
public void put(CasAuthentication token) { Element element = new Element(token.getCredentials().toString(), token); logger.debug("Cache put: {}", element.getKey()); cache.put(element); }
protected Element put( Object key, int hash, Element value, long sizeOf, boolean onlyIfAbsent, boolean pinned, boolean fire) { Element[] evicted = new Element[MAX_EVICTION]; writeLock().lock(); try { int c = count; if (c++ > threshold) // ensure capacity rehash(); HashEntry[] tab = table; int index = hash & (tab.length - 1); HashEntry first = tab[index]; HashEntry e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; Element oldValue; if (e != null) { oldValue = e.value; if (e.value == DUMMY_PINNED_ELEMENT || !onlyIfAbsent) { poolAccessor.delete(e.sizeOf); e.value = value; e.sizeOf = sizeOf; if (oldValue == DUMMY_PINNED_ELEMENT && value != DUMMY_PINNED_ELEMENT) { --numDummyPinnedKeys; oldValue = null; } if (fire) { postInstall(key, value, e.pinned); } } } else { oldValue = null; ++modCount; tab[index] = createHashEntry(key, hash, first, value, sizeOf, pinned); count = c; // write-volatile if (fire) { postInstall(key, value, pinned); } } if (!pinned && (onlyIfAbsent && oldValue != null || !onlyIfAbsent)) { if (!isPinned(key, hash)) { this.fullyPinned = false; } if (!fullyPinned && SelectableConcurrentHashMap.this.maxSize > 0) { int runs = Math.min( MAX_EVICTION, SelectableConcurrentHashMap.this.quickSize() - (int) SelectableConcurrentHashMap.this.maxSize); while (runs-- > 0) { Element evict = nextExpiredOrToEvict(value); if (evict != null) { Element removed; while ((removed = remove(evict.getKey(), hash(evict.getKey().hashCode()), null)) == null) { evict = nextExpiredOrToEvict(value); if (evict == null) { break; } } evicted[runs] = removed; } } } } return oldValue; } finally { writeLock().unlock(); for (Element element : evicted) { notifyEvictionOrExpiry(element); } } }
/** * Called immediately after an element is <i>found</i> to be expired. The {@link * net.sf.ehcache.Cache#remove(Object)} method will block until this method returns. * * <p>As the {@link net.sf.ehcache.Element} has been expired, only what was the key of the * element is known. * * <p>Elements are checked for expiry in ehcache at the following times: * * <ul> * <li>When a get request is made * <li>When an element is spooled to the diskStore in accordance with a MemoryStore eviction * policy * <li>In the DiskStore when the expiry thread runs, which by default is {@link * net.sf.ehcache.Cache#DEFAULT_EXPIRY_THREAD_INTERVAL_SECONDS} * </ul> * * If an element is found to be expired, it is deleted and this method is notified. * * @param cache the cache emitting the notification * @param element the element that has just expired * <p>Deadlock Warning: expiry will often come from the <code>DiskStore</code> expiry * thread. It holds a lock to the DiskStorea the time the notification is sent. If the * implementation of this method calls into a synchronized <code>Cache</code> method and * that subsequently calls into DiskStore a deadlock will result. Accordingly implementers * of this method should not call back into Cache. */ public void notifyElementExpired(final Ehcache cache, final Element element) { LOG.info("Element expired : " + element); cache.put(new Element(element.getKey(), "set on notify", Boolean.TRUE, 0, 0)); counter.incrementAndGet(); }
/** * Check we get reasonable results for 2000 entries where entry 0 is accessed once increasing to * entry 1999 accessed 2000 times. * * <p>1 to 5000 population, with hit counts ranging from 1 to 500, not selecting lowest half. 5000 * tests * * <p>Samples Cost No 7 38 99.24% confidence 8 27 99.46% confidence 9 10 10 11300 4 99.92% * confidence 12 2 20 11428 0 99.99% confidence * * <p>1 to 5000 population, with hit counts ranging from 1 to 500, not selecting lowest quarter. * 5000 tests S No 10 291 94.18% confidence 20 15 30 11536 1 99.99% confidence * * <p>For those with a statistical background the branch of stats which deals with this is * hypothesis testing and the Student's T distribution. The higher your sample the greater * confidence you can have in a hypothesis, in this case whether or not the "lowest" value lies in * the bottom half or quarter of the distribution. Adding samples rapidly increases confidence but * the return from extra sampling rapidly diminishes. * * <p>Cost is not affected much by sample size. Profiling shows that it is the iteration that is * causing most of the time. If we had access to the array backing Map, all would work very fast. * Still, it is fast enough. * * <p>A 99.99% confidence interval can be achieved that the "lowest" element is actually in the * bottom quarter of the hit count distribution. * * @throws java.io.IOException Performance: With a sample size of 10: 523ms for 5000 runs = 104 ?s * per run With a sample size of 30: 628ms for 5000 runs = 125 ?s per run */ @Test public void testLowest() throws IOException { createMemoryOnlyStore(MemoryStoreEvictionPolicy.LFU, 5000); // fully populate the otherwise we just find nulls for (int i = 0; i < 5000; i++) { Element newElement = new Element("" + i, new Date()); store.put(newElement); } Element element = null; Element newElement = null; for (int i = 0; i < 10; i++) { newElement = new Element("" + i, new Date()); store.put(newElement); int j; for (j = 0; j <= i; j++) { store.get("" + i); } if (i > 0) { try { element = (Element) GET_EVICTION_TARGET.invoke( PRIMARY_FACTORY.get(store), new Object(), Integer.MAX_VALUE); } catch (Exception e) { throw new RuntimeException(e); } assertTrue(!element.equals(newElement)); assertTrue(element.getHitCount() < 2); } } int lowestQuarterNotIdentified = 0; long findTime = 0; StopWatch stopWatch = new StopWatch(); for (int i = 10; i < 5000; i++) { store.put(new Element("" + i, new Date())); int j; int maximumHitCount = 0; for (j = 0; j <= i; j += 10) { store.get("" + i); maximumHitCount++; } stopWatch.getElapsedTime(); try { element = (Element) GET_EVICTION_TARGET.invoke( PRIMARY_FACTORY.get(store), new Object(), Integer.MAX_VALUE); } catch (Exception e) { throw new RuntimeException(e); } findTime += stopWatch.getElapsedTime(); long lowest = element.getHitCount(); long bottomQuarter = (Math.round(maximumHitCount / 4.0) + 1); assertTrue(!element.equals(newElement)); if (lowest > bottomQuarter) { LOG.info( "" + element.getKey() + " hit count: " + element.getHitCount() + " bottomQuarter: " + bottomQuarter); lowestQuarterNotIdentified++; } } LOG.info("Find time: " + findTime); assertTrue(findTime < 200); LOG.info("Selections not in lowest quartile: " + lowestQuarterNotIdentified); assertTrue(lowestQuarterNotIdentified <= 10); }