• Bug
  • Status: In Progress
  • 2 Major
  • Resolution:
  • ehcache-core
  • ljacomet
  • Reporter: david316
  • August 27, 2012
  • 1
  • Watchers: 9
  • February 20, 2014

Attachments

Description

Configure a cache as follows:

 <cache name="test" timeToIdleSeconds="20" timeToLiveSeconds="30" 
        memoryStoreEvictionPolicy="LFU" 
        copyOnRead="true" copyOnWrite="true" 
        statistics="true" logging="true" />

Create a simple Java class that puts an element in cache and then gets the element from cache every 5s. Expected behaviour is for element to expire after 30s (timeToLiveSeconds) and return null. Actual behaviour is element expiring after 20s (timeToIdleSeconds) even though it is not idle as it is being read every 5s.

Change the above cache configuration to copyOnRead=”false” and repeat the test. It then behaves as expected.

The issue only occurs when copyOnRead=”true”

Comments

David Mills 2012-08-27

I’ve attached sample ehcache config and test class to reproduce issue.

James House 2012-08-27

confirmed.

Chris Schanck 2012-08-27

Sample unit test: @Test public void testEHC967() {

    String key = "key";

    CacheManager cm = CacheManager.create();

    // original example had these true: copyOnWrite(true).statistics(true).logging(true)
    CacheConfiguration config = new CacheConfiguration().name("copyOnReadTest").timeToIdleSeconds(20).timeToLiveSeconds(30)
            .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU).copyOnRead(true)
            .maxBytesLocalHeap(100, MemoryUnit.KILOBYTES);

    Cache cache = new Cache(config);
    cm.addCache(cache);

    long start = System.currentTimeMillis();
    cache.put(new Element(key, "test"));
    for (int times=0;;times++) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        Element e = cache.get(key);
        if (e != null) {
            System.out.println(MessageFormat.format("{0}ms - Creation: {1}, Access - {2}, Update: {3}", System.currentTimeMillis()
                    - start, e.getCreationTime(), e.getLastAccessTime(), e.getLastUpdateTime()));
        } else {
            System.out.println(MessageFormat.format("{0}ms - Expired", System.currentTimeMillis() - start));
            break;
        }
    }
    long millisToExpire=System.currentTimeMillis()-start;
    cm.shutdown();
    assertTrue((millisToExpire/1000) >= (config.getTimeToLiveSeconds()+1));
}

Hung Huynh 2013-12-02

The root cause is that when we copy the element for reading, we don’t update the original element lastAccessTime. So even though you keep reading the element (in this case, copying it), Ehcache doesn’t see it as an “access” and the original element eventually expires after TTI time period.

The fix is as below. Waiting for feedback from the team.

Index: ehcache-core/src/main/java/net/sf/ehcache/store/compound/ReadWriteSerializationCopyStrategy.java
===================================================================
--- ehcache-core/src/main/java/net/sf/ehcache/store/compound/ReadWriteSerializationCopyStrategy.java    (revision 8497)
+++ ehcache-core/src/main/java/net/sf/ehcache/store/compound/ReadWriteSerializationCopyStrategy.java    (working copy)
@@ -118,6 +118,7 @@
         if (ElementIdHelper.hasId(element)) {
             ElementIdHelper.setId(newElement, ElementIdHelper.getId(element));
         }
+        element.updateAccessStatistics();
         return newElement;
     }

Hung Huynh 2013-12-03

test for this issue CopyOnReadAndTTITest

Louis Jacomet Jacomet 2014-02-13

Solving this properly is a difficult problem. In the current code organization, update of element attributes is the responsibility of the {{Cache}} class. However, when using {{copyOn*}} semantics, this class may not see the really stored {{Element}} instance. And I am not very keen on fixing through duplication of logic from {{Cache}} class to {{*CopyingCacheStore}} classes. I guess what I am saying is that I am not convinced that a proper fix will make it in Wawona GA.

Louis Jacomet Jacomet 2014-02-13

After some conversation, realized this issue is wider spread that this single use case. Anything that breaks object reference between what the user gets and what is stored will break this contract. This includes disk and offheap, although with a dependency on access pattern and whether or not the onheap tier can fit the hot set. Target for such a fix would be Ehcache 3.0 as it requires splitting stored value and associated metadata.