ozeigermann 2004/06/03 07:09:40 Modified: transaction/src/java/org/apache/commons/transaction/memory OptimisticMapWrapper.java Added: transaction/src/test/org/apache/commons/transaction/memory OptimisticMapWrapperTest.java Log: Added initial version of optimistic serializable map plus test. *This is work in progress* Revision Changes Path 1.2 +212 -28 jakarta-commons-sandbox/transaction/src/java/org/apache/commons/transaction/memory/OptimisticMapWrapper.java Index: OptimisticMapWrapper.java =================================================================== RCS file: /home/cvs/jakarta-commons-sandbox/transaction/src/java/org/apache/commons/transaction/memory/OptimisticMapWrapper.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- OptimisticMapWrapper.java 2 Jun 2004 21:54:04 -0000 1.1 +++ OptimisticMapWrapper.java 3 Jun 2004 14:09:40 -0000 1.2 @@ -23,12 +23,16 @@ package org.apache.commons.transaction.memory; +import java.util.HashSet; +import java.util.Iterator; import java.util.Map; +import java.util.Set; /** * Wrapper that adds transactional control to all kinds of maps that implement the [EMAIL PROTECTED] Map} interface. By using - * optimistic transaction control this wrapper has better isolation than [EMAIL PROTECTED] TransactionalMapWrapper}, but + * a naive optimistic transaction control this wrapper has better isolation than [EMAIL PROTECTED] TransactionalMapWrapper}, but * may also fail to commit. + * * <br> * Start a transaction by calling [EMAIL PROTECTED] #startTransaction()}. Then perform the normal actions on the map and * finally either calls [EMAIL PROTECTED] #commitTransaction()} to make your changes permanent or [EMAIL PROTECTED] #rollbackTransaction()} to @@ -37,8 +41,9 @@ * <em>Caution:</em> Do not modify values retrieved by [EMAIL PROTECTED] #get(Object)} as this will circumvent the transactional mechanism. * Rather clone the value or copy it in a way you see fit and store it back using [EMAIL PROTECTED] #put(Object, Object)}. * <br> - * <em>Note:</em> This wrapper guarantees isolation levels <code>REPEATABLE READ</code> or <code>SERIALIZABLE</code>. - * + * <em>Note:</em> This wrapper guarantees isolation level <code>SERIALIZABLE</code>. + * <br> + * <em>Caution:</em> This implementation might be slow when large amounts of data is changed in a transaction as much references will need to be copied around. * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Zeigermann</a> * @version $Revision$ * @see TransactionalMapWrapper @@ -46,25 +51,204 @@ */ public class OptimisticMapWrapper extends TransactionalMapWrapper { - /** - * Creates a new optimistic transactional map wrapper. Temporary maps and sets to store transactional - * data will be instances of [EMAIL PROTECTED] HashMap} and [EMAIL PROTECTED] HashSet}. - * - * @param wrapped map to be wrapped - */ - public OptimisticMapWrapper(Map wrapped) { - super(wrapped, new HashMapFactory(), new HashSetFactory()); - } - - /** - * Creates a new optimistic transactional map wrapper. Temporary maps and sets to store transactional - * data will be created and disposed using [EMAIL PROTECTED] MapFactory} and [EMAIL PROTECTED] SetFactory}. - * - * @param wrapped map to be wrapped - * @param mapFactory factory for temporary maps - * @param setFactory factory for temporary sets - */ - public OptimisticMapWrapper(Map wrapped, MapFactory mapFactory, SetFactory setFactory) { - super(wrapped, mapFactory, setFactory); - } + protected Set activeTransactions; + + /** + * Creates a new optimistic transactional map wrapper. Temporary maps and sets to store transactional + * data will be instances of [EMAIL PROTECTED] HashMap} and [EMAIL PROTECTED] HashSet}. + * + * @param wrapped map to be wrapped + */ + public OptimisticMapWrapper(Map wrapped) { + this(wrapped, new HashMapFactory(), new HashSetFactory()); + } + + /** + * Creates a new optimistic transactional map wrapper. Temporary maps and sets to store transactional + * data will be created and disposed using [EMAIL PROTECTED] MapFactory} and [EMAIL PROTECTED] SetFactory}. + * + * @param wrapped map to be wrapped + * @param mapFactory factory for temporary maps + * @param setFactory factory for temporary sets + */ + public OptimisticMapWrapper( + Map wrapped, + MapFactory mapFactory, + SetFactory setFactory) { + super(wrapped, mapFactory, setFactory); + activeTransactions = new HashSet(); + } + + public synchronized void startTransaction() { + if (getActiveTx() != null) { + throw new IllegalStateException( + "Active thread " + + Thread.currentThread() + + " already associated with a transaction!"); + } + CopyingTxContext context = new CopyingTxContext(); + activeTransactions.add(context); + setActiveTx(context); + } + + public synchronized void commitTransaction() { + TxContext txContext = getActiveTx(); + + if (txContext == null) { + throw new IllegalStateException( + "Active thread " + + Thread.currentThread() + + " not associated with a transaction!"); + } + + if (txContext.rollbackOnly) { + throw new IllegalStateException( + "Active thread " + + Thread.currentThread() + + " is marked for rollback!"); + } + + checkForConflicts(); + + copyChangesToConcurrentTransactions(); + + super.commitTransaction(); + activeTransactions.remove(Thread.currentThread()); + + } + + protected void checkForConflicts() { + // TODO + } + + protected void copyChangesToConcurrentTransactions() { + CopyingTxContext thisTxContext = (CopyingTxContext) getActiveTx(); + + for (Iterator it = activeTransactions.iterator(); it.hasNext();) { + CopyingTxContext otherTxContext = (CopyingTxContext) it.next(); + + // no need to copy data if the other transaction does not access global map anyway + if (otherTxContext.cleared) continue; + + if (thisTxContext.cleared) { + // we will clear everything, so we have to copy everything before + otherTxContext.externalChanges.putAll(wrapped); + } else // no need to check if we have already copied everthing + { + + for (Iterator it2 = thisTxContext.changes.entrySet().iterator(); + it2.hasNext(); + ) { + Map.Entry entry = (Map.Entry) it2.next(); + Object value = wrapped.get(entry.getKey()); + if (value != null) { + // undo change + otherTxContext.externalChanges.put( + entry.getKey(), + value); + } else { + // undo add + otherTxContext.externalDeletes.add(entry.getKey()); + } + } + + for (Iterator it2 = thisTxContext.deletes.iterator(); + it2.hasNext(); + ) { + // undo delete + Object key = it2.next(); + Object value = wrapped.get(key); + otherTxContext.externalChanges.put(key, value); + } + } + } + + } + + public class CopyingTxContext extends TxContext { + protected Map externalChanges; + protected Map externalAdds; + protected Set externalDeletes; + + protected CopyingTxContext() { + super(); + externalChanges = mapFactory.createMap(); + externalDeletes = setFactory.createSet(); + externalAdds = mapFactory.createMap(); + } + + protected Set keys() { + Set keySet = super.keys(); + keySet.removeAll(externalDeletes); + keySet.addAll(externalAdds.keySet()); + return keySet; + } + + protected Object get(Object key) { + + if (deletes.contains(key)) { + // reflects that entry has been deleted in this tx + return null; + } + + Object changed = changes.get(key); + if (changed != null) { + return changed; + } + + Object added = adds.get(key); + if (added != null) { + return added; + } + + if (cleared) { + return null; + } else { + if (externalDeletes.contains(key)) { + // reflects that entry has been deleted in this tx + return null; + } + + changed = externalChanges.get(key); + if (changed != null) { + return changed; + } + + added = externalAdds.get(key); + if (added != null) { + return added; + } + + // not modified in this tx + return wrapped.get(key); + } + } + + protected int size() { + int size = super.size(); + + size -= externalDeletes.size(); + size += externalAdds.size(); + + return size; + } + + protected void clear() { + super.clear(); + externalDeletes.clear(); + externalChanges.clear(); + externalAdds.clear(); + } + + protected void dispose() { + super.dispose(); + setFactory.disposeSet(externalDeletes); + externalDeletes = null; + mapFactory.disposeMap(externalChanges); + externalChanges = null; + mapFactory.disposeMap(externalAdds); + externalAdds = null; + } + + } } 1.1 jakarta-commons-sandbox/transaction/src/test/org/apache/commons/transaction/memory/OptimisticMapWrapperTest.java Index: OptimisticMapWrapperTest.java =================================================================== /* * $Header: /home/cvs/jakarta-commons-sandbox/transaction/src/test/org/apache/commons/transaction/memory/OptimisticMapWrapperTest.java,v 1.1 2004/06/03 14:09:40 ozeigermann Exp $ * $Revision: 1.1 $ * $Date: 2004/06/03 14:09:40 $ * * ==================================================================== * * Copyright 1999-2002 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.commons.transaction.memory; import junit.framework.*; import java.util.HashMap; import java.util.Map; import java.util.logging.*; import org.apache.commons.transaction.util.Jdk14Logger; import org.apache.commons.transaction.util.LoggerFacade; import org.apache.commons.transaction.util.RendezvousBarrier; /** * Tests for map wrapper. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Zeigermann</a> */ public class OptimisticMapWrapperTest extends TestCase { private static final Logger logger = Logger.getLogger(OptimisticMapWrapperTest.class.getName()); private static final LoggerFacade sLogger = new Jdk14Logger(logger); private static final long BARRIER_TIMEOUT = 2000; // XXX need this, as JUnit seems to print only part of these strings private static void report(String should, String is) { if (!should.equals(is)) { fail( "\nWrong output:\n'" + is + "'\nShould be:\n'" + should + "'\n"); } } public static Test suite() { TestSuite suite = new TestSuite(OptimisticMapWrapperTest.class); return suite; } public static void main(java.lang.String[] args) { junit.textui.TestRunner.run(suite()); } public OptimisticMapWrapperTest(String testName) { super(testName); } public void testBasic() throws Throwable { logger.info("Checking basic transaction features"); final Map map1 = new HashMap(); final OptimisticMapWrapper txMap1 = new OptimisticMapWrapper(map1); // make sure changes are propagated to wrapped map outside tx txMap1.put("key1", "value1"); report("value1", (String) map1.get("key1")); // make sure changes are progated to wrapped map only after commit txMap1.startTransaction(); txMap1.put("key1", "value2"); report("value1", (String) map1.get("key1")); report("value2", (String) txMap1.get("key1")); txMap1.commitTransaction(); report("value2", (String) map1.get("key1")); report("value2", (String) txMap1.get("key1")); // make sure changes are reverted after rollback txMap1.startTransaction(); txMap1.put("key1", "value3"); txMap1.rollbackTransaction(); report("value2", (String) map1.get("key1")); report("value2", (String) txMap1.get("key1")); } public void testMulti() throws Throwable { logger.info("Checking concurrent transaction features"); final Map map1 = new HashMap(); final OptimisticMapWrapper txMap1 = new OptimisticMapWrapper(map1); final RendezvousBarrier beforeCommitBarrier = new RendezvousBarrier("Before Commit", 2, BARRIER_TIMEOUT, sLogger); final RendezvousBarrier afterCommitBarrier = new RendezvousBarrier("After Commit", 2, BARRIER_TIMEOUT, sLogger); Thread thread1 = new Thread(new Runnable() { public void run() { txMap1.startTransaction(); try { beforeCommitBarrier.meet(); txMap1.put("key1", "value2"); txMap1.commitTransaction(); afterCommitBarrier.call(); } catch (InterruptedException e) { logger.log(Level.WARNING, "Thread interrupted", e); afterCommitBarrier.reset(); beforeCommitBarrier.reset(); } } }, "Thread1"); txMap1.put("key1", "value1"); txMap1.startTransaction(); thread1.start(); report("value1", (String) txMap1.get("key1")); beforeCommitBarrier.call(); afterCommitBarrier.meet(); // we have serializable as isolation level, that's why I will still see the old value report("value1", (String) txMap1.get("key1")); // now when I override it it should of course be my value txMap1.put("key1", "value3"); report("value3", (String) txMap1.get("key1")); // after rollback it must be the value written by the other thread txMap1.rollbackTransaction(); report("value2", (String) txMap1.get("key1")); } public void testTxControl() throws Throwable { logger.info("Checking advanced transaction control (heavily used in JCA implementation)"); } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]