X-Git-Url: https://git.mdrn.pl/pylucene.git/blobdiff_plain/a2e61f0c04805cfcb8706176758d1283c7e3a55c..aaeed5504b982cf3545252ab528713250aa33eed:/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/store/NativeFSLockFactory.java?ds=sidebyside diff --git a/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/store/NativeFSLockFactory.java b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/store/NativeFSLockFactory.java new file mode 100755 index 0000000..f4f63e6 --- /dev/null +++ b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/store/NativeFSLockFactory.java @@ -0,0 +1,330 @@ +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; + +/** + *

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.

+ * + *

The primary benefit of {@link NativeFSLockFactory} is + * that lock files will be properly removed (by the OS) if + * the JVM has an abnormal exit.

+ * + *

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.

+ * + *

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}.

+ * + * @see LockFactory + */ + +public class NativeFSLockFactory extends FSLockFactory { + + /** + * Create a NativeFSLockFactory instance, with null (unset) + * lock directory. When you pass this factory to a {@link FSDirectory} + * subclass, the lock directory is automatically set to the + * directory itself. Be sure to create one instance for each directory + * your create! + */ + public 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); + } + + @Override + public synchronized Lock makeLock(String lockName) { + if (lockPrefix != null) + lockName = lockPrefix + "-" + lockName; + return new NativeFSLock(lockDir, lockName); + } + + @Override + 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()) { + + // Try to release the lock first - if it's held by another process, this + // method should not silently fail. + // NOTE: makeLock fixes the lock name by prefixing it w/ lockPrefix. + // Therefore it should be called before the code block next which prefixes + // the given name. + makeLock(lockName).release(); + + if (lockPrefix != null) { + lockName = lockPrefix + "-" + lockName; + } + + // As mentioned above, we don't care if the deletion of the file failed. + new File(lockDir, lockName).delete(); + } + } +} + +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 (locks are tracked per + * FileChannel instance in Java 1.4/1.5). Even using the same + * FileChannel instance is not completely thread-safe with Java + * 1.4/1.5 though. To work around 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. However, this will not + * work when LockFactorys are created by different + * classloaders (eg multiple webapps). + * + * TODO: Java 1.6 tracks system wide locks in a thread safe manner + * (same FileChannel instance or not), so we may want to + * change this when Lucene moves to Java 1.6. + */ + private static HashSet LOCK_HELD = new HashSet(); + + public NativeFSLock(File lockDir, String lockFileName) { + this.lockDir = lockDir; + path = new File(lockDir, lockFileName); + } + + private synchronized boolean lockExists() { + return lock != null; + } + + @Override + public synchronized boolean obtain() throws IOException { + + if (lockExists()) { + // 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 intermittent "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 + // intermittent "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 && !lockExists()) { + synchronized(LOCK_HELD) { + if (LOCK_HELD.contains(canonicalPath)) { + LOCK_HELD.remove(canonicalPath); + } + } + } + } + return lockExists(); + } + + @Override + public synchronized void release() throws IOException { + if (lockExists()) { + 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()); + } + } + } + } + // LUCENE-2421: we don't care anymore if the file cannot be deleted + // because it's held up by another process (e.g. AntiVirus). NativeFSLock + // does not depend on the existence/absence of the lock file + path.delete(); + } else { + // if we don't hold the lock, and somebody still called release(), for + // example as a result of calling IndexWriter.unlock(), we should attempt + // to obtain the lock and release it. If the obtain fails, it means the + // lock cannot be released, and we should throw a proper exception rather + // than silently failing/not doing anything. + boolean obtained = false; + try { + if (!(obtained = obtain())) { + throw new LockReleaseFailedException( + "Cannot forcefully unlock a NativeFSLock which is held by another indexer component: " + + path); + } + } finally { + if (obtained) { + release(); + } + } + } + } + + @Override + public synchronized boolean isLocked() { + // The test for is isLocked is not directly possible with native file locks: + + // First a shortcut, if a lock reference in this instance is available + if (lockExists()) return true; + + // Look if lock file is present; if not, there can definitely be no lock! + if (!path.exists()) return false; + + // Try to obtain and release (if was locked) the lock + try { + boolean obtained = obtain(); + if (obtained) release(); + return !obtained; + } catch (IOException ioe) { + return false; + } + } + + @Override + public String toString() { + return "NativeFSLock@" + path; + } +}