You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jcs-dev@jakarta.apache.org by Travis Savo <ts...@IFILM.com> on 2004/08/09 19:25:33 UTC

RE: CachingWorker

I'm nearing completion of this refactoring.

I changed the JCSWorker to take the region only in the constructor, and do
the JCS.getInstance() there. No more calling JCS.getInstance() every time!

JCSWorker.getResult() takes a Serializable Key, and a JCSWorkerHelper, which
is an interface consisting of: public Object doWork() throws Exception.

So the typical usage looks like:

public class someClass{
  private static final JCSWorker worker = new JCSWorker("someRegion");

  public Object getSomething(Serializable aKey){
    JCSWorkerHelper helper = new AbstractJCSWorkerHelper(){
      public Object doWork(){
        return /* Do some work here on the cache miss */;
      }
    }
    Object result = worker.getResult(aKey, helper);
  }
}

AbstractJCSWorkerHelper is a basic implementation of JCSWorkerHelper for
convince. There's also methods with signatures for groups too.

The semantics of mutual exclusion still apply: If one person starts doing
the work, all subsequent identical requests will wait for the first one to
finish and then do cache gets, so there's no race condition where the work
gets done and put multiple times unnecessarily.

As an aside, it's trivial to extend this to perform additional operations on
objects coming out of cache. I'll post one for Hibernate which locks the
objects with a Hibernate Session.

I'll commit this to experimental later on once I've updated the Javadocs to
reflect the new interface.

-Travis Savo <ts...@ifilm.com>

-----Original Message-----
From: Aaron Smuts [mailto:aasmuts@wisc.edu]
Sent: Saturday, July 17, 2004 11:37 AM
To: 'Turbine JCS Developers List'
Subject: RE: CachingWorker


This is interesting.  I don't like getting the region everytime you do a
get.  This slows things down dramatically.  I posted on the javaworld
article on caching about this.  Their timings were way off because of
it.  We might be able to make it more efficient.  I'd prefer issuing a
get on a region I have has a static variable, and then calling something
like this if it returns null.  Would that work?

I see how it could be useful.  You can always put anything in the
experiemental package or, maybe better, something like this in a
subpackage of utils on the main src.  org.apache.jcs.utils.access or
worker or something that makes sense, say org.apache.control.loader .  

We should also look into the idea of implementing pluggable cacheLoaders
for a region.  

Have you looked at the FutureResult in Doug Lea's util concurrent
package.  It may be useful here.

I'm still looking over your idea.  

On another issue, we need to make items in groups returnable by jus the
key, without referencing the group.  Otherwise a group is just an
inefficient region.  To do this we need to create a CacheKey object for
the CacheElement.  The CacheKey and the GroupAttrName classes need
equals methods that can match on the same key.  

Cheers,

Aaron

> -----Original Message-----
> From: Travis Savo [mailto:tsavo@IFILM.com]
> Sent: Friday, July 16, 2004 7:04 PM
> To: 'Turbine JCS Developers List'
> Subject: CachingWorker
> 
> Did you want my CachingWorker?
> 
> It's a wrapper around JCS to encapsulate the most common set of
actions
> performed with JCS.
> 
> Given a region and a key, do the following psudo-code:
> 
> if(region.get(key) != null)
>   return region.get(key)
> otherwise
>   doTheWork()
>   region.put(result)
>   return result
> 
> It also ensures that if a CachingWorker is in the middle of doing the
work
> for the same region and key, the second (or Nth) CachingWorker will
wait
> and
> use the results from that instead of doing the get/work/put again.
> 
> Using JCS to do this might look like:
> 
> final String key = "some:key";
> MyObject myObject = null;
> JCS myRegion = JCS.getInstance("myRegion");
> if((myObject = (MyObject) myRegion.get(key)) == null){
>   myObject = makeSomeObject(key);
>   myRegion.put(key, myObject);
> }
> 
> With the caching worker it's:
> 
> final String key = "some:key";
> CachingWorker worker = new CachingWorker("myRegion", key) {
>   public Object doWork() throws Exception {
>       return makeSomeObject(key);
>   }
> };
> MyObject myObject = (MyObject) worker.getResult();
> 
> The advantage is with the CachingWorker, if two (or more) threads
execute
> this code in parallel, only one will do the work, while the second
> CachingWorker waits, and mooches off the result of the first.
> 
> I stuck it in the org.apache.jcs package in my project because it's
just
> an
> advanced interface for org.apache.jcs.JCS and has no dependencies on
> anything but org.apache.jcs.JCS and commons.logging.
> 
> Here's the code. It's well javadoced. Of course I'd be happy to commit
it
> myself if you want.
> 
> /*
>  * Created on Oct 8, 2003
>  *
>  */
> package org.apache.jcs;
> import java.io.Serializable;
> import java.util.HashMap;
> import java.util.Map;
> import org.apache.commons.logging.Log;
> import org.apache.commons.logging.LogFactory;
> /**
>  * Utility class to encapsulate doing a piece of work, and caching the
> results
>  * in JCS. Simply implement this class and override protected Object
> doWork(),
>  * and do the work in there, returning the object to be cached. Then
call
>  * .getResult() to get the result of the work. If the object isn't
> allready
> in
>  * the Cache, doWork() will get called, and the result will be put
into
> the
>  * cache. If the object is allready in cache, the cached result will
be
> returned
>  * instead. As an added bonus, multiple CachingWorkers with the same
> region,
>  * group, and key won't do the work multiple times: The first
> CachingWorker
> to
>  * get started will do the work, and all subsequent workers with the
same
>  * region, group, and key will wait on the first one and use his
resulting
> work
>  * instead of doing the work themselves.
>  *
>  * This is ideal when the work being done is a query to the database
where
> the
>  * results may take time to be retrieved.
>  *
>  * For example: <br>
>  *
>  * <code>
>  *     CachingWorker cachingWorker = new CachingWorker("example
region",
> aKey) {<br>
>  *     		public Object doWork() throws Exception { <br>
>  *          // Do some (DB?) work here which results in a list <br>
>  *          // This only happens if the cache dosn't have a item in
this
> region for aKey <br>
>  *          // Note this is especially useful with Hibernate, which
will
> cache indiviual <br>
>  *          // Objects, but not entire query result sets. <br>
>  *          List results = query.list(); <br>
>  *          // Whatever we return here get's cached with aKey, and
future
> calls to <br>
>  *          // getResult() on a CachedWorker with the same region and
key
> will return that instead. <br>
>  *          return results; <br>
>  *        } <br>
>  *      }; <br>
>  *      List results = (List) cachingWorker.getResult();<br>
>  *     </code>
>  *
>  * This is essentially the same as doing: JCS jcs =
> JCS.getInstance("example
>  * region"); List results = (List) jcs.get(aKey); if(results != null){
> //do
> the
>  * work here results = query.list(); jcs.put(aKey, results); }
>  *
>  *
>  * But has the added benifit of the work-load sharing; under normal
>  * circumstances if multiple threads all tried to do the same query at
the
> same
>  * time, the same query would happen multiple times on the database,
and
> the
>  * resulting object would get put into JCS multiple times.
>  *
>  * Using the Caching worker eliminates this senario entirely.
>  *
>  * @author Travis Savo
>  */
> public abstract class CachingWorker {
> 	private static final Log logger =
> LogFactory.getLog(CachingWorker.class);
> 	/**
> 	 * Object to hold the result of the work.
> 	 */
> 	private Object result = null;
> 	public boolean wasCached = false;
> 	/**
> 	 * Map to hold who's doing work presently.
> 	 */
> 	private static volatile Map map = new HashMap();
> 	/**
> 	 * Boolean to let us know if we need to wait or if it's
finished.
> 	 */
> 	private volatile boolean finished = false;
> 	/**
> 	 * Region for the JCS cache.
> 	 */
> 	private String region;
> 	/**
> 	 * Key for caching the results with.
> 	 */
> 	private Serializable key;
> 	/**
> 	 * Optional Group to store the result under.
> 	 */
> 	private String group = null;
> 	/**
> 	 * Constructor which takes a region and a key, but no group.
> 	 *
> 	 * @param aName
> 	 *          The Region to use for the JCS cache.
> 	 * @param aKey
> 	 *          The key to store the result under.
> 	 */
> 	public CachingWorker(String aRegion, Serializable aKey) {
> 		this(aRegion, aKey, null);
> 	}
> 	/**
> 	 * Constructor which takes a region, a key, and a group.
> 	 *
> 	 * @param aName
> 	 *          The Region to use for the JCS cache.
> 	 * @param aKey
> 	 *          The key to store the result under.
> 	 * @param aGroup
> 	 *          The group to store the result under.
> 	 */
> 	public CachingWorker(String aRegion, Serializable aKey, String
> aGroup) {
> 		region = aRegion;
> 		key = aKey;
> 		group = aGroup;
> 		finished = false;
> 	}
> 	/**
> 	 * Abstract method to implement for the case where the object
has
> not been
> 	 * cached yet, and we need to get it from somewhere else and
stick
> the result
> 	 * in the cache.
> 	 *
> 	 * You should not be calling this yourself! Call getResult()
> instead.
> 	 *
> 	 * @return The result of doing the work to be cached and
returned in
> 	 *         getResult()
> 	 * @throws Exception
> 	 *           If something goes wrong, throw an Exception and
nothing
> will get
> 	 *           cached.
> 	 */
> 	protected abstract Object doWork() throws Exception;
> 	/**
> 	 * Getter for the region of the JCS Cache.
> 	 *
> 	 * @return The JCS region in which the result will be cached.
> 	 */
> 	public String getRegion() {
> 		return region;
> 	}
> 	/**
> 	 * Getter for the key used to store the result of doing the work
in
> JCS.
> 	 *
> 	 * @return The key used to cache the result of the work.
> 	 */
> 	public Serializable getKey() {
> 		return key;
> 	}
> 	/**
> 	 * Getter for the group used to store the result of doing the
work
> in JCS.
> 	 *
> 	 * @return The key group used to cache the result of the work
(could
> be null).
> 	 */
> 	public String getGroup() {
> 		return group;
> 	}
> 	/**
> 	 * Gets the cached result for this region/key OR does the work
and
> caches the
> 	 * result, returning the result. If the result has not been
cached
> yet, this
> 	 * calls doWork() to do the work and cache the result.
> 	 *
> 	 * This is also an opertunity to do any post processing of the
> result in your
> 	 * CachedWorker implementation.
> 	 *
> 	 * @return The result of doing the work, or the cached result.
> 	 * @throws Exception
> 	 *           Throws an exception if anything goes wrong while
doing
> the work.
> 	 */
> 	public Object getResult() throws Exception {
> 		return run();
> 	}
> 	/**
> 	 * Try and get the object from the cache, and if it's not there,
do
> the work
> 	 * and cache it. This also ensures that only one CachedWorker is
> doing the
> 	 * work and subsequent calls to a CachedWorker with identical
> region/key/group
> 	 * will wait on the results of this call.
> 	 *
> 	 * @return Either the result of doing the work, or the cached
> result.
> 	 * @throws Exception
> 	 *           If something goes wrong while doing the work, throw
an
> exception.
> 	 */
> 	private Object run() throws Exception {
> 		long start = 0;
> 		long dbTime = 0;
> 		CachingWorker thread = null;
> 		synchronized (map) {
> 			//Check to see if we allready have a thread
doing
> this work.
> 			thread = (CachingWorker) map.get(getRegion() +
> getKey());
> 			if (thread == null) {
> 				//If not, add ourselves as the Worker so
> 				//calls in another thread will use this
> worker's result
> 				map.put(getRegion() + getKey(), this);
> 			}
> 		}
> 		if (thread != null) {
> 			synchronized (thread) {
> 				if (logger.isDebugEnabled()) {
> 					logger.debug("Found a worker
> allready doing this work (" + thread.getRegion() + ":" +
thread.getKey() +
> ").");
> 				}
> 				if (!thread.finished) {
> 					thread.wait();
> 				}
> 				if (logger.isDebugEnabled()) {
> 					logger.debug("Another thread
> finished our work for us. Using thoes results instead. (" +
> thread.getRegion() + ":" + thread.getKey() + ").");
> 				}
> 				//Get the result and return it.
> 				result = thread.result;
> 				wasCached = thread.wasCached;
> 				return result;
> 			}
> 		}
> 		//Do the work
> 		try {
> 			if (logger.isDebugEnabled()) {
> 				logger.debug(getRegion() + " is doing
the
> work.");
> 			}
> 			result = null;
> 			//Get the cache
> 			JCS cache = JCS.getInstance(getRegion());
> 			//Try to get the item from the cache
> 			if (group != null) {
> 				result = cache.getFromGroup(getKey(),
> group);
> 			} else {
> 				result = cache.get(getKey());
> 			}
> 			//If the cache dosn't have it, do the work.
> 			wasCached = true;
> 			if (result == null) {
> 				wasCached = false;
> 				result = doWork();
> 				if (logger.isDebugEnabled()) {
> 					logger.debug("Work Done,
caching:
> key:" + getKey() + ", group:" + getGroup() + ", result:" + result +
".");
> 				}
> 				//Stick the result of the work in the
cache.
> 				if (group != null) {
> 					cache.putInGroup(getKey(),
group,
> result);
> 				} else {
> 					cache.put(getKey(), result);
> 				}
> 			}
> 			//return the result
> 			return result;
> 		} finally {
> 			if (logger.isDebugEnabled()) {
> 				logger.debug(getRegion() + ":" +
getKey() +
> " entered finally.");
> 			}
> 			synchronized (map) {
> 				//Remove ourselves as the worker.
> 				map.remove(getRegion() + getKey());
> 				synchronized (this) {
> 					finished = true;
> 					//Wake everyone waiting on us
> 					notifyAll();
> 				}
> 			}
> 		}
> 	}
> }
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: turbine-jcs-dev-unsubscribe@jakarta.apache.org
> For additional commands, e-mail:
turbine-jcs-dev-help@jakarta.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: turbine-jcs-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: turbine-jcs-dev-help@jakarta.apache.org

---------------------------------------------------------------------
To unsubscribe, e-mail: turbine-jcs-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: turbine-jcs-dev-help@jakarta.apache.org