Return-Path: Delivered-To: apmail-commons-commits-archive@locus.apache.org Received: (qmail 30311 invoked from network); 15 Aug 2007 12:46:54 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 15 Aug 2007 12:46:53 -0000 Received: (qmail 41612 invoked by uid 500); 15 Aug 2007 12:46:50 -0000 Delivered-To: apmail-commons-commits-archive@commons.apache.org Received: (qmail 41523 invoked by uid 500); 15 Aug 2007 12:46:49 -0000 Mailing-List: contact commits-help@commons.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@commons.apache.org Delivered-To: mailing list commits@commons.apache.org Received: (qmail 41501 invoked by uid 99); 15 Aug 2007 12:46:49 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 15 Aug 2007 05:46:49 -0700 X-ASF-Spam-Status: No, hits=-100.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 15 Aug 2007 12:46:47 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 40E9A1A981A; Wed, 15 Aug 2007 05:46:27 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r566128 - in /commons/proper/transaction/branches/TRANSACTION_2/src: java/org/apache/commons/transaction/ java/org/apache/commons/transaction/file/ java/org/apache/commons/transaction/locking/ java/org/apache/commons/transaction/memory/ tes... Date: Wed, 15 Aug 2007 12:46:26 -0000 To: commits@commons.apache.org From: ozeigermann@apache.org X-Mailer: svnmailer-1.1.0 Message-Id: <20070815124627.40E9A1A981A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: ozeigermann Date: Wed Aug 15 05:46:25 2007 New Revision: 566128 URL: http://svn.apache.org/viewvc?view=rev&rev=566128 Log: New tests including fixes for revealed bugs. RWLockmanagers had to be deleted as default Lock implementation do not fit such an implementation. Deadlock-Detection had to be deleted for the same reason. Future versions might reintroduce those features. Added: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/DefaultHierarchicalLockManager.java - copied, changed from r565953, commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/HierarchicalRWLockManager.java commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/DefaultLockManager.java commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/ commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/BasicTxMapTest.java commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/OptimisticTxMapTest.java commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/PessimisticTxMapTest.java Removed: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/HierarchicalRWLockManager.java commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/RWLockManager.java Modified: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/AbstractTransactionalResourceManager.java commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/DefaultTransaction.java commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/TxFileResourceManager.java commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/LockManager.java commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/BasicTxMap.java commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/package.html commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/DefaultTransactionTest.java commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/file/TxFileResourceManagerTest.java Modified: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/AbstractTransactionalResourceManager.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/AbstractTransactionalResourceManager.java?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/AbstractTransactionalResourceManager.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/AbstractTransactionalResourceManager.java Wed Aug 15 05:46:25 2007 @@ -50,7 +50,7 @@ this.name = name; } - // can be used to share a lock manager with other transactinal resource + // can be used to share a lock manager with other transactional resource // managers public AbstractTransactionalResourceManager(String name, LockManager lm) { this.name = name; @@ -188,9 +188,6 @@ } public void setLm(LockManager lm) { - if (this.lm != null) { - throw new IllegalStateException("You can set the lock manager only once!"); - } this.lm = lm; } Modified: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/DefaultTransaction.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/DefaultTransaction.java?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/DefaultTransaction.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/DefaultTransaction.java Wed Aug 15 05:46:25 2007 @@ -20,6 +20,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import org.apache.commons.transaction.locking.DefaultLockManager; import org.apache.commons.transaction.locking.LockManager; /** @@ -62,11 +63,20 @@ /** * Creates a new transaction implementation. * - * @param lm the lock manager shared by all resource managers + * @param lm + * the lock manager shared by all resource managers */ public DefaultTransaction(LockManager lm) { this.lm = lm; this.rms = new LinkedList(); + } + + /** + * Creates a new transaction implementation using the default lock manager. + * + */ + public DefaultTransaction() { + this(new DefaultLockManager()); } public synchronized void commit() throws TransactionException { Modified: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/TxFileResourceManager.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/TxFileResourceManager.java?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/TxFileResourceManager.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/file/TxFileResourceManager.java Wed Aug 15 05:46:25 2007 @@ -32,8 +32,9 @@ import org.apache.commons.transaction.ManageableResourceManager; import org.apache.commons.transaction.AbstractTransactionalResourceManager.AbstractTxContext; import org.apache.commons.transaction.file.FileResourceManager.FileResource; +import org.apache.commons.transaction.locking.DefaultLockManager; import org.apache.commons.transaction.locking.HierarchicalLockManager; -import org.apache.commons.transaction.locking.HierarchicalRWLockManager; +import org.apache.commons.transaction.locking.DefaultHierarchicalLockManager; import org.apache.commons.transaction.locking.LockManager; import org.apache.commons.transaction.resource.ResourceException; import org.apache.commons.transaction.resource.ResourceManager; @@ -55,6 +56,7 @@ public TxFileResourceManager(String name, String rootPath) { super(name); wrapped = new FileResourceManager(rootPath); + setLm(new DefaultLockManager()); } @Override @@ -69,7 +71,7 @@ @Override public void setLm(LockManager lm) { super.setLm(lm); - hlm = new HierarchicalRWLockManager(getRootPath(), lm); + hlm = new DefaultHierarchicalLockManager(getRootPath(), lm); } public class FileTxContext extends AbstractTxContext implements Copied: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/DefaultHierarchicalLockManager.java (from r565953, commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/HierarchicalRWLockManager.java) URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/DefaultHierarchicalLockManager.java?view=diff&rev=566128&p1=commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/HierarchicalRWLockManager.java&r1=565953&p2=commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/DefaultHierarchicalLockManager.java&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/HierarchicalRWLockManager.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/DefaultHierarchicalLockManager.java Wed Aug 15 05:46:25 2007 @@ -18,13 +18,13 @@ import java.util.concurrent.TimeUnit; -public class HierarchicalRWLockManager implements HierarchicalLockManager { +public class DefaultHierarchicalLockManager implements HierarchicalLockManager { private final String rootPath; private final LockManager lm; - public HierarchicalRWLockManager(String rootPath, LockManager lm) { + public DefaultHierarchicalLockManager(String rootPath, LockManager lm) { this.rootPath = rootPath; this.lm = lm; } Added: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/DefaultLockManager.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/DefaultLockManager.java?view=auto&rev=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/DefaultLockManager.java (added) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/DefaultLockManager.java Wed Aug 15 05:46:25 2007 @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.locking; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.transaction.locking.LockException.Code; + +/** + * + * @author olli + * + * @param + * @param + */ +public class DefaultLockManager implements LockManager { + private Log logger = LogFactory.getLog(getClass()); + + protected ConcurrentHashMap, ReentrantLock> locks = new ConcurrentHashMap, ReentrantLock>(); + + protected Map> locksForThreads = new ConcurrentHashMap>(); + + protected Map effectiveGlobalTimeouts = new ConcurrentHashMap(); + + @Override + public void endWork() { + release(); + } + + @Override + public void startWork(long timeout, TimeUnit unit) { + if (isWorking()) { + throw new IllegalStateException("work has already been started"); + } + locksForThreads.put(Thread.currentThread(), new CopyOnWriteArraySet()); + + long timeoutMSecs = unit.toMillis(timeout); + long now = System.currentTimeMillis(); + long effectiveTimeout = now + timeoutMSecs; + effectiveGlobalTimeouts.put(Thread.currentThread(), effectiveTimeout); + } + + // TODO + protected boolean checkForDeadlock() { + return false; + + } + + protected long computeRemainingTime(Thread thread) { + long timeout = effectiveGlobalTimeouts.get(thread); + long now = System.currentTimeMillis(); + long remaining = timeout - now; + return remaining; + } + + protected ReentrantLock create() { + return new ReentrantLock(); + } + + protected static class KeyEntry { + + private K k; + + private M m; + + public KeyEntry(K k, M m) { + this.k = k; + this.m = m; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj instanceof KeyEntry) { + KeyEntry otherEntry = (KeyEntry) obj; + return (otherEntry.k.equals(k) && otherEntry.m.equals(m)); + } + return false; + } + + public int hashCode() { + return k.hashCode() + m.hashCode(); + } + } + + public boolean isWorking() { + return locksForThreads.get(Thread.currentThread()) != null; + } + + @Override + public void lock(M managedResource, K key, boolean exclusive) throws LockException { + long remainingTime = computeRemainingTime(Thread.currentThread()); + + boolean locked = tryLockInternal(managedResource, key, exclusive, remainingTime, + TimeUnit.MILLISECONDS); + if (!locked) { + throw new LockException(Code.TIMED_OUT, key); + } + } + + @Override + public boolean tryLock(M managedResource, K key, boolean exclusive) { + return tryLockInternal(managedResource, key, exclusive, 0, TimeUnit.MILLISECONDS); + } + + protected boolean tryLockInternal(M managedResource, K key, boolean exclusive, long time, + TimeUnit unit) throws LockException { + reportTimeout(Thread.currentThread()); + + KeyEntry entry = new KeyEntry(key, managedResource); + + ReentrantLock lock = create(); + ReentrantLock existingLock = locks.putIfAbsent(entry, lock); + if (existingLock != null) + lock = existingLock; + Set locks = locksForThreads.get(Thread.currentThread()); + if (locks == null) { + throw new IllegalStateException("lock() can only be called after startWork()"); + } + + boolean locked; + if (time == 0) { + locked = lock.tryLock(); + } else { + try { + locked = lock.tryLock(time, unit); + } catch (InterruptedException e) { + throw new LockException(Code.INTERRUPTED); + } + + } + if (locked) { + locks.add(lock); + } + return locked; + } + + protected void reportTimeout(Thread thread) throws LockException { + if (hasTimedOut(thread)) { + throw new LockException(LockException.Code.TIMED_OUT); + } + } + + protected boolean hasTimedOut(Thread thread) { + long remainingTime = computeRemainingTime(thread); + return (remainingTime < 0); + + } + + protected void release() { + Set locks = locksForThreads.get(Thread.currentThread()); + // graceful reaction... + if (locks == null) { + return; + } + for (ReentrantLock lock : locks) { + int holdCount = lock.getHoldCount(); + logger.debug("Locks held by this thread: " + holdCount); + while (true) { + try { + lock.unlock(); + } catch (IllegalMonitorStateException imse) { + // We are lacking information on whether we have a read + // lock and if so how many. + // XXX Just free as many as possible. + break; + } + } + } + + locksForThreads.remove(Thread.currentThread()); + } + +} Modified: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/LockManager.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/LockManager.java?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/LockManager.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/locking/LockManager.java Wed Aug 15 05:46:25 2007 @@ -20,7 +20,11 @@ /** * - * @author olli + *

+ * Implementations are free to decide whether they want to make use of the + * exclusive flag passed in + * {@link #lock(Object, Object, boolean)} and + * {@link #tryLock(Object, Object, boolean)}. * * * @@ -51,6 +55,7 @@ * resource for on which this block of work shall be done */ public void lock(M managedResource, K key, boolean exclusive) throws LockException; + public boolean tryLock(M managedResource, K key, boolean exclusive); } Modified: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/BasicTxMap.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/BasicTxMap.java?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/BasicTxMap.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/BasicTxMap.java Wed Aug 15 05:46:25 2007 @@ -27,34 +27,45 @@ import org.apache.commons.transaction.AbstractTransactionalResourceManager; import org.apache.commons.transaction.AbstractTransactionalResourceManager.AbstractTxContext; +import org.apache.commons.transaction.locking.DefaultLockManager; +import org.apache.commons.transaction.locking.LockManager; /** - * Wrapper that adds transactional control to all kinds of maps that implement - * the {@link Map} interface. This wrapper has rather weak isolation, but is - * simply, neven blocks and commits will never fail for logical reasons.
- * Start a transaction by calling {@link #startTransaction()}. Then perform the - * normal actions on the map and finally either call - * {@link #commitTransaction()} to make your changes permanent or - * {@link #rollbackTransaction()} to undo them.
- * Caution: Do not modify values retrieved by {@link #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 - * {@link #put(Object, Object)}.
+ * Map featuring transactional control. + * + *

+ * This implementation has rather weak isolation, but is simple, never blocks + * and commits will never fail for logical reasons. + * + *

* Note: This wrapper guarantees isolation level * READ COMMITTED only. I.e. as soon a value is committed in one * transaction it will be immediately visible in all other concurrent * transactions. * + *

+ * This implementation wraps a map of type {@link ConcurrentHashMap}. All + * internal synchronization is delegated to this class. + * * @see OptimisticTxMap * @see PessimisticTxMap + * @see ConcurrentHashMap */ -public class BasicTxMap extends AbstractTransactionalResourceManager implements - TxMap { +public class BasicTxMap extends AbstractTransactionalResourceManager + implements TxMap { - protected Map wrapped = new ConcurrentHashMap(); + protected final Map wrapped = new ConcurrentHashMap(); + + public BasicTxMap(String name, LockManager lm) { + super(name, lm); + } public BasicTxMap(String name) { - super(name); + this(name, new DefaultLockManager()); + } + + public Map getWrappedMap() { + return wrapped; } // @@ -120,7 +131,7 @@ /** * @see Map#values() */ - public Collection values() { + public Collection values() { MapTxContext txContext = getActiveTx(); @@ -128,11 +139,11 @@ return wrapped.values(); } else { // XXX expensive :( - Collection values = new ArrayList(); - for (Iterator it = keySet().iterator(); it.hasNext();) { - Object key = it.next(); - Object value = get(key); - // XXX we have no isolation, so get entry might have been + Collection values = new ArrayList(); + Set keys = keySet(); + for (K key : keys) { + V value = get(key); + // XXX we have no isolation, so entry might have been // deleted in the meantime if (value != null) { values.add(value); @@ -145,7 +156,7 @@ /** * @see Map#putAll(java.util.Map) */ - public void putAll(Map map) { + public void putAll(Map map) { MapTxContext txContext = getActiveTx(); if (txContext == null) { @@ -161,7 +172,7 @@ /** * @see Map#entrySet() */ - public Set entrySet() { + public Set> entrySet() { MapTxContext txContext = getActiveTx(); if (txContext == null) { return wrapped.entrySet(); @@ -184,7 +195,7 @@ /** * @see Map#keySet() */ - public Set keySet() { + public Set keySet() { MapTxContext txContext = getActiveTx(); if (txContext == null) { @@ -299,7 +310,7 @@ } public class MapTxContext extends AbstractTxContext { - protected Set deletes; + protected Set deletes; protected Map changes; @@ -308,14 +319,14 @@ protected boolean cleared; protected MapTxContext() { - deletes = new HashSet(); + deletes = new HashSet(); changes = new HashMap(); adds = new HashMap(); cleared = false; } - protected Set keys() { - Set keySet = new HashSet(); + protected Set keys() { + Set keySet = new HashSet(); if (!cleared) { keySet.addAll(wrapped.keySet()); keySet.removeAll(deletes); @@ -372,7 +383,7 @@ changes.remove(key); adds.remove(key); if (wrapped.containsKey(key) && !cleared) { - deletes.add(key); + deletes.add((K) key); } } catch (RuntimeException e) { markForRollback(); Modified: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java Wed Aug 15 05:46:25 2007 @@ -16,77 +16,89 @@ */ package org.apache.commons.transaction.memory; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.apache.commons.transaction.locking.DefaultLockManager; import org.apache.commons.transaction.locking.LockException; +import org.apache.commons.transaction.locking.LockManager; /** - * Wrapper that adds transactional control to all kinds of maps that implement - * the {@link Map} interface. By using a naive optimistic transaction control - * this wrapper has better isolation than {@link BasicTxMap}, but - * may also fail to commit. + * Map featuring transactional control. * - *
- * Start a transaction by calling {@link #startTransaction()}. Then perform the - * normal actions on the map and finally either call - * {@link #commitTransaction()} to make your changes permanent or - * {@link #rollbackTransaction()} to undo them.
- * Caution: Do not modify values retrieved by {@link #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 - * {@link #put(Object, Object)}.
- * Note: This wrapper guarantees isolation level - * SERIALIZABLE.
- * Caution: This implementation might be slow when large amounts of + *

By using a naive optimistic transaction control + * this implementation has better isolation than {@link BasicTxMap}, but may also fail + * to commit. + * + *

Caution: This implementation might be slow when large amounts of * data is changed in a transaction as much references will need to be copied * around. * - * @version $Id: OptimisticMapWrapper.java 493628 2007-01-07 01:42:48Z joerg $ + *

This implementation wraps a map of type {@link ConcurrentHashMap}. + * * @see BasicTxMap * @see PessimisticTxMap + * @see ConcurrentHashMap */ public class OptimisticTxMap extends BasicTxMap implements TxMap { - private Set activeTransactions = new HashSet(); + private Set activeTransactions = Collections + .synchronizedSet(new HashSet()); + private ReadWriteLock commitLock = new ReentrantReadWriteLock(); private long commitTimeout = 1000 * 60; // 1 minute private long accessTimeout = 1000 * 30; // 30 seconds + + public OptimisticTxMap(String name) { + this(name, new DefaultLockManager()); + } + + public OptimisticTxMap(String name, LockManager lm) { + super(name, lm); + } + + @Override + public void startTransaction(long timeout, TimeUnit unit) { + super.startTransaction(timeout, unit); + MapTxContext txContext = getActiveTx(); + activeTransactions.add((CopyingTxContext)txContext); + } + + @Override public void rollbackTransaction() { MapTxContext txContext = getActiveTx(); super.rollbackTransaction(); activeTransactions.remove(txContext); } - public OptimisticTxMap(String name) { - super(name); - } - - + @Override public boolean commitTransaction() throws LockException { return commitTransaction(false); } public boolean commitTransaction(boolean force) throws LockException { MapTxContext txContext = getActiveTx(); - + if (txContext == null) { - throw new IllegalStateException( - "Active thread " + Thread.currentThread() + " not associated with a transaction!"); + throw new IllegalStateException("Active thread " + Thread.currentThread() + + " not associated with a transaction!"); } if (txContext.isMarkedForRollback()) { - throw new IllegalStateException("Active thread " + Thread.currentThread() + " is marked for rollback!"); + throw new IllegalStateException("Active thread " + Thread.currentThread() + + " is marked for rollback!"); } - + try { // in this final commit phase we need to be the only one access the // map @@ -99,7 +111,7 @@ throw new LockException(LockException.Code.CONFLICT, conflictKey); } } - + activeTransactions.remove(txContext); copyChangesToConcurrentTransactions(); return super.commitTransaction(); @@ -166,7 +178,7 @@ } } } - + @Override protected CopyingTxContext createContext() { return new CopyingTxContext(); @@ -339,7 +351,7 @@ public void setCommitTimeout(long commitTimeout) { this.commitTimeout = commitTimeout; } - + @Override public boolean commitCanFail() { return true; Modified: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java Wed Aug 15 05:46:25 2007 @@ -18,50 +18,55 @@ package org.apache.commons.transaction.memory; import java.util.Collection; -import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.commons.transaction.locking.DefaultLockManager; +import org.apache.commons.transaction.locking.LockManager; /** - * Wrapper that adds transactional control to all kinds of maps that implement - * the {@link Map} interface. By using pessimistic transaction control (blocking - * locks) this wrapper has better isolation than {@link BasicTxMap}, - * but also has less possible concurrency and may even deadlock. A commit, - * however, will never fail.
- * Start a transaction by calling {@link #startTransaction()}. Then perform the - * normal actions on the map and finally either call - * {@link #commitTransaction()} to make your changes permanent or - * {@link #rollbackTransaction()} to undo them.
- * Caution: Do not modify values retrieved by {@link #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 - * {@link #put(Object, Object)}.
- * Note: This wrapper guarantees isolation level - * SERIALIZABLE. + * Map featuring transactional control. + * + *

+ * By using pessimistic transaction control (blocking locks) this wrapper has + * better isolation than {@link BasicTxMap}, but also has less possible + * concurrency and may even deadlock. A commit, however, will never fail. + * + *

+ * Caution:Some operations that would require global locks (e.g. + * size() or clear() or not properly isolated as + * this implementation does not support global locks. + * + *

+ * This implementation wraps a map of type {@link ConcurrentHashMap}. * - * @version $Id: PessimisticMapWrapper.java 493628 2007-01-07 01:42:48Z joerg $ * @see BasicTxMap * @see OptimisticTxMap + * @see ConcurrentHashMap */ public class PessimisticTxMap extends BasicTxMap implements TxMap { - protected static final Object GLOBAL_LOCK = "GLOBAL"; + private ReadWriteLock globalLock = new ReentrantReadWriteLock(); public PessimisticTxMap(String name) { - super(name); + this(name, new DefaultLockManager()); + } + + public PessimisticTxMap(String name, LockManager lm) { + super(name, lm); } public Collection values() { - assureGlobalReadLock(); return super.values(); } public Set entrySet() { - assureGlobalReadLock(); return super.entrySet(); } public Set keySet() { - assureGlobalReadLock(); return super.keySet(); } @@ -86,15 +91,6 @@ if (txContext != null) { txContext.writeLock(key); // XXX fake intention lock (prohibits global WRITE) - txContext.readLock(GLOBAL_LOCK); - } - } - - protected void assureGlobalReadLock() { - LockingTxContext txContext = (LockingTxContext) getActiveTx(); - if (txContext != null) { - // XXX fake intention lock (prohibits global WRITE) - txContext.readLock(GLOBAL_LOCK); } } @@ -106,45 +102,34 @@ public class LockingTxContext extends MapTxContext { protected Set keys() { - readLock(GLOBAL_LOCK); return super.keys(); } protected V get(Object key) { readLock(key); - // XXX fake intention lock (prohibits global WRITE) - readLock(GLOBAL_LOCK); return super.get(key); } protected void put(K key, V value) { writeLock(key); - // XXX fake intention lock (prohibits global WRITE) - readLock(GLOBAL_LOCK); super.put(key, value); } protected void remove(Object key) { writeLock(key); - // XXX fake intention lock (prohibits global WRITE) - readLock(GLOBAL_LOCK); super.remove(key); } protected int size() { - // XXX this is bad luck, we need a global read lock just for the - // size :( :( :( - readLock(GLOBAL_LOCK); return super.size(); } protected void clear() { - writeLock(GLOBAL_LOCK); super.clear(); } } - + @Override public boolean commitCanFail() { return false; Modified: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java Wed Aug 15 05:46:25 2007 @@ -19,6 +19,31 @@ import java.util.Map; import org.apache.commons.transaction.ManageableResourceManager; +import org.apache.commons.transaction.TransactionalResourceManager; +/** + * Interface for a map that features transactional support. + * + *

Start a transaction by calling {@link TransactionalResourceManager#startTransaction(long, java.util.concurrent.TimeUnit)}. Then perform the + * normal actions on the map and finally either call + * {@link TransactionalResourceManager#commitTransaction()} to make your changes permanent or + * {@link TransactionalResourceManager#rollbackTransaction()} to undo them. + * + *

Caution: Do not modify values retrieved by {@link Map#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 + * {@link Map#put(Object, Object)}.
+ * + * @see BasicTxMap + * @see OptimisticTxMap + * @see PessimisticTxMap + * + */ public interface TxMap extends Map, ManageableResourceManager { + /** + * Gets the underlying map that is wrapped by this transactional implementation. + * + * @return the wrapped map + */ + Map getWrappedMap(); } Modified: commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/package.html URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/package.html?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/package.html (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/package.html Wed Aug 15 05:46:25 2007 @@ -4,339 +4,17 @@

Working on maps as if they were databases.

-

One major part of the Transaction Component are a set of wrappers that allow to add transactional control -to any kind of map implemening the java.util.Map interface. Transactional control refers +

The implementation of this package add transactional control to the java.util.Map interface. Transactional control refers to the procedure that you start a transaction do a number of statements and finally decide to either commit the changes, i.e. make them permanent, or rather roll back the transaction by discarding all your changes.

-

As an example imagine a client application where you do complex stuff (maybe -talking to one or more servers) and the user is allowed to cancel the -whole operation. Or it might be needed to be canceled because of errors that occur in the middle of the whole request. -Now image all the results have been stored in a transactional map and you -simply do a rollback on errors and a commit upon success. -

- -

The whole thing gets a bit more complicated when more than one transaction is executed on a map at the same time. - In this case these transactions are running concurrently. +

You are free to have any number of transactions running on such a map at the same time. + In this case these transactions are said to be running concurrently. Problems that might occur with concurrent transaction are diverse and implementations differ in how much - spurious phenomena can be observed. Which phenomenon can occur with which transactional map implementation can be found in the chart a little bit down this page. Finally, there is a + spurious phenomena can be observed. Finally, there is a great more general article about different isolations.

-

If none of -the known phenomena is observable - which of course is the best you can get - a transaction is called serializable. - The origin of this term comes from a differet, but equivalent definition. This -definition switches the perspective from local phenomena observed inside -transactions to global one, more specific global corretctness. If a set of -transactions are executed -concurrently and there is a strictly sequential execution schedule of those transactions that leaves the -map in the same state as the concurrent one those transactions are called -serializable. -

- -

Comparision of transactional map implementations

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TransactionalMapWrapper OptimisticMapWrapper PessimisticMapWrapper
- -

Lost update

- -
- -

Possible

-
- -

Not possible

-
- -

Not possible

-
- -

Dirty write

- -
- -

Not possible

-
- -

Not possible

-
- -

Not possible

-
- -

Dirty read

-
- - -

Not possible

-
- -

Not possible

-
- -

Not possible

-
- -

Lost update

- -
- -

Possible

-
- -

Not possible

-
- -

Not possible

-
- -

Nonrepeatable read

-
- -

Possible

- -
- -

Not possible

-
- -

Not possible

-
- -

Phantoms

-
- -

Possible

-
- - -

Not possible

-
- - -

Not possible

-
- -

Read Skew

- -
- -

Possible

-
- -

Not possible

-
- -

Not possible

-
- -

Write Skew

- -
- -

Possible

-
- -

Possible

-
- -

Not possible

-
- - -

Readers block writers

-
- -

No

-
- -

No

-
- -

Yes

-
- -

Writers block readers

- -
- -

No

-
- -

No

-
- -

Yes

-
- -

Writers block writers

-
- -

No

- -
- -

No

- -
- -

Yes

-
- -

Might deadlock

-
- -

No

- -
- -

No

- -
- -

Yes

-
- -

Commit might fail

-
- -

No

- -
- -

Yes

- -
- -

No

-
- -

Isolation Level

- -
- -

Read Committed (with lost updates possible)

-
- -

Snapshot (Oracle would call it Serializable)

-
- -

Serializable

-
- - Modified: commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/DefaultTransactionTest.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/DefaultTransactionTest.java?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/DefaultTransactionTest.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/DefaultTransactionTest.java Wed Aug 15 05:46:25 2007 @@ -20,8 +20,6 @@ import junit.framework.JUnit4TestAdapter; -import org.apache.commons.transaction.locking.LockManager; -import org.apache.commons.transaction.locking.RWLockManager; import org.apache.commons.transaction.memory.PessimisticTxMap; import org.apache.commons.transaction.memory.TxMap; import org.junit.Test; @@ -33,8 +31,7 @@ @Test public void basic() { - LockManager lm = new RWLockManager(); - Transaction t = new DefaultTransaction(lm); + Transaction t = new DefaultTransaction(); TxMap txMap1 = new PessimisticTxMap("TxMap1"); t.enlistResourceManager(txMap1); TxMap txMap2 = new PessimisticTxMap("TxMap2"); Modified: commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/file/TxFileResourceManagerTest.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/file/TxFileResourceManagerTest.java?view=diff&rev=566128&r1=566127&r2=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/file/TxFileResourceManagerTest.java (original) +++ commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/file/TxFileResourceManagerTest.java Wed Aug 15 05:46:25 2007 @@ -25,12 +25,12 @@ import java.util.concurrent.TimeUnit; import junit.framework.JUnit4TestAdapter; -import static junit.framework.Assert.fail; +import static junit.framework.Assert.*; +import org.junit.Test; import org.apache.commons.transaction.file.FileResourceManager.FileResource; +import org.apache.commons.transaction.locking.DefaultLockManager; import org.apache.commons.transaction.locking.LockManager; -import org.apache.commons.transaction.locking.RWLockManager; -import org.junit.Test; public class TxFileResourceManagerTest { @@ -168,16 +168,12 @@ } } - - @Test public void basic() { TxFileResourceManager manager = new TxFileResourceManager("TxFileManager", "d:/tmp/content"); - LockManager lm = new RWLockManager(); FileResourceUndoManager um; try { um = new MemoryUndoManager("d:/tmp/txlogs"); - manager.setLm(lm); manager.setUndoManager(um); manager.startTransaction(60, TimeUnit.SECONDS); FileResource file = manager.getResource("d:/tmp/content/aha"); @@ -195,7 +191,7 @@ } - public static void main(String[] args) { - new TxFileResourceManagerTest().basic(); + public static void main(java.lang.String[] args) { + junit.textui.TestRunner.run(suite()); } } Added: commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/BasicTxMapTest.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/BasicTxMapTest.java?view=auto&rev=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/BasicTxMapTest.java (added) +++ commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/BasicTxMapTest.java Wed Aug 15 05:46:25 2007 @@ -0,0 +1,345 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import junit.framework.JUnit4TestAdapter; +import org.junit.Test; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.transaction.locking.LockManager; +import org.apache.commons.transaction.util.RendezvousBarrier; + +/** + * Tests for basic tx map. + * + */ +public class BasicTxMapTest { + + private static final Log log = LogFactory.getLog(BasicTxMapTest.class.getName()); + + protected static final long BARRIER_TIMEOUT = 20000; + + // XXX need this, as JUnit seems to print only part of these strings + protected static void report(String should, String is) { + if (!should.equals(is)) { + fail("\nWrong output:\n'" + is + "'\nShould be:\n'" + should + "'\n"); + } + } + + protected static void checkCollection(Collection col, Object[] values) { + int cnt = 0; + int trueCnt = 0; + + for (Iterator it = col.iterator(); it.hasNext();) { + cnt++; + Object value1 = it.next(); + for (int i = 0; i < values.length; i++) { + Object value2 = values[i]; + if (value2.equals(value1)) + trueCnt++; + } + } + assertEquals(cnt, values.length); + assertEquals(trueCnt, values.length); + } + + public static junit.framework.Test suite() { + return new JUnit4TestAdapter(BasicTxMapTest.class); + } + + public static void main(java.lang.String[] args) { + junit.textui.TestRunner.run(suite()); + } + + @Test + public void testBasic() { + + log.info("Checking basic transaction features"); + + final BasicTxMap txMap1 = new BasicTxMap("txMap1"); + final Map map1 = txMap1.getWrappedMap(); + + assertTrue(txMap1.isEmpty()); + + // make sure changes are propagated to wrapped map outside tx + txMap1.put("key1", "value1"); + report("value1", (String) map1.get("key1")); + assertFalse(txMap1.isEmpty()); + + // make sure changes are propagated to wrapped map only after commit + txMap1.startTransaction(1, TimeUnit.HOURS); + assertFalse(txMap1.isEmpty()); + 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 roll back + txMap1.startTransaction(1, TimeUnit.HOURS); + txMap1.put("key1", "value3"); + txMap1.rollbackTransaction(); + report("value2", (String) map1.get("key1")); + report("value2", (String) txMap1.get("key1")); + } + + @Test + public void testComplex() { + + log.info("Checking advanced and complex transaction features"); + + final BasicTxMap txMap1 = new BasicTxMap("txMap1"); + final Map map1 = txMap1.getWrappedMap(); + + // first fill in some global values: + txMap1.put("key1", "value1"); + txMap1.put("key2", "value2"); + + // let's see if we have all values: + log.info("Checking if global values are present"); + + assertTrue(txMap1.containsValue("value1")); + assertTrue(txMap1.containsValue("value2")); + assertFalse(txMap1.containsValue("novalue")); + + // ... and all keys + log.info("Checking if global keys are present"); + assertTrue(txMap1.containsKey("key1")); + assertTrue(txMap1.containsKey("key2")); + assertFalse(txMap1.containsKey("nokey")); + + // and now some inside a transaction + txMap1.startTransaction(1, TimeUnit.HOURS); + txMap1.put("key3", "value3"); + txMap1.put("key4", "value4"); + + // let's see if we have all values: + log.info("Checking if values inside transactions are present"); + assertTrue(txMap1.containsValue("value1")); + assertTrue(txMap1.containsValue("value2")); + assertTrue(txMap1.containsValue("value3")); + assertTrue(txMap1.containsValue("value4")); + assertFalse(txMap1.containsValue("novalue")); + + // ... and all keys + log.info("Checking if keys inside transactions are present"); + assertTrue(txMap1.containsKey("key1")); + assertTrue(txMap1.containsKey("key2")); + assertTrue(txMap1.containsKey("key3")); + assertTrue(txMap1.containsKey("key4")); + assertFalse(txMap1.containsKey("nokey")); + + // now let's delete some old stuff + log.info("Checking remove inside transactions"); + txMap1.remove("key1"); + assertFalse(txMap1.containsKey("key1")); + assertFalse(txMap1.containsValue("value1")); + assertNull(txMap1.get("key1")); + assertEquals(3, txMap1.size()); + + // and some newly created + txMap1.remove("key3"); + assertFalse(txMap1.containsKey("key3")); + assertFalse(txMap1.containsValue("value3")); + assertNull(txMap1.get("key3")); + assertEquals(2, txMap1.size()); + + log.info("Checking remove and propagation after commit"); + txMap1.commitTransaction(); + + txMap1.remove("key1"); + assertFalse(txMap1.containsKey("key1")); + assertFalse(txMap1.containsValue("value1")); + assertNull(txMap1.get("key1")); + assertFalse(txMap1.containsKey("key3")); + assertFalse(txMap1.containsValue("value3")); + assertNull(txMap1.get("key3")); + assertEquals(2, txMap1.size()); + } + + @Test + public void testSets() { + + log.info("Checking set opertaions"); + + final BasicTxMap txMap1 = new BasicTxMap("txMap1"); + final Map map1 = txMap1.getWrappedMap(); + + // first fill in some global values: + txMap1.put("key1", "value1"); + txMap1.put("key2", "value200"); + + // and now some inside a transaction + txMap1.startTransaction(1, TimeUnit.HOURS); + txMap1.put("key2", "value2"); // modify + txMap1.put("key3", "value3"); + txMap1.put("key4", "value4"); + + // check entry set + boolean key1P, key2P, key3P, key4P; + key1P = key2P = key3P = key4P = false; + int cnt = 0; + for (Iterator it = txMap1.entrySet().iterator(); it.hasNext();) { + cnt++; + Map.Entry entry = (Map.Entry) it.next(); + if (entry.getKey().equals("key1") && entry.getValue().equals("value1")) + key1P = true; + else if (entry.getKey().equals("key2") && entry.getValue().equals("value2")) + key2P = true; + else if (entry.getKey().equals("key3") && entry.getValue().equals("value3")) + key3P = true; + else if (entry.getKey().equals("key4") && entry.getValue().equals("value4")) + key4P = true; + } + assertEquals(cnt, 4); + assertTrue(key1P && key2P && key3P && key4P); + + checkCollection(txMap1.values(), new String[] { "value1", "value2", "value3", "value4" }); + checkCollection(txMap1.keySet(), new String[] { "key1", "key2", "key3", "key4" }); + + txMap1.commitTransaction(); + + // check again after commit (should be the same) + key1P = key2P = key3P = key4P = false; + cnt = 0; + for (Iterator it = txMap1.entrySet().iterator(); it.hasNext();) { + cnt++; + Map.Entry entry = (Map.Entry) it.next(); + if (entry.getKey().equals("key1") && entry.getValue().equals("value1")) + key1P = true; + else if (entry.getKey().equals("key2") && entry.getValue().equals("value2")) + key2P = true; + else if (entry.getKey().equals("key3") && entry.getValue().equals("value3")) + key3P = true; + else if (entry.getKey().equals("key4") && entry.getValue().equals("value4")) + key4P = true; + } + assertEquals(cnt, 4); + assertTrue(key1P && key2P && key3P && key4P); + + checkCollection(txMap1.values(), new String[] { "value1", "value2", "value3", "value4" }); + checkCollection(txMap1.keySet(), new String[] { "key1", "key2", "key3", "key4" }); + + // now try clean + + txMap1.startTransaction(1, TimeUnit.HOURS); + + // add + txMap1.put("key5", "value5"); + // modify + txMap1.put("key4", "value400"); + // delete + txMap1.remove("key1"); + + assertEquals(txMap1.size(), 4); + + txMap1.clear(); + assertEquals(txMap1.size(), 0); + assertEquals(map1.size(), 4); + + // add + txMap1.put("key5", "value5"); + // delete + txMap1.remove("key1"); + + // adding one, not removing anything gives size 1 + assertEquals(txMap1.size(), 1); + assertEquals(map1.size(), 4); + assertNull(txMap1.get("key4")); + assertNotNull(txMap1.get("key5")); + + txMap1.commitTransaction(); + + // after commit clear must have been propagated to wrapped map: + assertEquals(txMap1.size(), 1); + assertEquals(map1.size(), 1); + assertNull(txMap1.get("key4")); + assertNotNull(txMap1.get("key5")); + assertNull(map1.get("key4")); + assertNotNull(map1.get("key5")); + } + + @Test + public void testMulti() { + log.info("Checking concurrent transaction features"); + + final BasicTxMap txMap1 = new BasicTxMap("txMap1"); + final Map map1 = txMap1.getWrappedMap(); + + final RendezvousBarrier beforeCommitBarrier = new RendezvousBarrier("Before Commit", 2, + BARRIER_TIMEOUT); + + final RendezvousBarrier afterCommitBarrier = new RendezvousBarrier("After Commit", 2, + BARRIER_TIMEOUT); + + Thread thread1 = new Thread(new Runnable() { + public void run() { + txMap1.startTransaction(1, TimeUnit.HOURS); + try { + beforeCommitBarrier.meet(); + txMap1.put("key1", "value2"); + txMap1.commitTransaction(); + afterCommitBarrier.call(); + } catch (InterruptedException e) { + log.warn("Thread interrupted", e); + afterCommitBarrier.reset(); + beforeCommitBarrier.reset(); + } + } + }, "Thread1"); + + txMap1.put("key1", "value1"); + + txMap1.startTransaction(1, TimeUnit.HOURS); + thread1.start(); + + report("value1", (String) txMap1.get("key1")); + beforeCommitBarrier.call(); + try { + afterCommitBarrier.meet(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + // we have read committed as isolation level, that's why I will see the + // new value of the other thread now + report("value2", (String) txMap1.get("key1")); + + // now when I override it it should of course be my value again + txMap1.put("key1", "value3"); + report("value3", (String) txMap1.get("key1")); + + // after rollback it must be the value written by the other thread again + txMap1.rollbackTransaction(); + report("value2", (String) txMap1.get("key1")); + } + +} Added: commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/OptimisticTxMapTest.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/OptimisticTxMapTest.java?view=auto&rev=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/OptimisticTxMapTest.java (added) +++ commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/OptimisticTxMapTest.java Wed Aug 15 05:46:25 2007 @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 java.util.Map; +import java.util.concurrent.TimeUnit; + +import junit.framework.JUnit4TestAdapter; +import static junit.framework.Assert.*; +import org.junit.Test; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.commons.transaction.locking.LockException; +import org.apache.commons.transaction.util.RendezvousBarrier; + +/** + * Tests for optimistic tx map. + * + */ +public class OptimisticTxMapTest extends BasicTxMapTest { + + private static final Log log = LogFactory.getLog(OptimisticTxMapTest.class.getName()); + + public static junit.framework.Test suite() { + return new JUnit4TestAdapter(OptimisticTxMapTest.class); + } + + public static void main(java.lang.String[] args) { + junit.textui.TestRunner.run(suite()); + } + + @Test + public void testMulti() { + log.info("Checking concurrent transaction features"); + + final OptimisticTxMap txMap1 = new OptimisticTxMap("txMap1"); + final Map map1 = txMap1.getWrappedMap(); + + final RendezvousBarrier beforeCommitBarrier = new RendezvousBarrier("Before Commit", 2, + BARRIER_TIMEOUT); + + final RendezvousBarrier afterCommitBarrier = new RendezvousBarrier("After Commit", 2, + BARRIER_TIMEOUT); + + Thread thread1 = new Thread(new Runnable() { + public void run() { + txMap1.startTransaction(1, TimeUnit.HOURS); + try { + beforeCommitBarrier.meet(); + txMap1.put("key1", "value2"); + txMap1.commitTransaction(); + afterCommitBarrier.call(); + } catch (InterruptedException e) { + log.warn("Thread interrupted", e); + afterCommitBarrier.reset(); + beforeCommitBarrier.reset(); + } + } + }, "Thread1"); + + txMap1.put("key1", "value1"); + + txMap1.startTransaction(1, TimeUnit.HOURS); + thread1.start(); + + report("value1", (String) txMap1.get("key1")); + beforeCommitBarrier.call(); + try { + afterCommitBarrier.meet(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + // 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")); + } + + @Test + public void testConflict() { + log.info("Checking concurrent transaction features"); + + final OptimisticTxMap txMap1 = new OptimisticTxMap("txMap1"); + final Map map1 = txMap1.getWrappedMap(); + + final RendezvousBarrier beforeCommitBarrier = new RendezvousBarrier("Before Commit", 2, + BARRIER_TIMEOUT); + + final RendezvousBarrier afterCommitBarrier = new RendezvousBarrier("After Commit", 2, + BARRIER_TIMEOUT); + + Thread thread1 = new Thread(new Runnable() { + public void run() { + txMap1.startTransaction(1, TimeUnit.HOURS); + try { + beforeCommitBarrier.meet(); + txMap1.put("key1", "value2"); + txMap1.commitTransaction(); + afterCommitBarrier.call(); + } catch (InterruptedException e) { + log.warn("Thread interrupted", e); + afterCommitBarrier.reset(); + beforeCommitBarrier.reset(); + } + } + }, "Thread1"); + + txMap1.put("key1", "value1"); + + txMap1.startTransaction(1, TimeUnit.HOURS); + thread1.start(); + + report("value1", (String) txMap1.get("key1")); + beforeCommitBarrier.call(); + try { + afterCommitBarrier.meet(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + // 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")); + + boolean conflict = false; + + try { + txMap1.commitTransaction(); + } catch (LockException ce) { + conflict = true; + } + + assertTrue(conflict); + // after failed commit it must be the value written by the other thread + report("value2", (String) map1.get("key1")); + + // force commit anyhow... + txMap1.commitTransaction(true); + // after successful commit it must be the value written by this thread + report("value3", (String) txMap1.get("key1")); + report("value3", (String) map1.get("key1")); + } + +} \ No newline at end of file Added: commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/PessimisticTxMapTest.java URL: http://svn.apache.org/viewvc/commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/PessimisticTxMapTest.java?view=auto&rev=566128 ============================================================================== --- commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/PessimisticTxMapTest.java (added) +++ commons/proper/transaction/branches/TRANSACTION_2/src/test/org/apache/commons/transaction/memory/PessimisticTxMapTest.java Wed Aug 15 05:46:25 2007 @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 java.util.Map; +import java.util.concurrent.TimeUnit; + +import junit.framework.JUnit4TestAdapter; +import static junit.framework.Assert.*; +import org.junit.Test; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.commons.transaction.locking.LockException; +import org.apache.commons.transaction.util.RendezvousBarrier; + +/** + * Tests for map wrapper. + */ +public class PessimisticTxMapTest extends BasicTxMapTest { + + private static final Log log = LogFactory.getLog(PessimisticTxMapTest.class.getName()); + + protected static final long TIMEOUT = Long.MAX_VALUE; + + private static int deadlockCnt = 0; + + public static junit.framework.Test suite() { + return new JUnit4TestAdapter(PessimisticTxMapTest.class); + } + + public static void main(java.lang.String[] args) { + junit.textui.TestRunner.run(suite()); + } + + @Test + public void testMulti() { + log.info("Checking concurrent transaction features"); + + final PessimisticTxMap txMap1 = new PessimisticTxMap( + "txMap1"); + final Map map1 = txMap1.getWrappedMap(); + + Thread thread1 = new Thread(new Runnable() { + public void run() { + txMap1.startTransaction(5, TimeUnit.MINUTES); + txMap1.put("key1", "value2"); + synchronized (txMap1) { + txMap1.commitTransaction(); + report("value2", (String) txMap1.get("key1")); + } + } + }, "Thread1"); + + txMap1.put("key1", "value1"); + + txMap1.startTransaction(5, TimeUnit.MINUTES); + + report("value1", (String) txMap1.get("key1")); + + thread1.start(); + + // we have serializable as isolation level, that's why I will still see + // the old value + report("value1", (String) txMap1.get("key1")); + + txMap1.put("key1", "value3"); + + // after commit it must be our value + synchronized (txMap1) { + txMap1.commitTransaction(); + report("value3", (String) txMap1.get("key1")); + } + try { + thread1.join(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +}