| package org.apache.lucene.store; |
| |
| /** |
| * 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. |
| */ |
| |
| import java.nio.channels.FileChannel; |
| import java.nio.channels.FileLock; |
| import java.io.File; |
| import java.io.RandomAccessFile; |
| import java.io.IOException; |
| import java.util.HashSet; |
| import java.util.Random; |
| |
| /** |
| * <p>Implements {@link LockFactory} using native OS file |
| * locks. Note that because this LockFactory relies on |
| * java.nio.* APIs for locking, any problems with those APIs |
| * will cause locking to fail. Specifically, on certain NFS |
| * environments the java.nio.* locks will fail (the lock can |
| * incorrectly be double acquired) whereas {@link |
| * SimpleFSLockFactory} worked perfectly in those same |
| * environments. For NFS based access to an index, it's |
| * recommended that you try {@link SimpleFSLockFactory} |
| * first and work around the one limitation that a lock file |
| * could be left when the JVM exits abnormally.</p> |
| * |
| * <p>The primary benefit of {@link NativeFSLockFactory} is |
| * that lock files will be properly removed (by the OS) if |
| * the JVM has an abnormal exit.</p> |
| * |
| * <p>Note that, unlike {@link SimpleFSLockFactory}, the existence of |
| * leftover lock files in the filesystem on exiting the JVM |
| * is fine because the OS will free the locks held against |
| * these files even though the files still remain.</p> |
| * |
| * <p>If you suspect that this or any other LockFactory is |
| * not working properly in your environment, you can easily |
| * test it by using {@link VerifyingLockFactory}, {@link |
| * LockVerifyServer} and {@link LockStressTest}.</p> |
| * |
| * @see LockFactory |
| */ |
| |
| public class NativeFSLockFactory extends LockFactory { |
| |
| /** |
| * Directory specified by <code>org.apache.lucene.lockDir</code> |
| * system property. If that is not set, then <code>java.io.tmpdir</code> |
| * system property is used. |
| */ |
| |
| private File lockDir; |
| |
| // Simple test to verify locking system is "working". On |
| // NFS, if it's misconfigured, you can hit long (35 |
| // second) timeouts which cause Lock.obtain to take far |
| // too long (it assumes the obtain() call takes zero |
| // time). Since it's a configuration problem, we test up |
| // front once on creating the LockFactory: |
| private void acquireTestLock() throws IOException { |
| String randomLockName = "lucene-" + Long.toString(new Random().nextInt(), Character.MAX_RADIX) + "-test.lock"; |
| |
| Lock l = makeLock(randomLockName); |
| try { |
| l.obtain(); |
| } catch (IOException e) { |
| IOException e2 = new IOException("Failed to acquire random test lock; please verify filesystem for lock directory '" + lockDir + "' supports locking"); |
| e2.initCause(e); |
| throw e2; |
| } |
| |
| l.release(); |
| } |
| |
| /** |
| * Create a NativeFSLockFactory instance, with null (unset) |
| * lock directory. This is package-private and is only |
| * used by FSDirectory when creating this LockFactory via |
| * the System property |
| * org.apache.lucene.store.FSDirectoryLockFactoryClass. |
| */ |
| NativeFSLockFactory() throws IOException { |
| this((File) null); |
| } |
| |
| /** |
| * Create a NativeFSLockFactory instance, storing lock |
| * files into the specified lockDirName: |
| * |
| * @param lockDirName where lock files are created. |
| */ |
| public NativeFSLockFactory(String lockDirName) throws IOException { |
| this(new File(lockDirName)); |
| } |
| |
| /** |
| * Create a NativeFSLockFactory instance, storing lock |
| * files into the specified lockDir: |
| * |
| * @param lockDir where lock files are created. |
| */ |
| public NativeFSLockFactory(File lockDir) throws IOException { |
| setLockDir(lockDir); |
| } |
| |
| /** |
| * Set the lock directory. This is package-private and is |
| * only used externally by FSDirectory when creating this |
| * LockFactory via the System property |
| * org.apache.lucene.store.FSDirectoryLockFactoryClass. |
| */ |
| void setLockDir(File lockDir) throws IOException { |
| this.lockDir = lockDir; |
| if (lockDir != null) { |
| // Ensure that lockDir exists and is a directory. |
| if (!lockDir.exists()) { |
| if (!lockDir.mkdirs()) |
| throw new IOException("Cannot create directory: " + |
| lockDir.getAbsolutePath()); |
| } else if (!lockDir.isDirectory()) { |
| throw new IOException("Found regular file where directory expected: " + |
| lockDir.getAbsolutePath()); |
| } |
| |
| acquireTestLock(); |
| } |
| } |
| |
| public synchronized Lock makeLock(String lockName) { |
| if (lockPrefix != null) |
| lockName = lockPrefix + "-n-" + lockName; |
| return new NativeFSLock(lockDir, lockName); |
| } |
| |
| public void clearLock(String lockName) throws IOException { |
| // Note that this isn't strictly required anymore |
| // because the existence of these files does not mean |
| // they are locked, but, still do this in case people |
| // really want to see the files go away: |
| if (lockDir.exists()) { |
| if (lockPrefix != null) { |
| lockName = lockPrefix + "-n-" + lockName; |
| } |
| File lockFile = new File(lockDir, lockName); |
| if (lockFile.exists() && !lockFile.delete()) { |
| throw new IOException("Cannot delete " + lockFile); |
| } |
| } |
| } |
| }; |
| |
| class NativeFSLock extends Lock { |
| |
| private RandomAccessFile f; |
| private FileChannel channel; |
| private FileLock lock; |
| private File path; |
| private File lockDir; |
| |
| /* |
| * The javadocs for FileChannel state that you should have |
| * a single instance of a FileChannel (per JVM) for all |
| * locking against a given file. To ensure this, we have |
| * a single (static) HashSet that contains the file paths |
| * of all currently locked locks. This protects against |
| * possible cases where different Directory instances in |
| * one JVM (each with their own NativeFSLockFactory |
| * instance) have set the same lock dir and lock prefix. |
| */ |
| private static HashSet LOCK_HELD = new HashSet(); |
| |
| public NativeFSLock(File lockDir, String lockFileName) { |
| this.lockDir = lockDir; |
| path = new File(lockDir, lockFileName); |
| } |
| |
| public synchronized boolean obtain() throws IOException { |
| |
| if (isLocked()) { |
| // Our instance is already locked: |
| return false; |
| } |
| |
| // Ensure that lockDir exists and is a directory. |
| if (!lockDir.exists()) { |
| if (!lockDir.mkdirs()) |
| throw new IOException("Cannot create directory: " + |
| lockDir.getAbsolutePath()); |
| } else if (!lockDir.isDirectory()) { |
| throw new IOException("Found regular file where directory expected: " + |
| lockDir.getAbsolutePath()); |
| } |
| |
| String canonicalPath = path.getCanonicalPath(); |
| |
| boolean markedHeld = false; |
| |
| try { |
| |
| // Make sure nobody else in-process has this lock held |
| // already, and, mark it held if not: |
| |
| synchronized(LOCK_HELD) { |
| if (LOCK_HELD.contains(canonicalPath)) { |
| // Someone else in this JVM already has the lock: |
| return false; |
| } else { |
| // This "reserves" the fact that we are the one |
| // thread trying to obtain this lock, so we own |
| // the only instance of a channel against this |
| // file: |
| LOCK_HELD.add(canonicalPath); |
| markedHeld = true; |
| } |
| } |
| |
| try { |
| f = new RandomAccessFile(path, "rw"); |
| } catch (IOException e) { |
| // On Windows, we can get intermittant "Access |
| // Denied" here. So, we treat this as failure to |
| // acquire the lock, but, store the reason in case |
| // there is in fact a real error case. |
| failureReason = e; |
| f = null; |
| } |
| |
| if (f != null) { |
| try { |
| channel = f.getChannel(); |
| try { |
| lock = channel.tryLock(); |
| } catch (IOException e) { |
| // At least on OS X, we will sometimes get an |
| // intermittant "Permission Denied" IOException, |
| // which seems to simply mean "you failed to get |
| // the lock". But other IOExceptions could be |
| // "permanent" (eg, locking is not supported via |
| // the filesystem). So, we record the failure |
| // reason here; the timeout obtain (usually the |
| // one calling us) will use this as "root cause" |
| // if it fails to get the lock. |
| failureReason = e; |
| } finally { |
| if (lock == null) { |
| try { |
| channel.close(); |
| } finally { |
| channel = null; |
| } |
| } |
| } |
| } finally { |
| if (channel == null) { |
| try { |
| f.close(); |
| } finally { |
| f = null; |
| } |
| } |
| } |
| } |
| |
| } finally { |
| if (markedHeld && !isLocked()) { |
| synchronized(LOCK_HELD) { |
| if (LOCK_HELD.contains(canonicalPath)) { |
| LOCK_HELD.remove(canonicalPath); |
| } |
| } |
| } |
| } |
| return isLocked(); |
| } |
| |
| public synchronized void release() throws IOException { |
| if (isLocked()) { |
| try { |
| lock.release(); |
| } finally { |
| lock = null; |
| try { |
| channel.close(); |
| } finally { |
| channel = null; |
| try { |
| f.close(); |
| } finally { |
| f = null; |
| synchronized(LOCK_HELD) { |
| LOCK_HELD.remove(path.getCanonicalPath()); |
| } |
| } |
| } |
| } |
| if (!path.delete()) |
| throw new LockReleaseFailedException("failed to delete " + path); |
| } |
| } |
| |
| public synchronized boolean isLocked() { |
| return lock != null; |
| } |
| |
| public String toString() { |
| return "NativeFSLock@" + path; |
| } |
| |
| public void finalize() throws Throwable { |
| try { |
| if (isLocked()) { |
| release(); |
| } |
| } finally { |
| super.finalize(); |
| } |
| } |
| } |