You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by st...@apache.org on 2007/05/25 15:49:16 UTC

svn commit: r541651 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/ main/java/org/apache/jackrabbit/core/util/ test/java/org/apache/jackrabbit/core/util/

Author: stefan
Date: Fri May 25 06:49:15 2007
New Revision: 541651

URL: http://svn.apache.org/viewvc?view=rev&rev=541651
Log:
JCR-933: RepositoryImpl.acquireRepositoryLock() fails to detect that the file lock is already held by the current process

committing jukka's patch

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/RepositoryLock.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/RepositoryLockTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/TestAll.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.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?view=diff&rev=541651&r1=541650&r2=541651
==============================================================================
--- 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 Fri May 25 06:49:15 2007
@@ -55,6 +55,7 @@
 import org.apache.jackrabbit.core.state.ItemStateException;
 import org.apache.jackrabbit.core.state.ManagedMLRUItemStateCacheFactory;
 import org.apache.jackrabbit.core.state.SharedItemStateManager;
+import org.apache.jackrabbit.core.util.RepositoryLock;
 import org.apache.jackrabbit.core.version.VersionManager;
 import org.apache.jackrabbit.core.version.VersionManagerImpl;
 import org.apache.jackrabbit.name.NamespaceResolver;
@@ -69,10 +70,6 @@
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
 import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.util.Arrays;
@@ -105,11 +102,6 @@
     private static Logger log = LoggerFactory.getLogger(RepositoryImpl.class);
 
     /**
-     * repository home lock
-     */
-    private static final String REPOSITORY_LOCK = ".lock";
-
-    /**
      * hardcoded id of the repository root node
      */
     public static final NodeId ROOT_NODE_ID = NodeId.valueOf("cafebabe-cafe-babe-cafe-babecafebabe");
@@ -193,7 +185,7 @@
     /**
      * the lock that guards instantiation of multiple repositories.
      */
-    private FileLock repLock;
+    private RepositoryLock repLock;
 
     /**
      * Clustered node used, <code>null</code> if clustering is not configured.
@@ -232,7 +224,9 @@
 
         this.repConfig = repConfig;
 
-        acquireRepositoryLock();
+        // Acquire a lock on the repository home
+        repLock = new RepositoryLock(repConfig.getHomeDir());
+        repLock.acquire();
 
         // setup file systems
         repStore = repConfig.getFileSystemConfig().createFileSystem();
@@ -393,67 +387,6 @@
     }
 
     /**
-     * Lock the repository home.
-     *
-     * @throws RepositoryException if the repository lock can not be acquired
-     */
-    protected void acquireRepositoryLock() throws RepositoryException {
-        File home = new File(this.repConfig.getHomeDir());
-        File lock = new File(home, REPOSITORY_LOCK);
-
-        if (lock.exists()) {
-            log.warn("Existing lock file at " + lock.getAbsolutePath()
-                    + " detected. Repository was not shut down properly.");
-        } else {
-            try {
-                lock.createNewFile();
-            } catch (IOException e) {
-                throw new RepositoryException(
-                    "Unable to create lock file at " + lock.getAbsolutePath(), e);
-            }
-        }
-        try {
-            repLock = new RandomAccessFile(lock, "rw").getChannel().tryLock();
-        } catch (IOException e) {
-            throw new RepositoryException(
-                "Unable to lock file at " + lock.getAbsolutePath(), e);
-        } catch (OverlappingFileLockException e) {
-            throw new RepositoryException(
-                    "The repository home at " + home.getAbsolutePath()
-                    + " appears to be in use since the file at "
-                    + lock.getAbsolutePath() + " is already locked by the current process.");
-        }
-        if (repLock == null) {
-            throw new RepositoryException(
-                    "The repository home at " + home.getAbsolutePath()
-                    + " appears to be in use since the file at "
-                    + lock.getAbsolutePath() + " is locked by another process.");
-        }
-    }
-
-    /**
-     * Release repository lock
-     */
-    protected void releaseRepositoryLock() {
-        if (repLock != null) {
-            try {
-                FileChannel channel = repLock.channel();
-                repLock.release();
-                channel.close();
-            } catch (IOException e) {
-                // ignore
-            }
-        }
-        repLock = null;
-
-        File home = new File(this.repConfig.getHomeDir());
-        File lock = new File(home, REPOSITORY_LOCK);
-        if (!lock.delete()) {
-            log.error("Unable to release repository lock");
-        }
-    }
-
-    /**
      * Returns the root node uuid.
      * @param fs
      * @return
@@ -1041,7 +974,7 @@
         notifyAll();
 
         // finally release repository lock
-        releaseRepositoryLock();
+        repLock.release();
 
         log.info("Repository has been shutdown");
     }

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/RepositoryLock.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/RepositoryLock.java?view=auto&rev=541651
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/RepositoryLock.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/RepositoryLock.java Fri May 25 06:49:15 2007
@@ -0,0 +1,188 @@
+/*
+ * 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.jackrabbit.core.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+
+import javax.jcr.RepositoryException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Exclusive lock on a repository home directory. This class encapsulates
+ * collective experience on how to acquire an exclusive lock on a given
+ * directory. The lock is expected to be exclusive both across process
+ * boundaries and within a single JVM. The lock mechanism must also work
+ * consistently on a variety of operating systems and JVM implementations.
+ *
+ * @see https://issues.apache.org/jira/browse/JCR-213
+ * @see https://issues.apache.org/jira/browse/JCR-233
+ * @see https://issues.apache.org/jira/browse/JCR-254
+ * @see https://issues.apache.org/jira/browse/JCR-912
+ * @see https://issues.apache.org/jira/browse/JCR-933
+ */
+public class RepositoryLock {
+
+    /**
+     * Name of the lock file within a directory.
+     */
+    private static final String LOCK = ".lock";
+
+    /**
+     * Logger instance.
+     */
+    private static final Logger log =
+        LoggerFactory.getLogger(RepositoryLock.class);
+
+    /**
+     * The locked directory.
+     */
+    private final File directory;
+
+    /**
+     * The lock file within the given directory.
+     */
+    private final File file;
+
+    /**
+     * Unique identifier (canonical path name) of the locked directory.
+     * Used to ensure exclusive locking within a single JVM.
+     * 
+     * @see https://issues.apache.org/jira/browse/JCR-933
+     */
+    private final String identifier;
+
+    /**
+     * The file lock. Used to ensure exlusive lockin across process boundaries.
+     *
+     * @see https://issues.apache.org/jira/browse/JCR-233
+     */
+    private FileLock lock;
+
+    /**
+     * Creates a lock instance for the given directory path. An instantiated
+     * lock still needs to be explicitly acquired using the {@link #acquire()}
+     * method.
+     *
+     * @param path directory path
+     * @throws RepositoryException if the canonical path of the directory
+     *                             can not be determined
+     */
+    public RepositoryLock(String path) throws RepositoryException {
+        try {
+            directory = new File(path).getCanonicalFile();
+            file = new File(directory, LOCK);
+            identifier = RepositoryLock.class.getName()
+                + ":" + directory.getPath().intern();
+            lock = null;
+        } catch (IOException e) {
+            throw new RepositoryException(
+                    "Unable to determine canonical path of " + path, e);
+        }
+    }
+
+    /**
+     * Lock the repository home.
+     *
+     * @throws RepositoryException if the repository lock can not be acquired
+     */
+    public void acquire() throws RepositoryException {
+        if (file.exists()) {
+            log.warn("Existing lock file " + file + " detected."
+                    + " Repository was not shut down properly.");
+        }
+
+        try {
+            lock = new RandomAccessFile(file, "rw").getChannel().tryLock();
+        } catch (IOException e) {
+            throw new RepositoryException(
+                    "Unable to create or lock file " + file, e);
+        } catch (OverlappingFileLockException e) {
+            // JCR-912: OverlappingFileLockException with JRE 1.6
+            throw new RepositoryException(
+                    "The repository home " + directory + " appears to be in use"
+                    + " since the file named " + file.getName()
+                    + " is already locked by the current process.");
+        }
+
+        if (lock == null) {
+            throw new RepositoryException(
+                    "The repository home " + directory + " appears to be in use"
+                    + " since the file named " + file.getName()
+                    + " is locked by another process.");
+        }
+
+        // JCR-933: due to a bug in java 1.4/1.5 on *nix platforms
+        // it's possible that java.nio.channels.FileChannel.tryLock()
+        // returns a non-null FileLock object although the lock is already
+        // held by *this* jvm process
+        synchronized (identifier) {
+            if (null != System.getProperty(identifier)) {
+                // note that the newly acquired (redundant) file lock
+                // is deliberately *not* released because this could
+                // potentially cause, depending on the implementation,
+                // the previously acquired lock(s) to be released
+                // as well
+                throw new RepositoryException(
+                        "The repository home " + directory + " appears to be"
+                        + " already locked by the current process.");
+            } else {
+                try {
+                    System.setProperty(identifier, identifier);
+                } catch (SecurityException e) {
+                    log.warn("Unable to set system property: " + identifier, e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Releases repository lock.
+     */
+    public void release() {
+        if (lock != null) {
+            try {
+                FileChannel channel = lock.channel();
+                lock.release();
+                channel.close();
+            } catch (IOException e) {
+                // ignore
+            }
+            lock = null;
+        }
+
+        if (!file.delete()) {
+            log.error("Unable to release repository lock");
+        }
+
+        // JCR-933: see #acquire()
+        synchronized (identifier) {
+            try {
+                System.getProperties().remove(identifier);
+            } catch (SecurityException e) {
+                log.error("Unable to clear system property: " + identifier, e);
+            }
+        }
+    }
+
+}

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/RepositoryLockTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/RepositoryLockTest.java?view=auto&rev=541651
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/RepositoryLockTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/RepositoryLockTest.java Fri May 25 06:49:15 2007
@@ -0,0 +1,125 @@
+/*
+ * 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.jackrabbit.core.util;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.util.RepositoryLock;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for the {@link RepositoryLock} class.
+ */
+public class RepositoryLockTest extends TestCase {
+
+    /**
+     * The temporary directory used for testing.
+     */
+    private File directory;
+
+    /**
+     * Sets up the temporary directory used for testing.
+     */
+    protected void setUp() throws IOException {
+        directory = File.createTempFile("RepositoryLock", "Test");
+        directory.delete();
+        directory.mkdir();
+    }
+
+    /**
+     * Deletes the temporary directory used for testing.
+     */
+    protected void tearDown() {
+        delete(directory);
+    }
+
+    /**
+     * Recursively deletes the given file or directory.
+     *
+     * @param file file or directory to be deleted
+     */
+    private void delete(File file) {
+        File[] files = file.listFiles();
+        for (int i = 0; files != null && i < files.length; i++) {
+            delete(files[i]);
+        }
+        file.delete();
+    }
+
+    /**
+     * Tests that when an acquired lock is released, the lock file is
+     * automatically removed.
+     *
+     * @throws RepositoryException if an error occurs
+     */
+    public void testNoFilesLeftBehind() throws RepositoryException {
+        RepositoryLock lock = new RepositoryLock(directory.getPath());
+        lock.acquire();
+        lock.release();
+        assertEquals(
+                "Some files left behind by a lock",
+                0, directory.listFiles().length);
+    }
+
+    /**
+     * Tests that locking is exclusive within a single JVM.
+     *
+     * @throws RepositoryException if an error occurs
+     */
+    public void testTwoLocks() throws RepositoryException {
+        RepositoryLock lockA = new RepositoryLock(directory.getPath());
+        RepositoryLock lockB = new RepositoryLock(directory.getPath());
+        lockA.acquire();
+        try {
+            lockB.acquire();
+            fail("Can acquire an already acquired lock");
+        } catch (RepositoryException e) {
+        }
+        lockA.release();
+        try {
+            lockB.acquire();
+        } catch (RepositoryException e) {
+            fail("Can not acquire a released lock");
+        }
+        lockB.release();
+    }
+
+    /**
+     * Tests that the canonical path is used for locking.
+     *
+     * @see https://issues.apache.org/jira/browse/JCR-933
+     * @throws RepositoryException
+     */
+    public void testCanonicalPath() throws RepositoryException {
+        RepositoryLock lockA = new RepositoryLock(directory.getPath());
+        lockA.acquire();
+        try {
+            File parent = new File(directory, "..");
+            RepositoryLock lockB = new RepositoryLock(
+                    new File(parent, directory.getName()).getPath());
+            lockB.acquire();
+            fail("Can acquire an already acquired lock using a different path");
+        } catch (RepositoryException e) {
+        }
+        lockA.release();
+    }
+
+}

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/TestAll.java?view=auto&rev=541651
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/TestAll.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/TestAll.java Fri May 25 06:49:15 2007
@@ -0,0 +1,38 @@
+/*
+ * 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.jackrabbit.core.util;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test suite that includes all testcases for the util module.
+ */
+public class TestAll extends TestCase {
+
+    /**
+     * Returns a test suite that executes all tests inside this package.
+     *
+     * @return a test suite that executes all tests inside this package
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite("Utility tests");
+        suite.addTestSuite(RepositoryLockTest.class);
+        return suite;
+    }
+}