commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ozeigerm...@apache.org
Subject cvs commit: jakarta-commons/transaction/src/test/org/apache/commons/transaction/locking GenericLockTest.java
Date Tue, 14 Dec 2004 12:12:47 GMT
ozeigermann    2004/12/14 04:12:47

  Modified:    transaction/src/java/org/apache/commons/transaction/locking
                        LockManager.java GenericLockManager.java
                        GenericLock.java
               transaction/src/test/org/apache/commons/transaction/file
                        FileResourceManagerTest.java
               transaction/src/java/org/apache/commons/transaction/memory
                        PessimisticMapWrapper.java
               transaction/src/java/org/apache/commons/transaction/file
                        ResourceManagerErrorCodes.java ResourceManager.java
                        FileResourceManager.java
               transaction/src/test/org/apache/commons/transaction/memory
                        PessimisticMapWrapperTest.java
               transaction/src/test/org/apache/commons/transaction/locking
                        GenericLockTest.java
  Added:       transaction/src/java/org/apache/commons/transaction/locking
                        ReadWriteLockManager.java LockException.java
  Removed:     transaction/src/java/org/apache/commons/transaction/memory
                        LockException.java
  Log:
  Added deadlock detection.
  
  Revision  Changes    Path
  1.2       +120 -4    jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/LockManager.java
  
  Index: LockManager.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/LockManager.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- LockManager.java	18 Nov 2004 23:27:17 -0000	1.1
  +++ LockManager.java	14 Dec 2004 12:12:46 -0000	1.2
  @@ -23,6 +23,8 @@
   
   package org.apache.commons.transaction.locking;
   
  +import java.util.Set;
  +
   /**
    * 
    * A manager for multi level locks on resources. Encapsulates creation, removal, and retrieval of locks.
  @@ -36,11 +38,117 @@
   public interface LockManager {
   
       /**
  +     * Tries to acquire a lock on a resource. <br>
  +     * <br>
  +     * This method does not block, but immediatly returns. If a lock is not
  +     * available <code>false</code> will be returned.
  +     * 
  +     * @param ownerId
  +     *            a unique id identifying the entity that wants to acquire this
  +     *            lock
  +     * @param resourceId
  +     *            the resource to get the level for
  +     * @param targetLockLevel
  +     *            the lock level to acquire
  +     * @param reentrant
  +     *            <code>true</code> if this request shall not be blocked by
  +     *            other locks held by the same owner
  +     * @return <code>true</code> if the lock has been acquired, <code>false</code> otherwise
  +     *  
  +     */
  +    public boolean tryLock(Object ownerId, Object resourceId, int targetLockLevel, boolean reentrant);
  +
  +    /**
  +     * Tries to acquire a lock on a resource. <br>
  +     * <br>
  +     * This method blocks and waits for the lock in case it is not avaiable. If
  +     * there is a timeout or a deadlock or the thread is interrupted a
  +     * LockException is thrown.
  +     * 
  +     * @param ownerId
  +     *            a unique id identifying the entity that wants to acquire this
  +     *            lock
  +     * @param resourceId
  +     *            the resource to get the level for
  +     * @param targetLockLevel
  +     *            the lock level to acquire
  +     * @param reentrant
  +     *            <code>true</code> if this request shall not be blocked by
  +     *            other locks held by the same owner
  +     * @throws LockException
  +     *             will be thrown when the lock can not be acquired
  +     */
  +    public void lock(Object ownerId, Object resourceId, int targetLockLevel, boolean reentrant)
  +            throws LockException;
  +
  +    /**
  +     * Tries to acquire a lock on a resource. <br>
  +     * <br>
  +     * This method blocks and waits for the lock in case it is not avaiable. If
  +     * there is a timeout or a deadlock or the thread is interrupted a
  +     * LockException is thrown.
  +     * 
  +     * @param ownerId
  +     *            a unique id identifying the entity that wants to acquire this
  +     *            lock
  +     * @param resourceId
  +     *            the resource to get the level for
  +     * @param targetLockLevel
  +     *            the lock level to acquire
  +     * @param reentrant
  +     *            <code>true</code> if this request shall not be blocked by
  +     *            other locks held by the same owner
  +     * @param timeoutMSecs
  +     *            specifies the maximum wait time in milliseconds
  +     * @throws LockException
  +     *             will be thrown when the lock can not be acquired
  +     */
  +    public void lock(Object ownerId, Object resourceId, int targetLockLevel, boolean reentrant,
  +            long timeoutMSecs) throws LockException;
  +
  +    /**
  +     * Gets the lock level held by certain owner on a certain resource.
  +     * 
  +     * @param ownerId the id of the owner of the lock
  +     * @param resourceId the resource to get the level for
  +     */
  +    public int getLevel(Object ownerId, Object resourceId);
  +
  +    /**
  +     * Releases all locks for a certain resource held by a certain owner.
  +     * 
  +     * @param ownerId the id of the owner of the lock
  +     * @param resourceId the resource to releases the lock for
  +     */
  +    public void release(Object ownerId, Object resourceId);
  +
  +    /**
  +     * Releases all locks (partially) held by an owner.
  +     * 
  +     * @param ownerId the id of the owner
  +     */
  +    public void releaseAll(Object ownerId);
  +    
  +    /**
  +     * Gets all locks (partially) held by an owner.
  +     * 
  +     * @param ownerId the id of the owner
  +     * @return all locks held by ownerId
  +     */
  +    public Set getAll(Object ownerId);
  +
  +    
  +    /**
        * Either gets an existing lock on the specified resource or creates one if none exists. 
        * This methods guarantees to do this atomically. 
        * 
        * @param resourceId the resource to get or create the lock on
        * @return the lock for the specified resource
  +     * 
  +     * @deprecated Direct access to locks is discouraged as dead lock detection and lock
  +     * maintenance now relies on the call to {@link #lock(Object, Object, int, int, long)}, 
  +     * {@link #tryLock(Object, Object, int, int)}, {@link #release(Object, Object)} and 
  +     * {@link #releaseAll(Object)}. This method will be deleted. 
        */
       public MultiLevelLock atomicGetOrCreateLock(Object resourceId);
   
  @@ -49,11 +157,19 @@
        * 
        * @param resourceId the resource to get the lock for
        * @return the lock on the specified resource
  +     * 
  +     * @deprecated Direct access to locks is discouraged as dead lock detection and lock
  +     * maintenance now relies on the call to {@link #lock(Object, Object, int, int, long)}, 
  +     * {@link #tryLock(Object, Object, int, int)}, {@link #release(Object, Object)} and 
  +     * {@link #releaseAll(Object)}. This method will be deleted. 
        */
       public MultiLevelLock getLock(Object resourceId);
   
       /**
        * Removes the specified lock from the associated resource. 
  +     * 
  +     * <em>Caution:</em> This does not release the lock, but only moves it out
  +     * of the scope of this manager. Use {@link #release(Object, Object)} for that.
        * 
        * @param lock the lock to be removed
        */
  
  
  
  1.2       +208 -9    jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/GenericLockManager.java
  
  Index: GenericLockManager.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/GenericLockManager.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- GenericLockManager.java	18 Nov 2004 23:27:17 -0000	1.1
  +++ GenericLockManager.java	14 Dec 2004 12:12:46 -0000	1.2
  @@ -24,8 +24,12 @@
   package org.apache.commons.transaction.locking;
   
   import java.util.Collection;
  +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 org.apache.commons.transaction.util.LoggerFacade;
   
  @@ -36,18 +40,213 @@
    */
   public class GenericLockManager implements LockManager {
   
  +    public static final long DEFAULT_TIMEOUT = 30000;
  +    
  +    /** Maps lock to ownerIds waiting for it. */
  +    protected Map waitsForLock = Collections.synchronizedMap(new HashMap());
  +
  +    /** Maps onwerId to locks it (partially) owns. */
  +    protected Map globalOwners = Collections.synchronizedMap(new HashMap());
  +
  +    /** Maps resourceId to lock. */
       protected Map globalLocks = new HashMap();
  +    
       protected int maxLockLevel = -1;
       protected LoggerFacade logger;
  -
  -    public GenericLockManager(int maxLockLevel, LoggerFacade logger) throws IllegalArgumentException {
  -       if (maxLockLevel < 1)
  -           throw new IllegalArgumentException(
  -               "The maximum lock level must be at least 1 (" + maxLockLevel + " was specified)");
  +    protected long globalTimeoutMSecs;
  +    
  +    /**
  +     * Creates a new generic lock manager.
  +     * 
  +     * @param maxLockLevel
  +     *            highest allowed lock level as described in {@link GenericLock}'s class intro
  +     * @param logger
  +     *            generic logger used for all kind of debug logging
  +     * @param timeoutMSecs
  +     *            specifies the maximum time to wait for a lock in milliseconds
  +     * @throws IllegalArgumentException
  +     *             if maxLockLevel is less than 1
  +     */
  +    public GenericLockManager(int maxLockLevel, LoggerFacade logger, long timeoutMSecs)
  +            throws IllegalArgumentException {
  +        if (maxLockLevel < 1)
  +            throw new IllegalArgumentException("The maximum lock level must be at least 1 ("
  +                    + maxLockLevel + " was specified)");
           this.maxLockLevel = maxLockLevel;
           this.logger = logger.createLogger("Locking");
  +        this.globalTimeoutMSecs = timeoutMSecs;
  +    }
  +
  +    public GenericLockManager(int maxLockLevel, LoggerFacade logger)
  +            throws IllegalArgumentException {
  +        this(maxLockLevel, logger, DEFAULT_TIMEOUT);
  +    }
  +
  +    /**
  +     * @see LockManager#tryLock(Object, Object, int, boolean)
  +     */
  +    public boolean tryLock(Object ownerId, Object resourceId, int targetLockLevel, boolean reentrant) {
  +        GenericLock lock = (GenericLock) atomicGetOrCreateLock(resourceId);
  +        boolean acquired = lock.tryLock(ownerId, targetLockLevel,
  +                reentrant ? GenericLock.COMPATIBILITY_REENTRANT : GenericLock.COMPATIBILITY_NONE);
  +        if (acquired) {
  +            addOwner(ownerId, lock);
  +        }
  +        return acquired;
  +    }
  +
  +    /**
  +     * @see LockManager#lock(Object, Object, int, boolean)
  +     */
  +    public void lock(Object ownerId, Object resourceId, int targetLockLevel, boolean reentrant)
  +            throws LockException {
  +        lock(ownerId, resourceId, targetLockLevel, reentrant, globalTimeoutMSecs);
  +    }
  +
  +    /**
  +     * @see LockManager#lock(Object, Object, int, boolean, long)
  +     */
  +    public void lock(Object ownerId, Object resourceId, int targetLockLevel, boolean reentrant,
  +            long timeoutMSecs) throws LockException {
  +
  +        GenericLock lock = (GenericLock) atomicGetOrCreateLock(resourceId);
  +
  +        boolean deadlock = wouldDeadlock(ownerId, lock, targetLockLevel,
  +                reentrant ? GenericLock.COMPATIBILITY_REENTRANT : GenericLock.COMPATIBILITY_NONE);
  +        if (deadlock) {
  +            throw new LockException("Lock would cause deadlock",
  +                    LockException.CODE_DEADLOCK_VICTIM, resourceId);
  +        }
  +
  +        addWaiter(lock, ownerId);
  +        try {
  +            boolean acquired = lock
  +                    .acquire(ownerId, targetLockLevel, true, reentrant, timeoutMSecs);
  +            if (!acquired) {
  +                throw new LockException("Lock wait timed out", LockException.CODE_TIMED_OUT,
  +                        resourceId);
  +            } else {
  +                addOwner(ownerId, lock);
  +            }
  +        } catch (InterruptedException e) {
  +            throw new LockException("Interrupted", LockException.CODE_INTERRUPTED, resourceId);
  +        } finally {
  +            removeWaiter(lock, ownerId);
  +        }
  +    }
  +
  +    /**
  +     * @see LockManager#getLevel(Object, Object)
  +     */
  +    public int getLevel(Object ownerId, Object resourceId) {
  +        GenericLock lock = (GenericLock) getLock(resourceId);
  +        if (lock != null) {
  +            return lock.getLockLevel(ownerId);
  +        } else {
  +            return 0;
  +        }
  +    }
  +
  +    /**
  +     * @see LockManager#release(Object, Object)
  +     */
  +    public void release(Object ownerId, Object resourceId) {
  +        GenericLock lock = (GenericLock) atomicGetOrCreateLock(resourceId);
  +        lock.release(ownerId);
  +        removeOwner(ownerId, lock);
  +    }
  +
  +    /**
  +     * @see LockManager#releaseAll(Object)
  +     */
  +    public void releaseAll(Object ownerId) {
  +        synchronized (globalOwners) {
  +            Set locks = (Set) globalOwners.get(ownerId);
  +            if (locks != null) {
  +                for (Iterator it = locks.iterator(); it.hasNext();) {
  +                    GenericLock lock = (GenericLock) it.next();
  +                    lock.release(ownerId);
  +                    it.remove();
  +                }
  +            }
  +        }
  +    }
  +
  +    /**
  +     * @see LockManager#getAll(Object)
  +     */
  +    public Set getAll(Object ownerId) {
  +        Set locks = (Set) globalOwners.get(ownerId);
  +        if (locks == null) {
  +            locks = new HashSet();
  +            globalOwners.put(ownerId, locks);
  +        }
  +        return locks;
  +    }
  +
  +    protected void addOwner(Object ownerId, GenericLock lock) {
  +        Set locks = (Set) globalOwners.get(ownerId);
  +        if (locks == null) {
  +            locks = new HashSet();
  +            globalOwners.put(ownerId, locks);
  +        }
  +        locks.add(lock);
  +    }
  +
  +    protected void removeOwner(Object ownerId, GenericLock lock) {
  +        Set locks = (Set) globalOwners.get(ownerId);
  +        if (locks != null) {
  +            locks.remove(lock);
  +        }
  +    }
  +
  +    protected void addWaiter(GenericLock lock, Object ownerId) {
  +        Set waiters = (Set) waitsForLock.get(lock);
  +        if (waiters == null) {
  +            waiters = new HashSet();
  +            waitsForLock.put(lock, waiters);
  +        }
  +        waiters.add(ownerId);
  +    }
  +
  +    protected void removeWaiter(GenericLock lock, Object ownerId) {
  +        Set waiters = (Set) waitsForLock.get(lock);
  +        if (waiters != null) {
  +            waiters.remove(ownerId);
  +        }
  +    }
  +
  +    protected boolean wouldDeadlock(Object ownerId, GenericLock lock, int targetLockLevel,
  +            int compatibility) {
  +        // let's see if any of the conflicting owners waits for us, if so we
  +        // have a deadlock
  +
  +        Set conflicts = lock.getConflictingOwners(ownerId, targetLockLevel, compatibility);
  +        if (conflicts != null) {
  +            // these are our locks
  +            Set locks = (Set) globalOwners.get(ownerId);
  +            if (locks != null) {
  +                for (Iterator i = locks.iterator(); i.hasNext();) {
  +                    GenericLock mylock = (GenericLock) i.next();
  +                    // these are the ones waiting for one of our locks
  +                    Set waiters = (Set) waitsForLock.get(mylock);
  +                    if (waiters != null) {
  +                        for (Iterator j = waiters.iterator(); j.hasNext();) {
  +                            Object waitingOwnerId = j.next();
  +                            // if someone waiting for one of our locks would make us wait
  +                            // this is a deadlock
  +                            if (conflicts.contains(waitingOwnerId))
  +                                return true;
  +
  +                        }
  +                    }
  +                }
  +            }
  +        }
  +        return false;
       }
   
  +    
       public MultiLevelLock getLock(Object resourceId) {
           synchronized (globalLocks) {
               return (MultiLevelLock) globalLocks.get(resourceId);
  
  
  
  1.3       +49 -8     jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/GenericLock.java
  
  Index: GenericLock.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/GenericLock.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- GenericLock.java	29 Nov 2004 18:28:17 -0000	1.2
  +++ GenericLock.java	14 Dec 2004 12:12:46 -0000	1.3
  @@ -24,8 +24,10 @@
   package org.apache.commons.transaction.locking;
   
   import java.util.HashMap;
  +import java.util.HashSet;
   import java.util.Iterator;
   import java.util.Map;
  +import java.util.Set;
   
   import org.apache.commons.transaction.util.LoggerFacade;
   
  @@ -109,8 +111,8 @@
       public static final int COMPATIBILITY_SUPPORT = 2;
       public static final int COMPATIBILITY_REENTRANT_AND_SUPPORT = 3;
       
  -    private Object resourceId;
  -    private Map owners = new HashMap();
  +    protected Object resourceId;
  +    protected Map owners = new HashMap();
       private int maxLockLevel;
       protected LoggerFacade logger;
   
  @@ -430,7 +432,7 @@
           }
   
           // we are only allowed to acquire our locks if we do not compromise locks of any other lock owner
  -        if (targetLockLevel <= getLevelMaxLock() - currentLockLevel) {
  +        if (isCompatible(targetLockLevel, currentLockLevel)) {
               setLockLevel(ownerId, myLock, targetLockLevel);
               return true;
           } else {
  @@ -438,7 +440,46 @@
           }
       }
   
  -    private static class LockOwner {
  +    protected boolean isCompatible(int targetLockLevel, int currentLockLevel) {
  +        return (targetLockLevel <= getLevelMaxLock() - currentLockLevel);
  +    }
  +    
  +    protected Set getConflictingOwners(Object ownerId, int targetLockLevel,
  +            int compatibility) {
  +
  +        LockOwner myLock = (LockOwner) owners.get(ownerId);
  +        if (myLock != null && targetLockLevel <= myLock.lockLevel) {
  +            // shortcut as we already have the lock
  +            return null;
  +        }
  +
  +        Set conflicts = new HashSet();
  +
  +        // check if any locks conflict with ours
  +        for (Iterator it = owners.values().iterator(); it.hasNext();) {
  +            LockOwner owner = (LockOwner) it.next();
  +            
  +            // we do not interfere with ourselves, except when explicitely said so
  +            if ((compatibility == COMPATIBILITY_REENTRANT || compatibility == COMPATIBILITY_REENTRANT_AND_SUPPORT)
  +                    && owner.ownerId.equals(ownerId))
  +                continue;
  +            
  +            // otherwise find out the lock level of the owner and see if we conflict with it
  +            int onwerLockLevel = owner.lockLevel;
  +           
  +            if (compatibility == COMPATIBILITY_SUPPORT
  +                    || compatibility == COMPATIBILITY_REENTRANT_AND_SUPPORT
  +                    && targetLockLevel == onwerLockLevel)
  +                continue;
  +            
  +            if (!isCompatible(targetLockLevel, onwerLockLevel)) {
  +                conflicts.add(owner.ownerId);
  +            }
  +        }
  +        return (conflicts.isEmpty() ? null : conflicts);
  +    }
  +
  +    protected static class LockOwner {
           public Object ownerId;
           public int lockLevel;
   
  
  
  
  1.1                  jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/ReadWriteLockManager.java
  
  Index: ReadWriteLockManager.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/ReadWriteLockManager.java,v 1.1 2004/12/14 12:12:46 ozeigermann Exp $
   * $Revision: 1.1 $
   * $Date: 2004/12/14 12:12:46 $
   *
   * ====================================================================
   *
   * Copyright 1999-2004 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.locking;
  
  import org.apache.commons.transaction.util.LoggerFacade;
  
  /**
   * Manager for {@link org.apache.commons.transaction.locking.ReadWriteLock}s on resources.
   * 
   * @version $Revision: 1.1 $
   */
  public class ReadWriteLockManager extends GenericLockManager {
  
      /**
       * Creates a new read/write lock manager.
       * 
       * @param logger generic logger used for all kind of debug logging
       * @param timeoutMSecs specifies the maximum time to wait for a lock in milliseconds
       */
      public ReadWriteLockManager(LoggerFacade logger, long timeoutMSecs) {
          super(ReadWriteLock.WRITE_LOCK, logger, timeoutMSecs);
      }
  
      /**
       * Tries to acquire a shared, reentrant read lock on a resource. <br>
       * <br>
       * This method does not block, but immediatly returns. If a lock is not
       * available <code>false</code> will be returned.
       * 
       * @param ownerId
       *            a unique id identifying the entity that wants to acquire this
       *            lock
       * @param resourceId
       *            the resource to get the level for
       * @return <code>true</code> if the lock has been acquired, <code>false</code> otherwise
       */
      public boolean tryReadLock(Object ownerId, Object resourceId) {
          return super.tryLock(ownerId, resourceId, ReadWriteLock.READ_LOCK, true);
      }
  
      /**
       * Tries to acquire an exclusive, reentrant write lock on a resource. <br>
       * <br>
       * This method does not block, but immediatly returns. If a lock is not
       * available <code>false</code> will be returned.
       * 
       * @param ownerId
       *            a unique id identifying the entity that wants to acquire this
       *            lock
       * @param resourceId
       *            the resource to get the level for
       * @return <code>true</code> if the lock has been acquired, <code>false</code> otherwise
       */
      public boolean tryWriteLock(Object ownerId, Object resourceId) {
          return super.tryLock(ownerId, resourceId, ReadWriteLock.WRITE_LOCK, true);
      }
  
      /**
       * Tries to acquire a shared, reentrant read lock on a resource. <br>
       * <br>
       * This method blocks and waits for the lock in case it is not avaiable. If
       * there is a timeout or a deadlock or the thread is interrupted a
       * LockException is thrown.
       * 
       * @param ownerId
       *            a unique id identifying the entity that wants to acquire this
       *            lock
       * @param resourceId
       *            the resource to get the level for
       * @throws LockException
       *             will be thrown when the lock can not be acquired
       */
      public void readLock(Object ownerId, Object resourceId) throws LockException {
          super.lock(ownerId, resourceId, ReadWriteLock.READ_LOCK, true);
      }
  
      /**
       * Tries to acquire an exclusive, reentrant write lock on a resource. <br>
       * <br>
       * This method blocks and waits for the lock in case it is not avaiable. If
       * there is a timeout or a deadlock or the thread is interrupted a
       * LockException is thrown.
       * 
       * @param ownerId
       *            a unique id identifying the entity that wants to acquire this
       *            lock
       * @param resourceId
       *            the resource to get the level for
       * @throws LockException
       *             will be thrown when the lock can not be acquired
       */
      public void writeLock(Object ownerId, Object resourceId) throws LockException {
          super.lock(ownerId, resourceId, ReadWriteLock.WRITE_LOCK, true);
      }
  
      protected GenericLock createLock(Object resourceId) {
          synchronized (globalLocks) {
              GenericLock lock = new ReadWriteLock(resourceId, logger);
              globalLocks.put(resourceId, lock);
              return lock;
          }
      }
  
  }
  
  
  1.1                  jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/LockException.java
  
  Index: LockException.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/LockException.java,v 1.1 2004/12/14 12:12:46 ozeigermann Exp $
   * $Revision: 1.1 $
   * $Date: 2004/12/14 12:12:46 $
   *
   * ====================================================================
   *
   * 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.locking;
  
  /**
   * Exception displaying a lock problem.
   * 
   * @version $Revision: 1.1 $
   */
  public class LockException extends RuntimeException {
  
      /**
       * Thread has been interrupted while waiting for lock.
       */
      public static final int CODE_INTERRUPTED = 1;
  
      /**
       * Maximum wait time for a lock has been exceeded.
       */
      public static final int CODE_TIMED_OUT = 2;
  
      /**
       * Locking request canceled because of deadlock.
       */
      public static final int CODE_DEADLOCK_VICTIM = 3;
  
      protected Object resourceId;
  
      protected String reason;
  
      protected int code;
  
      public LockException(String reason, int code, Object resourceId) {
          this.reason = reason;
          this.code = code;
          this.resourceId = resourceId;
      }
  
      /**
       * Returns the formal reason for the exception.
       * 
       * @return one of {@link #CODE_INTERRUPTED},{@link #CODE_TIMED_OUT}or
       *         {@link #CODE_DEADLOCK_VICTIM}.
       */
      public int getCode() {
          return code;
      }
  
      /**
       * Returns the resource the lock was tried on.
       * 
       * @return the resource or <code>null</code> if not applicable
       */
      public Object getResourceId() {
          return resourceId;
      }
  
      /**
       * Returns the verbose for the exception.
       * 
       * @return the reason message
       */
      public String getReason() {
          return reason;
      }
  }
  
  
  1.2       +114 -10   jakarta-commons/transaction/src/test/org/apache/commons/transaction/file/FileResourceManagerTest.java
  
  Index: FileResourceManagerTest.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/transaction/src/test/org/apache/commons/transaction/file/FileResourceManagerTest.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- FileResourceManagerTest.java	18 Nov 2004 23:27:17 -0000	1.1
  +++ FileResourceManagerTest.java	14 Dec 2004 12:12:46 -0000	1.2
  @@ -23,18 +23,26 @@
   
   package org.apache.commons.transaction.file;
   
  -import junit.framework.*;
  -
  -import java.io.*;
  -import java.util.*;
  -import java.util.logging.*;
  +import java.io.BufferedReader;
  +import java.io.File;
  +import java.io.FileInputStream;
  +import java.io.FileOutputStream;
  +import java.io.IOException;
  +import java.io.InputStream;
  +import java.io.InputStreamReader;
  +import java.io.OutputStream;
  +import java.util.logging.Logger;
   
   import javax.transaction.Status;
   
  -import org.apache.commons.transaction.util.*;
  +import junit.framework.Test;
  +import junit.framework.TestCase;
  +import junit.framework.TestSuite;
  +
   import org.apache.commons.transaction.util.FileHelper;
   import org.apache.commons.transaction.util.Jdk14Logger;
   import org.apache.commons.transaction.util.LoggerFacade;
  +import org.apache.commons.transaction.util.RendezvousBarrier;
   
   /**
    * Tests for FileResourceManager. 
  @@ -79,6 +87,10 @@
       };
       private static final String[] STATUS_COMMITTED_CONTEXT_RESULT_FILES = new String[] { "Hubert6", "Hubert" };
   
  +    protected static final long TIMEOUT = Long.MAX_VALUE;
  +
  +    private static int deadlockCnt = 0;
  +
       private static void initCommittedRecovery() throws Throwable {
           String txId = "COMMITTED";
           createTxContextFile(txId, STATUS_COMMITTED_CONTEXT);
  @@ -613,6 +625,98 @@
           // tx rolled forward created "/olli/Hubert100", so it should be here as well
           checkExactlyContains(STORE + "/olli", new String[] { "Hubert", "Hubert100", "Hubert6", "Hubert10" });
           checkIsEmpty(WORK);
  +    }
  +
  +    public void testConflict() throws Throwable {
  +        logger.info("Checking concurrent transaction features");
  +
  +        reset();
  +        createInitialFiles();
  +        
  +        final FileResourceManager rm = createFRM(); 
  +        
  +        rm.start();
  +
  +        final RendezvousBarrier restart = new RendezvousBarrier("restart",
  +                TIMEOUT, sLogger);
  +
  +        for (int i = 0; i < 25; i++) {
  +
  +            final RendezvousBarrier deadlockBarrier1 = new RendezvousBarrier("deadlock" + i,
  +                    TIMEOUT, sLogger);
  +
  +            Thread thread1 = new Thread(new Runnable() {
  +                public void run() {
  +                    try {
  +                        rm.startTransaction("tx1");
  +                        // first both threads get a lock, this one on res2
  +                        rm.createResource("tx1", "key2");
  +                        synchronized (deadlockBarrier1) {
  +                            deadlockBarrier1.meet();
  +                            deadlockBarrier1.reset();
  +                        }
  +                        // if I am first, the other thread will be dead, i.e.
  +                        // exactly one
  +                        rm.createResource("tx1", "key1");
  +                        rm.commitTransaction("tx1");
  +                    } catch (InterruptedException ie) {
  +                    } catch (ResourceManagerException e) {
  +                        assertEquals(e.getStatus(), ResourceManagerException.ERR_DEAD_LOCK);
  +                        deadlockCnt++;
  +                        try {
  +                            rm.rollbackTransaction("tx1");
  +                        } catch (ResourceManagerException e1) {
  +                            // TODO Auto-generated catch block
  +                            e1.printStackTrace();
  +                        }
  +                    } finally {
  +                        try {
  +                        synchronized (restart) {
  +                            restart.meet();
  +                            restart.reset();
  +                        }
  +                        } catch (InterruptedException ie) {}
  +
  +                    }
  +                }
  +            }, "Thread1");
  +
  +            thread1.start();
  +
  +            rm.startTransaction("tx2");
  +            try {
  +                // first both threads get a lock, this one on res2
  +                rm.deleteResource("tx2", "key1");
  +                synchronized (deadlockBarrier1) {
  +                    deadlockBarrier1.meet();
  +                    deadlockBarrier1.reset();
  +                }
  +                //          if I am first, the other thread will be dead, i.e. exactly
  +                // one
  +                rm.deleteResource("tx2", "key2");
  +                rm.commitTransaction("tx2");
  +            } catch (ResourceManagerException e) {
  +                assertEquals(e.getStatus(), ResourceManagerException.ERR_DEAD_LOCK);
  +                deadlockCnt++;
  +                try {
  +                    rm.rollbackTransaction("tx2");
  +                } catch (ResourceManagerException e1) {
  +                    // TODO Auto-generated catch block
  +                    e1.printStackTrace();
  +                }
  +            } finally {
  +                try {
  +                synchronized (restart) {
  +                    restart.meet();
  +                    restart.reset();
  +                }
  +                } catch (InterruptedException ie) {}
  +
  +            }
  +
  +            assertEquals(deadlockCnt, 1);
  +            deadlockCnt = 0;
  +        }
       }
   
   }
  
  
  
  1.2       +32 -54    jakarta-commons/transaction/src/java/org/apache/commons/transaction/memory/PessimisticMapWrapper.java
  
  Index: PessimisticMapWrapper.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/transaction/src/java/org/apache/commons/transaction/memory/PessimisticMapWrapper.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- PessimisticMapWrapper.java	18 Nov 2004 23:27:18 -0000	1.1
  +++ PessimisticMapWrapper.java	14 Dec 2004 12:12:46 -0000	1.2
  @@ -24,15 +24,10 @@
   package org.apache.commons.transaction.memory;
   
   import java.util.Collection;
  -import java.util.HashSet;
  -import java.util.Iterator;
   import java.util.Map;
   import java.util.Set;
   
  -import org.apache.commons.transaction.locking.GenericLock;
  -import org.apache.commons.transaction.locking.GenericLockManager;
  -import org.apache.commons.transaction.locking.LockManager;
  -import org.apache.commons.transaction.locking.MultiLevelLock;
  +import org.apache.commons.transaction.locking.ReadWriteLockManager;
   import org.apache.commons.transaction.util.LoggerFacade;
   
   /**
  @@ -58,10 +53,10 @@
       protected static final int READ = 1;
       protected static final int WRITE = 2;
   
  -    protected static final String GLOBAL_LOCK_NAME = "GLOBAL";
  +    protected static final Object GLOBAL_LOCK = "GLOBAL";
   
  -    protected LockManager lockManager;
  -    protected MultiLevelLock globalLock;
  +    protected ReadWriteLockManager lockManager;
  +//    protected MultiLevelLock globalLock;
       protected long readTimeOut = 60000; /* FIXME: pass in ctor */
   
       /**
  @@ -84,8 +79,8 @@
        */
       public PessimisticMapWrapper(Map wrapped, MapFactory mapFactory, SetFactory setFactory, LoggerFacade logger) {
           super(wrapped, mapFactory, setFactory);
  -        lockManager = new GenericLockManager(WRITE, logger);
  -        globalLock = new GenericLock(GLOBAL_LOCK_NAME, WRITE, logger);
  +        lockManager = new ReadWriteLockManager(logger, readTimeOut);
  +//        globalLock = new GenericLock(GLOBAL_LOCK_NAME, WRITE, logger);
       }
   
       public void startTransaction() {
  @@ -98,17 +93,17 @@
       }
   
       public Collection values() {
  -        assureGlobalLock(READ);
  +        assureGlobalReadLock();
           return super.values();
       }
   
       public Set entrySet() {
  -        assureGlobalLock(READ);
  +        assureGlobalReadLock();
           return super.entrySet();
       }
   
       public Set keySet() {
  -        assureGlobalLock(READ);
  +        assureGlobalReadLock();
           return super.keySet();
       }
   
  @@ -129,84 +124,67 @@
       protected void assureWriteLock(Object key) {
           LockingTxContext txContext = (LockingTxContext) getActiveTx();
           if (txContext != null) {
  -            txContext.lock(lockManager.atomicGetOrCreateLock(key), WRITE);
  -            txContext.lock(globalLock, READ); // XXX fake intention lock (prohibits global WRITE)
  +            lockManager.writeLock(txContext, key);
  +            // XXX fake intention lock (prohibits global WRITE)
  +            lockManager.readLock(txContext, GLOBAL_LOCK); 
           }
       }
       
  -    protected void assureGlobalLock(int level) {
  +    protected void assureGlobalReadLock() {
           LockingTxContext txContext = (LockingTxContext) getActiveTx();
           if (txContext != null) {
  -            txContext.lock(globalLock, level); // XXX fake intention lock (prohibits global WRITE)
  +            // XXX fake intention lock (prohibits global WRITE)
  +            lockManager.readLock(txContext, GLOBAL_LOCK); 
           }
       }
       
       public class LockingTxContext extends TxContext {
  -        protected Set locks;
  -
  -        protected LockingTxContext() {
  -            super();
  -            locks = new HashSet();
  -        }
   
           protected Set keys() {
  -            lock(globalLock, READ);
  +            lockManager.readLock(this, GLOBAL_LOCK); 
               return super.keys();
           }
   
           protected Object get(Object key) {
  -            lock(lockManager.atomicGetOrCreateLock(key), READ);
  -            lock(globalLock, READ); // XXX fake intention lock (prohibits global WRITE)
  +            lockManager.readLock(this, key);
  +            // XXX fake intention lock (prohibits global WRITE)
  +            lockManager.readLock(this, GLOBAL_LOCK);
               return super.get(key);
           }
   
           protected void put(Object key, Object value) {
  -            lock(lockManager.atomicGetOrCreateLock(key), WRITE);
  -            lock(globalLock, READ); // XXX fake intention lock (prohibits global WRITE)
  +            lockManager.writeLock(this, key);
  +            // XXX fake intention lock (prohibits global WRITE)
  +            lockManager.readLock(this, GLOBAL_LOCK);
               super.put(key, value);
           }
   
           protected void remove(Object key) {
  -            lock(lockManager.atomicGetOrCreateLock(key), WRITE);
  -            lock(globalLock, READ); // XXX fake intention lock (prohibits global WRITE)
  +            lockManager.writeLock(this, key);
  +            // XXX fake intention lock (prohibits global WRITE)
  +            lockManager.readLock(this, GLOBAL_LOCK);
               super.remove(key);
           }
   
           protected int size() {
               // XXX this is bad luck, we need a global read lock just for the size :( :( :(
  -            lock(globalLock, READ);
  +            lockManager.readLock(this, GLOBAL_LOCK);
               return super.size();
           }
   
           protected void clear() {
  -            lock(globalLock, WRITE);
  +            lockManager.writeLock(this, GLOBAL_LOCK);
               super.clear();
           }
   
           protected void dispose() {
               super.dispose();
  -            for (Iterator it = locks.iterator(); it.hasNext();) {
  -                MultiLevelLock lock = (MultiLevelLock) it.next();
  -                lock.release(this);
  -            }
  +            lockManager.releaseAll(this);
           }
   
           protected void finalize() throws Throwable {
               dispose();
               super.finalize();
  -        }
  -
  -        protected void lock(MultiLevelLock lock, int level) throws LockException {
  -            boolean acquired = false;
  -            try {
  -                acquired = lock.acquire(this, level, true, true, readTimeOut);
  -            } catch (InterruptedException e) {
  -                throw new LockException("Interrupted", LockException.CODE_INTERRUPTED, GLOBAL_LOCK_NAME);
  -            }
  -            if (!acquired) {
  -                throw new LockException("Timed out", LockException.CODE_TIMED_OUT, GLOBAL_LOCK_NAME);
  -            }
  -            locks.add(lock);
           }
       }
   
  
  
  
  1.2       +10 -4     jakarta-commons/transaction/src/java/org/apache/commons/transaction/file/ResourceManagerErrorCodes.java
  
  Index: ResourceManagerErrorCodes.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/transaction/src/java/org/apache/commons/transaction/file/ResourceManagerErrorCodes.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- ResourceManagerErrorCodes.java	18 Nov 2004 23:27:19 -0000	1.1
  +++ ResourceManagerErrorCodes.java	14 Dec 2004 12:12:46 -0000	1.2
  @@ -113,4 +113,10 @@
        */
       public static final int ERR_NO_LOCK = ERR_LOCK + 1;
   
  +    /**
  +     * Error code: lock could not be acquired error
  +     */
  +    public static final int ERR_DEAD_LOCK = ERR_LOCK + 2;
  +
  +
   }
  
  
  
  1.2       +7 -8      jakarta-commons/transaction/src/java/org/apache/commons/transaction/file/ResourceManager.java
  
  Index: ResourceManager.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/transaction/src/java/org/apache/commons/transaction/file/ResourceManager.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- ResourceManager.java	18 Nov 2004 23:27:19 -0000	1.1
  +++ ResourceManager.java	14 Dec 2004 12:12:46 -0000	1.2
  @@ -315,10 +315,9 @@
        * @param wait <code>true</code> if the method shall block when lock can not be acquired now
        * @param timeoutMSecs timeout in milliseconds
        * @param reentrant <code>true</code> if the lock should be acquired even when the <em>requesting transaction and no other</em> holds an incompatible lock
  -     * @return <code>true</code> when the lock has been acquired
        * @throws ResourceManagerException if an error occured
        */
  -    public boolean lockResource(
  +    public void lockResource(
           Object resourceId,
           Object txId,
           boolean shared,
  @@ -337,7 +336,7 @@
        * @throws ResourceManagerException if an error occured
        * @see #lockResource(Object, Object, boolean, boolean, long, boolean)
        */
  -    public boolean lockResource(Object resourceId, Object txId, boolean shared) throws ResourceManagerException;
  +    public void lockResource(Object resourceId, Object txId, boolean shared) throws ResourceManagerException;
   
       /**
        * Explicitly locks a resource exclusively, i.e. for writing, in reentrant style. This method blocks until the lock
  @@ -349,7 +348,7 @@
        * @see #lockResource(Object, Object, boolean)
        * @see #lockResource(Object, Object, boolean, boolean, long, boolean)
        */
  -    public boolean lockResource(Object resourceId, Object txId) throws ResourceManagerException;
  +    public void lockResource(Object resourceId, Object txId) throws ResourceManagerException;
   
       /**
        * Checks if a resource exists. 
  
  
  
  1.3       +40 -51    jakarta-commons/transaction/src/java/org/apache/commons/transaction/file/FileResourceManager.java
  
  Index: FileResourceManager.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/transaction/src/java/org/apache/commons/transaction/file/FileResourceManager.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- FileResourceManager.java	29 Nov 2004 18:28:17 -0000	1.2
  +++ FileResourceManager.java	14 Dec 2004 12:12:46 -0000	1.3
  @@ -44,10 +44,13 @@
   import java.util.Iterator;
   import java.util.Collections;
   
  -import org.apache.commons.transaction.util.*;
  -import org.apache.commons.transaction.locking.*;
  -
   import org.apache.commons.codec.binary.Base64;
  +import org.apache.commons.transaction.locking.GenericLock;
  +import org.apache.commons.transaction.locking.GenericLockManager;
  +import org.apache.commons.transaction.locking.LockException;
  +import org.apache.commons.transaction.locking.LockManager;
  +import org.apache.commons.transaction.util.FileHelper;
  +import org.apache.commons.transaction.util.LoggerFacade;
   
   /**
    * A resource manager for streamable objects stored in a file system.
  @@ -272,15 +275,15 @@
        *  
        */
   
  -    public boolean lockResource(Object resourceId, Object txId) throws ResourceManagerException {
  -        return lockResource(resourceId, txId, false);
  +    public void lockResource(Object resourceId, Object txId) throws ResourceManagerException {
  +        lockResource(resourceId, txId, false);
       }
   
  -    public boolean lockResource(Object resourceId, Object txId, boolean shared) throws ResourceManagerException {
  -        return lockResource(resourceId, txId, shared, true, Long.MAX_VALUE, true);
  +    public void lockResource(Object resourceId, Object txId, boolean shared) throws ResourceManagerException {
  +        lockResource(resourceId, txId, shared, true, Long.MAX_VALUE, true);
       }
   
  -    public boolean lockResource(
  +    public void lockResource(
           Object resourceId,
           Object txId,
           boolean shared,
  @@ -295,15 +298,21 @@
   
           // XXX allows locking of non existent resources (e.g. to prepare a create)
           int level = (shared ? getSharedLockLevel(context) : LOCK_EXCLUSIVE);
  -        MultiLevelLock rl;
  -        synchronized (lockManager) {
  -            rl = lockManager.atomicGetOrCreateLock(resourceId);
  -            context.getLocks().add(rl);
  -        }
           try {
  -            return rl.acquire(txId, level, wait, reentrant, Math.min(timeoutMSecs, context.timeoutMSecs));
  -        } catch (InterruptedException e) {
  -            throw new ResourceManagerSystemException(ERR_SYSTEM, txId, e);
  +            lockManager.lock(txId, resourceId, level, reentrant, Math.min(timeoutMSecs,
  +                    context.timeoutMSecs));
  +        } catch (LockException e) {
  +            switch (e.getCode()) {
  +            case LockException.CODE_INTERRUPTED:
  +                throw new ResourceManagerException("Could not get lock for resource at '"
  +                        + resourceId + "'", ERR_NO_LOCK, txId);
  +            case LockException.CODE_TIMED_OUT:
  +                throw new ResourceManagerException("Lock timed out for resource at '" + resourceId
  +                        + "'", ERR_NO_LOCK, txId);
  +            case LockException.CODE_DEADLOCK_VICTIM:
  +                throw new ResourceManagerException("Deadlock victim resource at '" + resourceId
  +                        + "'", ERR_DEAD_LOCK, txId);
  +            }
           }
       }
   
  @@ -662,7 +671,7 @@
       }
   
       public boolean resourceExists(Object txId, Object resourceId) throws ResourceManagerException {
  -        assureLock(resourceId, txId, true);
  +        lockResource(resourceId, txId, true);
           return (getPathForRead(txId, resourceId) != null);
       }
   
  @@ -674,7 +683,7 @@
   
           if (logger.isFineEnabled()) logger.logFine(txId + " deleting " + resourceId);
   
  -        assureLock(resourceId, txId, false);
  +        lockResource(resourceId, txId, false);
   
           if (getPathForRead(txId, resourceId) == null) {
               if (assureOnly) {
  @@ -711,7 +720,7 @@
   
           if (logger.isFineEnabled()) logger.logFine(txId + " creating " + resourceId);
   
  -        assureLock(resourceId, txId, false);
  +        lockResource(resourceId, txId, false);
   
           if (getPathForRead(txId, resourceId) != null) {
               if (assureOnly) {
  @@ -769,7 +778,7 @@
   
           if (logger.isFineEnabled()) logger.logFine(txId + " reading " + resourceId);
   
  -        assureLock(resourceId, txId, true);
  +        lockResource(resourceId, txId, true);
   
           String resourcePath = getPathForRead(txId, resourceId);
           if (resourcePath == null) {
  @@ -790,7 +799,7 @@
   
           if (logger.isFineEnabled()) logger.logFine(txId + " writing " + resourceId);
   
  -        assureLock(resourceId, txId, false);
  +        lockResource(resourceId, txId, false);
   
           String resourcePath = getPathForWrite(txId, resourceId);
           if (resourcePath == null) {
  @@ -1083,16 +1092,6 @@
           }
       }
   
  -    protected void assureLock(Object resourceId, Object txId, boolean shared) throws ResourceManagerException {
  -
  -        if (!lockResource(resourceId, txId, shared)) {
  -            throw new ResourceManagerException(
  -                "Could not get lock for resource at '" + resourceId + "'",
  -                ERR_NO_LOCK,
  -                txId);
  -        }
  -    }
  -
       /*
        * --- Resource Management ---
        *
  @@ -1306,7 +1305,6 @@
           protected boolean readOnly = true;
           protected boolean finished = false;
   
  -        private Collection locks = Collections.synchronizedCollection(new ArrayList());
           // list of streams participating in this tx
           private List openResourcs = new ArrayList();
   
  @@ -1315,10 +1313,6 @@
               startTime = System.currentTimeMillis();
           }
   
  -        public Collection getLocks() {
  -            return locks;
  -        }
  -
           public long getRemainingTimeout() {
               long now = System.currentTimeMillis();
               return (startTime - now + timeoutMSecs);
  @@ -1384,7 +1378,7 @@
           }
   
           public synchronized void upgradeLockToCommit() throws ResourceManagerException {
  -            for (Iterator it = locks.iterator(); it.hasNext();) {
  +            for (Iterator it =  lockManager.getAll(txId).iterator(); it.hasNext();) {
                   GenericLock lock = (GenericLock) it.next();
                   // only upgrade if we had write access
                   if (lock.getLockLevel(txId) == LOCK_EXCLUSIVE) {
  @@ -1413,11 +1407,7 @@
           }
   
           public synchronized void freeLocks() {
  -            for (Iterator it = locks.iterator(); it.hasNext();) {
  -                MultiLevelLock lock = (MultiLevelLock) it.next();
  -                lock.release(txId);
  -                it.remove();
  -            }
  +            lockManager.releaseAll(txId);
           }
   
           public synchronized void closeResources() {
  @@ -1509,8 +1499,8 @@
               if (debug) {
                   buf.append("----- Lock Debug Info -----\n");
                   
  -                for (Iterator it = locks.iterator(); it.hasNext();) {
  -                    MultiLevelLock lock = (MultiLevelLock) it.next();
  +                for (Iterator it = lockManager.getAll(txId).iterator(); it.hasNext();) {
  +                    GenericLock lock = (GenericLock) it.next();
                       buf.append(lock.toString()+"\n");
                   }
                   
  @@ -1566,8 +1556,7 @@
                           globalTransactions.remove(txId);
                       } else {
                           // release access lock in order to allow other transactions to commit
  -                        MultiLevelLock lock = lockManager.atomicGetOrCreateLock(resourceId);
  -                        if (lock.getLockLevel(txId) == LOCK_ACCESS) {
  +                        if (lockManager.getLevel(txId, resourceId) == LOCK_ACCESS) {
                               if (logger.isFinerEnabled()) {
   	                            logger.logFiner(
   	                                "Upon close of resource releasing access lock for tx "
  @@ -1575,7 +1564,7 @@
   	                                    + " on resource at "
   	                                    + resourceId);
                               }
  -                            lock.release(txId);
  +                            lockManager.release(txId, resourceId);
                           }
                       }
                   }
  
  
  
  1.2       +88 -4     jakarta-commons/transaction/src/test/org/apache/commons/transaction/memory/PessimisticMapWrapperTest.java
  
  Index: PessimisticMapWrapperTest.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/transaction/src/test/org/apache/commons/transaction/memory/PessimisticMapWrapperTest.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- PessimisticMapWrapperTest.java	18 Nov 2004 23:27:19 -0000	1.1
  +++ PessimisticMapWrapperTest.java	14 Dec 2004 12:12:47 -0000	1.2
  @@ -29,6 +29,7 @@
   import java.util.Map;
   import java.util.logging.*;
   
  +import org.apache.commons.transaction.locking.LockException;
   import org.apache.commons.transaction.util.Jdk14Logger;
   import org.apache.commons.transaction.util.LoggerFacade;
   import org.apache.commons.transaction.util.RendezvousBarrier;
  @@ -43,6 +44,10 @@
       private static final Logger logger = Logger.getLogger(PessimisticMapWrapperTest.class.getName());
       private static final LoggerFacade sLogger = new Jdk14Logger(logger);
   
  +    protected static final long TIMEOUT = Long.MAX_VALUE;
  +
  +    private static int deadlockCnt = 0;
  +
       public static Test suite() {
           TestSuite suite = new TestSuite(PessimisticMapWrapperTest.class);
           return suite;
  @@ -113,6 +118,85 @@
           synchronized (txMap1) {
               txMap1.commitTransaction();
               report("value3", (String) txMap1.get("key1"));
  +        }
  +    }
  +
  +    public void testConflict() throws Throwable {
  +        logger.info("Checking concurrent transaction features");
  +
  +        final Map map1 = new HashMap();
  +
  +        final PessimisticMapWrapper txMap1 = (PessimisticMapWrapper) getNewWrapper(map1);
  +
  +        final RendezvousBarrier restart = new RendezvousBarrier("restart",
  +                TIMEOUT, sLogger);
  +
  +        for (int i = 0; i < 25; i++) {
  +
  +            final RendezvousBarrier deadlockBarrier1 = new RendezvousBarrier("deadlock" + i,
  +                    TIMEOUT, sLogger);
  +
  +            Thread thread1 = new Thread(new Runnable() {
  +                public void run() {
  +                    txMap1.startTransaction();
  +                    try {
  +                        // first both threads get a lock, this one on res2
  +                        txMap1.put("key2", "value2");
  +                        synchronized (deadlockBarrier1) {
  +                            deadlockBarrier1.meet();
  +                            deadlockBarrier1.reset();
  +                        }
  +                        // if I am first, the other thread will be dead, i.e.
  +                        // exactly one
  +                        txMap1.put("key1", "value2");
  +                        txMap1.commitTransaction();
  +                    } catch (LockException le) {
  +                        assertEquals(le.getCode(), LockException.CODE_DEADLOCK_VICTIM);
  +                        deadlockCnt++;
  +                        txMap1.rollbackTransaction();
  +                    } catch (InterruptedException ie) {
  +                    } finally {
  +                        try {
  +                        synchronized (restart) {
  +                            restart.meet();
  +                            restart.reset();
  +                        }
  +                        } catch (InterruptedException ie) {}
  +
  +                    }
  +                }
  +            }, "Thread1");
  +
  +            thread1.start();
  +
  +            txMap1.startTransaction();
  +            try {
  +                // first both threads get a lock, this one on res2
  +                txMap1.get("key1");
  +                synchronized (deadlockBarrier1) {
  +                    deadlockBarrier1.meet();
  +                    deadlockBarrier1.reset();
  +                }
  +                //          if I am first, the other thread will be dead, i.e. exactly
  +                // one
  +                txMap1.get("key2");
  +                txMap1.commitTransaction();
  +            } catch (LockException le) {
  +                assertEquals(le.getCode(), LockException.CODE_DEADLOCK_VICTIM);
  +                deadlockCnt++;
  +                txMap1.rollbackTransaction();
  +            } finally {
  +                try {
  +                synchronized (restart) {
  +                    restart.meet();
  +                    restart.reset();
  +                }
  +                } catch (InterruptedException ie) {}
  +
  +            }
  +
  +            assertEquals(deadlockCnt, 1);
  +            deadlockCnt = 0;
           }
       }
   
  
  
  
  1.2       +71 -3     jakarta-commons/transaction/src/test/org/apache/commons/transaction/locking/GenericLockTest.java
  
  Index: GenericLockTest.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/transaction/src/test/org/apache/commons/transaction/locking/GenericLockTest.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- GenericLockTest.java	18 Nov 2004 23:27:22 -0000	1.1
  +++ GenericLockTest.java	14 Dec 2004 12:12:47 -0000	1.2
  @@ -31,6 +31,7 @@
   
   import org.apache.commons.transaction.util.LoggerFacade;
   import org.apache.commons.transaction.util.PrintWriterLogger;
  +import org.apache.commons.transaction.util.RendezvousBarrier;
   
   /**
    * Tests for generic locks. 
  @@ -45,6 +46,10 @@
       protected static final int READ_LOCK = 1;
       protected static final int WRITE_LOCK = 2;
       
  +    protected static final long TIMEOUT = Long.MAX_VALUE;
  +    
  +    private static int deadlockCnt = 0;
  +
       public static Test suite() {
           TestSuite suite = new TestSuite(GenericLockTest.class);
           return suite;
  @@ -137,4 +142,67 @@
           canRead1 = acquireNoWait(lock, owner1, READ_LOCK);
           assertTrue(canRead1);
       }
  -}
  +
  +    public void testDeadlock() throws Throwable {
  +        final String owner1 = "owner1";
  +        final String owner2 = "owner2";
  +        final String owner3 = "owner3";
  +
  +        final String res1 = "res1";
  +        final String res2 = "res2";
  +
  +        // a read / write lock
  +        final ReadWriteLockManager manager = new ReadWriteLockManager(sLogger, TIMEOUT);
  +
  +        for (int i = 0; i < 25; i++) {
  +
  +            final RendezvousBarrier deadlockBarrier1 = new RendezvousBarrier("deadlock1" + i,
  +                    TIMEOUT, sLogger);
  +
  +            Thread deadlock = new Thread(new Runnable() {
  +                public void run() {
  +                    try {
  +                        // first both threads get a lock, this one on res2
  +                        manager.writeLock(owner2, res2);
  +                        synchronized (deadlockBarrier1) {
  +                            deadlockBarrier1.meet();
  +                            deadlockBarrier1.reset();
  +                        }
  +                        // if I am first, the other thread will be dead, i.e.
  +                        // exactly one
  +                        manager.writeLock(owner2, res1);
  +                    } catch (LockException le) {
  +                        assertEquals(le.getCode(), LockException.CODE_DEADLOCK_VICTIM);
  +                        deadlockCnt++;
  +                    } catch (InterruptedException ie) {
  +                    } finally {
  +                        manager.releaseAll(owner2);
  +                    }
  +                }
  +            }, "Deadlock Thread");
  +
  +            deadlock.start();
  +
  +            try {
  +                // first both threads get a lock, this one on res2
  +                manager.readLock(owner1, res1);
  +                synchronized (deadlockBarrier1) {
  +                    deadlockBarrier1.meet();
  +                    deadlockBarrier1.reset();
  +                }
  +                //          if I am first, the other thread will be dead, i.e. exactly
  +                // one
  +                manager.readLock(owner1, res2);
  +            } catch (LockException le) {
  +                assertEquals(le.getCode(), LockException.CODE_DEADLOCK_VICTIM);
  +                deadlockCnt++;
  +            } finally {
  +                manager.releaseAll(owner1);
  +            }
  +
  +            assertEquals(deadlockCnt, 1);
  +            deadlockCnt = 0;
  +        }
  +    }
  +
  +}
  \ No newline at end of file
  
  
  

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


Mime
View raw message