You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2009/08/18 16:51:34 UTC

svn commit: r805431 - in /jackrabbit/trunk: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/ jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/ jackrabbit-jcr2...

Author: jukka
Date: Tue Aug 18 14:51:34 2009
New Revision: 805431

URL: http://svn.apache.org/viewvc?rev=805431&view=rev
Log:
JCR-1590: JSR 283: Locking

Added a timeout handler to LockManagerImpl. It runs as a scheduled task (at one second intervals) inside a new repository-wide ScheduledExecutorService instance.

Adjusted the TCK test for this and fixed some jcr2spi assumptions about this (the lock can expire from under the SPI layer).

Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockInfo.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
    jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/AbstractLockTest.java
    jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java?rev=805431&r1=805430&r2=805431&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java Tue Aug 18 14:51:34 2009
@@ -34,6 +34,12 @@
 import java.util.Properties;
 import java.util.Set;
 import java.util.Iterator;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 
 import javax.jcr.AccessDeniedException;
 import javax.jcr.Credentials;
@@ -252,6 +258,11 @@
     private WorkspaceEventChannel createWorkspaceEventChannel;
 
     /**
+     * Scheduled executor service.
+     */
+    private final ScheduledExecutorService executor;
+
+    /**
      * Protected constructor.
      *
      * @param repConfig the repository configuration.
@@ -260,6 +271,10 @@
      *                             or another error occurs.
      */
     protected RepositoryImpl(RepositoryConfig repConfig) throws RepositoryException {
+        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(
+                Runtime.getRuntime().availableProcessors() * 2,
+                new ThreadPoolExecutor.CallerRunsPolicy());
+        this.executor = executor;
 
         // Acquire a lock on the repository home
         repLock = repConfig.getRepositoryLockMechanism();
@@ -1166,6 +1181,18 @@
         // wake up threads waiting on this instance's monitor (e.g. workspace janitor)
         notifyAll();
 
+        // Shut down the executor service 
+        executor.shutdown();
+        try {
+            // Wait for all remaining background threads to terminate
+            if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
+                log.warn("Attempting to forcibly shutdown runaway threads");
+                executor.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            log.warn("Interrupted while waiting for background threads", e);
+        }
+
         // finally release repository lock
         if (repLock != null) {
             try {
@@ -1841,7 +1868,8 @@
                 // lock manager is lazily instantiated in order to avoid
                 // 'chicken & egg' bootstrap problems
                 if (lockMgr == null) {
-                    lockMgr = new LockManagerImpl(getSystemSession(), fs);
+                    lockMgr =
+                        new LockManagerImpl(getSystemSession(), fs, executor);
                     if (clusterNode != null && config.isClustered()) {
                         lockChannel = clusterNode.createLockChannel(getName());
                         lockMgr.setEventChannel(lockChannel);

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java?rev=805431&r1=805430&r2=805431&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java Tue Aug 18 14:51:34 2009
@@ -131,18 +131,16 @@
     public long getSecondsRemaining() {
         if (!info.isLive()) {
             return -1;
-        } else {
-            return Long.MAX_VALUE;
         }
 
         // TODO JCR-1590: Disabled until locks get unlocked when they timeout
-//        long timeout = info.getTimeoutTime();
-//        if (timeout == Long.MAX_VALUE) {
-//            return Long.MAX_VALUE;
-//        }
-//
-//        long now = (System.currentTimeMillis() + 999) / 1000; // round up
-//        return Math.max(timeout - now, 1); // must always be positive
+        long timeout = info.getTimeoutTime();
+        if (timeout == Long.MAX_VALUE) {
+            return Long.MAX_VALUE;
+        }
+
+        long now = (System.currentTimeMillis() + 999) / 1000; // round up
+        return Math.max(timeout - now, 1); // must always be positive
     }
 
     /**

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockInfo.java?rev=805431&r1=805430&r2=805431&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockInfo.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockInfo.java Tue Aug 18 14:51:34 2009
@@ -227,6 +227,11 @@
         return timeoutTime;
     }
 
+    public boolean isExpired() {
+        return timeoutTime != Long.MAX_VALUE
+            && timeoutTime * 1000 > System.currentTimeMillis();
+    }
+
     /**
      * Updates the timeout time of this lock based on current time and
      * the timeout hint specified for this lock. The timeout time is always

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java?rev=805431&r1=805430&r2=805431&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java Tue Aug 18 14:51:34 2009
@@ -69,6 +69,9 @@
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Provides the functionality needed for locking and unlocking nodes.
@@ -134,6 +137,11 @@
     };
 
     /**
+     * The periodically invoked lock timeout handler.
+     */
+    private final ScheduledFuture<?> timeoutHandler;
+
+    /**
      * System session
      */
     private final SessionImpl sysSession;
@@ -156,12 +164,14 @@
     /**
      * Create a new instance of this class.
      *
-     * @param session system session
-     * @param fs      file system for persisting locks
+     * @param session  system session
+     * @param fs       file system for persisting locks
+     * @param executor scheduled executor service for handling lock timeouts
      * @throws RepositoryException if an error occurs
      */
-    public LockManagerImpl(SessionImpl session, FileSystem fs)
-            throws RepositoryException {
+    public LockManagerImpl(
+            SessionImpl session, FileSystem fs,
+            ScheduledExecutorService executor) throws RepositoryException {
 
         this.sysSession = session;
         this.locksFile = new FileSystemResource(fs, FileSystem.SEPARATOR + LOCKS_FILE);
@@ -178,16 +188,51 @@
             throw new RepositoryException("I/O error while reading locks from '"
                     + locksFile.getPath() + "'", e);
         }
+
+        timeoutHandler = executor.scheduleWithFixedDelay(
+                new TimeoutHandler(), 1, 1, TimeUnit.SECONDS);
     }
 
     /**
      * Close this lock manager. Writes back all changes.
      */
     public void close() {
+        timeoutHandler.cancel(false);
         save();
     }
 
     /**
+     * Periodically (at one second delay) invoked timeout handler. Traverses
+     * all locks and unlocks those that have expired.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-1590">JCR-1590</a>:
+     *      JSR 283: Locking
+     */
+    private class TimeoutHandler implements Runnable {
+        public void run() {
+            lockMap.traverse(new PathMap.ElementVisitor<LockInfo>() {
+                public void elementVisited(PathMap.Element<LockInfo> element) {
+                    LockInfo info = element.get();
+                    if (info != null && info.isLive() && info.isExpired()) {
+                        NodeId id = info.getId();
+                        SessionImpl holder = info.getLockHolder();
+                        if (holder == null) {
+                            info.setLockHolder(sysSession);
+                            holder = sysSession;
+                        }
+                        try {
+                            // FIXME: This session access is not thread-safe!
+                            unlock(holder.getNodeById(id));
+                        } catch (RepositoryException e) {
+                            log.warn("Unable to expire the lock " + id, e);
+                        }
+                    }
+                }
+            }, false);
+        }
+    }
+
+    /**
      * Read locks from locks file and populate path map
      */
     private void load() throws FileSystemException {

Modified: jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/AbstractLockTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/AbstractLockTest.java?rev=805431&r1=805430&r2=805431&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/AbstractLockTest.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/AbstractLockTest.java Tue Aug 18 14:51:34 2009
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.test.api.lock;
 
 import javax.jcr.Node;
+import javax.jcr.Property;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -257,39 +258,33 @@
     /**
      * Test expiration of the lock
      */
-    public void testLockExpiration() throws RepositoryException, NotExecutableException {
+    public synchronized void testLockExpiration()
+            throws RepositoryException, NotExecutableException {
         lockedNode.unlock();
 
-        ObservationManager obsMgr = superuser.getWorkspace().getObservationManager();
-        EventResult listener = new EventResult(((JUnitTest) this).log);
-        try {
-            obsMgr.addEventListener(listener, Event.PROPERTY_REMOVED, lockedNode.getPath(), false, new String[0], new String[0], false);
-
-            boolean lockPropRemoved = false;            
-            long hint = 1;
-            lock = lockMgr.lock(lockedNode.getPath(), isDeep(), isSessionScoped(), hint, null);
-            // only test if timeout hint was respected.
-            if (lock.getSecondsRemaining() <= 1) {
-                Event[] evts = listener.getEvents(2000);
-                for (int i = 0; i < evts.length; i++) {
-                    if (evts[i].getType() == Event.PROPERTY_REMOVED &&
-                            evts[i].getPath().endsWith(jcrLockOwner)) {
-                        lockPropRemoved = true;
-                        // lock property has been removed -> make sure lock has
-                        // been released and lock.getSecondsRemaining behaves properly.
-                        assertTrue("A released lock must return a negative number of seconds", lock.getSecondsRemaining() < 0);
-                        assertFalse("If the timeout hint is respected the lock must be automatically released.", lock.isLive());
-                        assertFalse("If the timeout hint is respected the lock must be automatically released.", lockedNode.isLocked());
-                    }
-                }
-                if (!lockPropRemoved) {
-                    fail("If the timeout hint is respected the lock must be automatically released.");
-                }
-            } else {
-                throw new NotExecutableException("timeout hint was ignored.");
+        long hint = 1;
+        lock = lockMgr.lock(
+                lockedNode.getPath(), isDeep(), isSessionScoped(), hint, null);
+
+        // only test if timeout hint was respected.
+        long remaining = lock.getSecondsRemaining();
+        if (remaining <= hint) {
+            try {
+                wait(remaining * 2000); // wait twice as long to be safe
+            } catch (InterruptedException ignore) {
             }
-        } finally {
-            obsMgr.removeEventListener(listener);
+            assertTrue(
+                    "A released lock must return a negative number of seconds",
+                    lock.getSecondsRemaining() < 0);
+            String message = "If the timeout hint is respected the lock"
+                + " must be automatically released.";
+            assertFalse(message, lock.isLive());
+            assertFalse(message, lockedNode.isLocked());
+            assertFalse(message, lockMgr.isLocked(lockedNode.getPath()));
+            assertFalse(message, lockedNode.hasProperty(Property.JCR_LOCK_IS_DEEP));
+            assertFalse(message, lockedNode.hasProperty(Property.JCR_LOCK_OWNER));
+        } else {
+            throw new NotExecutableException("timeout hint was ignored.");
         }
     }
 

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java?rev=805431&r1=805430&r2=805431&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java Tue Aug 18 14:51:34 2009
@@ -496,7 +496,7 @@
                 if (lockHoldingState == nodeState) {
                     return true;
                 } else {
-                    return lockInfo.isDeep();
+                    return lockInfo != null && lockInfo.isDeep();
                 }
             }
         }
@@ -691,14 +691,20 @@
          * @see Lock#getLockOwner()
          */
         public String getLockOwner() {
-            return getLockInfo().getOwner();
+            LockInfo info = getLockInfo();
+            if (info != null) {
+                return info.getOwner();
+            } else {
+                return null;
+            }
         }
 
         /**
          * @see Lock#isDeep()
          */
         public boolean isDeep() {
-            return getLockInfo().isDeep();
+            LockInfo info = getLockInfo();
+            return info != null && info.isDeep();
         }
 
         /**
@@ -719,7 +725,12 @@
             }
 
             updateLockInfo();
-            return getLockInfo().getLockToken();
+            LockInfo info = getLockInfo();
+            if (info != null) {
+                return info.getLockToken();
+            } else {
+                return null;
+            }
         }
 
         /**
@@ -734,7 +745,8 @@
          * @see Lock#isSessionScoped()
          */
         public boolean isSessionScoped() {
-            return getLockInfo().isSessionScoped();
+            LockInfo info = getLockInfo();
+            return info != null && info.isSessionScoped();
         }
 
         /**
@@ -766,7 +778,8 @@
          * @see javax.jcr.lock.Lock#isLockOwningSession()
          */
         public boolean isLockOwningSession(){
-            return lockState.lockInfo.isLockOwner();
+            LockInfo info = getLockInfo();
+            return info != null && info.isLockOwner();
         }
 
         //----------------------------------------------< LockTokenListener >---