Friday, September 4, 2009

The semantics of Flushing in Ehcache

When I was going through the ehcache user manual, I was a little confused about a 'flush' and a 'shutdown' of Ehcache. Of course, the names of these methods suggests their purpose in a perfectly clear way. But I never really paid any attention towards the inner workings of these methods.

I always thought if I can flush each and every cache in my manager class before I shutdown its equivalent to calling a shutdown method on it. I'm talking about caches with basic functionalities ( with no bootstraps, no loaders etc.. so sending shutdown hook to all these (empty)listeners doesn't matter ). But, I was proved wrong. Here's the reason ...

Following code does a very simple task. It reads all the elements from one ehcache and transfers all its element to a new cache and tries to persist the items to diskstore before termination.


import net.sf.ehcache.Element
import net.sf.ehcache.CacheManager
import net.sf.ehcache.Cache

def cachemgr = new CacheManager("D:/mPortal/workspace_new_cvs_structure/EhCacheDemo/config/change_listener_cache.xml")
def deltacache = cachemgr.getCache("deltaCache")

def deltaclone = new Cache("deltaCacheClone", 10000,null, true,cachemgr.getDiskStorePath(), true, 120,120,true, 120,null)
cachemgr.addCache(deltaclone)


println "Migration about to begin"
println "Size of the original cache : ${deltacache.getSize()}"
println "Size of the clone : ${deltaclone.getSize()}"

deltacache.getKeys().each{
ele = deltacache.get(it)
deltaclone.put(new Element (ele.getKey(),ele.getValue()))
}

println "Size of the original cache after migration : ${deltacache.getSize()}"
println "Size of the clone after migration : ${deltaclone.getSize()}"

println "Migration successfully finished.."

deltacache.flush()
deltaclone.flush()


Note that I'm flushing all the caches I created/used in my program at the end.

To my surprise, whenever I run this program again the 'deltaclone' cache always initializes itself to zero. This puzzled me for quite sometime and finally forced me to revisit the source code of ehcache to understand the behavior.

The Reason

What I found is that the 'flush' operation was not a synchronous operation at all. it only signals the spool to flush it when it wakes again. In my case, my VM didn't stop till this thread do its job.. it has killed it forcibly.

How Shutdown Solves this problem ?

The shutdown method however, is very responsible and gracefully waits till the thread finishes its execution. Following is the snippet which can explain this behavior...



//set the write index flag. Ignored if not persistent
flush();

//tell the spool thread to spool down. It will loop one last time if flush was caled.
spoolAndExpiryThreadActive = false;

//interrupt the spoolAndExpiryThread if it is waiting to run again to get it to run now
// Then wait for it to write
spoolAndExpiryThread.interrupt();
if (spoolAndExpiryThread != null) {
spoolAndExpiryThread.join();
}


This is the snippet from the DiskStore's dispose method. Thanks to the documentation, the code is pretty much self explainable.


The fix

So, the proper way to fix my program is very simple.. by adding the shutdown() method call at the end.


import net.sf.ehcache.Element
import net.sf.ehcache.CacheManager
import net.sf.ehcache.Cache

def cachemgr = new CacheManager("D:/mPortal/workspace_new_cvs_structure/EhCacheDemo/config/change_listener_cache.xml")
def deltacache = cachemgr.getCache("deltaCache")

def deltaclone = new Cache("deltaCacheClone", 10000,null, true,cachemgr.getDiskStorePath(), true, 120,120,true, 120,null)
cachemgr.addCache(deltaclone)


println "Migration about to begin"
println "Size of the original cache : ${deltacache.getSize()}"
println "Size of the clone : ${deltaclone.getSize()}"

deltacache.getKeys().each{
ele = deltacache.get(it)
deltaclone.put(new Element (ele.getKey(),ele.getValue()))
}

println "Size of the original cache after migration : ${deltacache.getSize()}"
println "Size of the clone after migration : ${deltaclone.getSize()}"

println "Migration successfully finished.."

deltacache.flush() // Redundant
deltaclone.flush() // Redundant

cachemgr.shutdown()


The additional flush operations are totally redundant, the program works even if you remove those statements..

No comments: