I attached a first draft of the cache. Maybe you would like to have a look at it and test a bit. The code is small and should be very obvious. Some more lines of comments are probably missing ;)
Caching of content is omitted for now, as it is much more complicated. A LRU map will not do, as I suppose we will want to restrict the size of the cache in bytes and not in objects. Any ideas?
I have actually named the store "TxCacheStore", so the Domain.xml fragment below should do. If the cache turns out to be working and useful we might rethink this...
What about the size of caches? This should be configurable, shouldn't it?
Oliver
P.S.: Martin, would you mind if we listed you as author as well?
Oliver Zeigermann wrote:
That's fine :)
As Stefan pointed out you can disable the cache. That was something I had forgotten about. I thought I had to fix the StandardStore first and then continue with fixes for my store that were hard to fix because of inaccurate caching.
In short: I will do some fixes on the tx file store first and then will do the caching. Hopefully, I will come up with it next week.
I would propose to leave StandardStore untouched as some implementation might rely on its - sometimes erroneous - behavior. We might call the new one TxCacheStore or so and configure it in Domain.xml like
... <definition> <store name="tx" classname="org.apache.slide.store.TxCacheStore"> ... </definition> ...
What do you think?
Oliver
Martin Holz wrote:
Stefan L�tzkendorf <[EMAIL PROTECTED]> writes:
Hello Oliver,
yes of course is anyone interested!
Me too !!!
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
.
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
.
/* * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/store/StandardStore.java,v 1.19 2002/11/11 13:54:19 pnever Exp $ * $Revision: 1.19 $ * $Date: 2002/11/11 13:54:19 $ * * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999-2002 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Slide", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */
package org.apache.slide.store; import org.apache.slide.common.Service; import org.apache.slide.common.ServiceAccessException; import org.apache.slide.store.txfile.rm.impl.XidWrapper; import org.apache.slide.util.*; import org.apache.slide.util.ObjectCache; import javax.transaction.xa.XAException; import javax.transaction.xa.Xid; /** * Store that allows for transactional caching of data. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Zeigermann</a> * @version $Revision: 1.0$ */ public class TxCacheStore extends StandardStore { private static final String LOG_CHANNEL = TxCacheStore.class.getName(); public static final int DEFAULT_GLOBAL_CACHE_SIZE = 10000; // there might be at least one active transaction branch per thread protected ThreadLocal activeTransactionBranch = new ThreadLocal(); public TxCacheStore(int globalCacheSize) { init(globalCacheSize); } public TxCacheStore() { this(DEFAULT_GLOBAL_CACHE_SIZE); } public void forget(Xid xid) throws XAException { super.forget(xid); Xid txId = (Xid) XidWrapper.wrap(xid); activeTransactionBranch.set(null); ((TxCacheWrapper) objectsCache).getTxCache().forget(txId); ((TxCacheWrapper) permissionsCache).getTxCache().forget(txId); ((TxCacheWrapper) locksCache).getTxCache().forget(txId); ((TxCacheWrapper) descriptorsCache).getTxCache().forget(txId); ((TxCacheWrapper) descriptorCache).getTxCache().forget(txId); } public void rollback(Xid xid) throws XAException { super.rollback(xid); Xid txId = (Xid) XidWrapper.wrap(xid); activeTransactionBranch.set(null); ((TxCacheWrapper) objectsCache).getTxCache().rollback(txId); ((TxCacheWrapper) permissionsCache).getTxCache().rollback(txId); ((TxCacheWrapper) locksCache).getTxCache().rollback(txId); ((TxCacheWrapper) descriptorsCache).getTxCache().rollback(txId); ((TxCacheWrapper) descriptorCache).getTxCache().rollback(txId); } public void commit(Xid xid, boolean onePhase) throws XAException { super.commit(xid, onePhase); Xid txId = (Xid) XidWrapper.wrap(xid); activeTransactionBranch.set(null); ((TxCacheWrapper) objectsCache).getTxCache().commit(txId); ((TxCacheWrapper) permissionsCache).getTxCache().commit(txId); ((TxCacheWrapper) locksCache).getTxCache().commit(txId); ((TxCacheWrapper) descriptorsCache).getTxCache().commit(txId); ((TxCacheWrapper) descriptorCache).getTxCache().commit(txId); } public void start(Xid xid, int flags) throws XAException { super.start(xid, flags); // XXX we do not suspend, so we do not resume if (flags == TMNOFLAGS || flags == TMJOIN) { Xid txId = (Xid) XidWrapper.wrap(xid); activeTransactionBranch.set(txId); ((TxCacheWrapper) objectsCache).getTxCache().start(txId); ((TxCacheWrapper) permissionsCache).getTxCache().start(txId); ((TxCacheWrapper) locksCache).getTxCache().start(txId); ((TxCacheWrapper) descriptorsCache).getTxCache().start(txId); ((TxCacheWrapper) descriptorCache).getTxCache().start(txId); } } // have it outside ctor to make it overloadable protected void init(int globalCacheSize) { objectsCache = new TxCacheWrapper(globalCacheSize, "object"); permissionsCache = new TxCacheWrapper(globalCacheSize, "permission"); locksCache = new TxCacheWrapper(globalCacheSize, "locks"); descriptorsCache = new TxCacheWrapper(globalCacheSize, "descriptors"); descriptorCache = new TxCacheWrapper(globalCacheSize, "descriptor"); } protected void enlist(Service service) throws ServiceAccessException { // we need to register ourselves to be part of the tx // XXX be careful not to call super.enlist() as this will call this method recursively super.enlist(this); super.enlist(service); } protected void delist(Service service, boolean success) throws ServiceAccessException { // as we have registered we need to deregister // XXX be careful not to call super.delist(success) as this will call this method recursively super.delist(this, success); super.delist(service, success); } protected void resetCaches() { // StandardStore calls this upon delist for a rudimentary tx control // we have one of our own tx control, so do nothing here } protected class TxCacheWrapper implements ObjectCache { private TxLRUObjectCache txCache; public TxCacheWrapper(TxLRUObjectCache txCache) { this.txCache = txCache; } public TxCacheWrapper(int globalCacheSize, String name) { this(new TxLRUObjectCache(globalCacheSize, name, getLogger())); } public Object get(Object key) { Xid txId = (Xid) activeTransactionBranch.get(); return txCache.get(txId, key); } public void put(Object key, Object value) { Xid txId = (Xid) activeTransactionBranch.get(); txCache.put(txId, key, value); } public void remove(Object key) { Xid txId = (Xid) activeTransactionBranch.get(); txCache.remove(txId, key); } public void clear() { txCache.clear(); } public TxLRUObjectCache getTxCache() { return txCache; } } }
/* * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/store/StandardStore.java,v 1.19 2002/11/11 13:54:19 pnever Exp $ * $Revision: 1.19 $ * $Date: 2002/11/11 13:54:19 $ * * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999-2002 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Slide", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.slide.util; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.slide.util.logger.Logger; import org.apache.commons.collections.LRUMap; /** * Transactional LRU object cache. Caches objects using a least-recently-used strategy. * * It provides basic isolation from other transactions and atomicity of all operations. As * no locking is used (which is undesirable mainly as a cache should never block and a commit must never fail) * serializability needs to be guaranteed by underlying stores. * <br> * <br> * <em>Caution</em>: Only global caches are limited by given size. * Size of temporary data inside a transaction is unlimited. This is needed to assure correctness of global cache. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Zeigermann</a> * @version $Revision: 1.0$ */ public class TxLRUObjectCache { protected Map globalCache; protected Map txChangeCaches; protected Map txDeleteCaches; protected int hits = 0; protected int misses = 0; protected String name; protected Logger logger; protected String logChannel; protected final boolean loggingEnabled; public TxLRUObjectCache(int globalCacheSize, String name, Logger logger) { globalCache = new LRUMap(globalCacheSize); txChangeCaches = new HashMap(); txDeleteCaches = new HashMap(); this.name = name; this.logger = logger; logChannel = "TxLRUObjectCache"; if (name != null) { logChannel += "(" + name + ")"; } // used for guarded logging as preparation is expensive loggingEnabled = logger.isEnabled(logChannel, Logger.DEBUG); } public TxLRUObjectCache(int globalCacheSize) { this(globalCacheSize, null, null); } public synchronized void clear() { globalCache.clear(); txChangeCaches.clear(); txDeleteCaches.clear(); } public synchronized Object get(Object txId, Object key) { if (txId != null) { Set deleteCache = (Set) txDeleteCaches.get(txId); if (deleteCache.contains(key)) { hit(txId, key); // reflects that entry has been deleted in this tx return null; } Map changeCache = (Map) txChangeCaches.get(txId); Object changed = changeCache.get(key); if (changed != null) { hit(txId, key); // if object has been changed in this tx, get the local one return changed; } } // as fall back return value from global cache (if present) Object global = globalCache.get(key); if (global != null) { hit(txId, key); } else { miss(txId, key); } return global; } public synchronized void put(Object txId, Object key, Object value) { if (txId != null) { // if it has been deleted before, undo this Set deleteCache = (Set) txDeleteCaches.get(txId); deleteCache.remove(key); Map changeCache = (Map) txChangeCaches.get(txId); changeCache.put(key, value); if (loggingEnabled) { logger.log(txId + " added '" + key + "'", logChannel, Logger.DEBUG); } } else { globalCache.put(key, value); if (loggingEnabled) { logger.log("Added '" + key + "'", logChannel, Logger.DEBUG); } } } public synchronized void remove(Object txId, Object key) { if (txId != null) { // if it has been changed before, undo this Map changeCache = (Map) txChangeCaches.get(txId); changeCache.remove(key); Set deleteCache = (Set) txDeleteCaches.get(txId); deleteCache.add(key); // guard logging as preparation is expensive if (loggingEnabled) { logger.log(txId + " removed '" + key + "'", logChannel, Logger.DEBUG); } } else { globalCache.remove(key); if (loggingEnabled) { logger.log("Removed '" + key + "'", logChannel, Logger.DEBUG); } } } public synchronized void start(Object txId) { if (txId != null) { txChangeCaches.put(txId, new HashMap()); txDeleteCaches.put(txId, new HashSet()); } } public synchronized void rollback(Object txId) { if (txId != null) { if (loggingEnabled) { Map changeCache = (Map) txChangeCaches.get(txId); Set deleteCache = (Set) txDeleteCaches.get(txId); logger.log( txId + " rolled back " + changeCache.size() + " changes and " + deleteCache.size() + " scheduled deletes", logChannel, Logger.DEBUG); } // simply forget about tx forget(txId); } } public synchronized void commit(Object txId) { if (txId != null) { // apply local changes and deletes (is atomic as we have a global lock on this TxCache) Map changeCache = (Map) txChangeCaches.get(txId); for (Iterator it = changeCache.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); globalCache.put(entry.getKey(), entry.getValue()); } Set deleteCache = (Set) txDeleteCaches.get(txId); for (Iterator it = deleteCache.iterator(); it.hasNext();) { Object key = it.next(); globalCache.remove(key); } if (loggingEnabled) { logger.log( txId + " committed " + changeCache.size() + " changes and " + deleteCache.size() + " scheduled deletes", logChannel, Logger.DEBUG); } // finally forget about tx forget(txId); } } public synchronized void forget(Object txId) { if (txId != null) { txChangeCaches.remove(txId); txDeleteCaches.remove(txId); } } protected void hit(Object txId, Object key) { hits++; log(txId, key, true); } protected void miss(Object txId, Object key) { misses++; log(txId, key, false); } protected void log(Object txId, Object key, boolean hit) { if (loggingEnabled) { StringBuffer log = new StringBuffer(); if (txId != null) { Map changeCache = (Map) txChangeCaches.get(txId); Set deleteCache = (Set) txDeleteCaches.get(txId); log.append(txId + " (" + changeCache.size() + ", " + deleteCache.size() + ") "); } log.append( (hit ? "Cache Hit: '" : "Cache Miss: '") + key + "' " + hits + " / " + misses + " / " + globalCache.size()); logger.log(log.toString(), logChannel, Logger.DEBUG); } } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
