1 package org.apache.lucene.store;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import java.nio.channels.FileChannel;
21 import java.nio.channels.FileLock;
23 import java.io.RandomAccessFile;
24 import java.io.IOException;
25 import java.util.HashSet;
28 * <p>Implements {@link LockFactory} using native OS file
29 * locks. Note that because this LockFactory relies on
30 * java.nio.* APIs for locking, any problems with those APIs
31 * will cause locking to fail. Specifically, on certain NFS
32 * environments the java.nio.* locks will fail (the lock can
33 * incorrectly be double acquired) whereas {@link
34 * SimpleFSLockFactory} worked perfectly in those same
35 * environments. For NFS based access to an index, it's
36 * recommended that you try {@link SimpleFSLockFactory}
37 * first and work around the one limitation that a lock file
38 * could be left when the JVM exits abnormally.</p>
40 * <p>The primary benefit of {@link NativeFSLockFactory} is
41 * that lock files will be properly removed (by the OS) if
42 * the JVM has an abnormal exit.</p>
44 * <p>Note that, unlike {@link SimpleFSLockFactory}, the existence of
45 * leftover lock files in the filesystem on exiting the JVM
46 * is fine because the OS will free the locks held against
47 * these files even though the files still remain.</p>
49 * <p>If you suspect that this or any other LockFactory is
50 * not working properly in your environment, you can easily
51 * test it by using {@link VerifyingLockFactory}, {@link
52 * LockVerifyServer} and {@link LockStressTest}.</p>
57 public class NativeFSLockFactory extends FSLockFactory {
60 * Create a NativeFSLockFactory instance, with null (unset)
61 * lock directory. When you pass this factory to a {@link FSDirectory}
62 * subclass, the lock directory is automatically set to the
63 * directory itself. Be sure to create one instance for each directory
66 public NativeFSLockFactory() throws IOException {
71 * Create a NativeFSLockFactory instance, storing lock
72 * files into the specified lockDirName:
74 * @param lockDirName where lock files are created.
76 public NativeFSLockFactory(String lockDirName) throws IOException {
77 this(new File(lockDirName));
81 * Create a NativeFSLockFactory instance, storing lock
82 * files into the specified lockDir:
84 * @param lockDir where lock files are created.
86 public NativeFSLockFactory(File lockDir) throws IOException {
91 public synchronized Lock makeLock(String lockName) {
92 if (lockPrefix != null)
93 lockName = lockPrefix + "-" + lockName;
94 return new NativeFSLock(lockDir, lockName);
98 public void clearLock(String lockName) throws IOException {
99 // Note that this isn't strictly required anymore
100 // because the existence of these files does not mean
101 // they are locked, but, still do this in case people
102 // really want to see the files go away:
103 if (lockDir.exists()) {
105 // Try to release the lock first - if it's held by another process, this
106 // method should not silently fail.
107 // NOTE: makeLock fixes the lock name by prefixing it w/ lockPrefix.
108 // Therefore it should be called before the code block next which prefixes
110 makeLock(lockName).release();
112 if (lockPrefix != null) {
113 lockName = lockPrefix + "-" + lockName;
116 // As mentioned above, we don't care if the deletion of the file failed.
117 new File(lockDir, lockName).delete();
122 class NativeFSLock extends Lock {
124 private RandomAccessFile f;
125 private FileChannel channel;
126 private FileLock lock;
128 private File lockDir;
131 * The javadocs for FileChannel state that you should have
132 * a single instance of a FileChannel (per JVM) for all
133 * locking against a given file (locks are tracked per
134 * FileChannel instance in Java 1.4/1.5). Even using the same
135 * FileChannel instance is not completely thread-safe with Java
136 * 1.4/1.5 though. To work around this, we have a single (static)
137 * HashSet that contains the file paths of all currently
138 * locked locks. This protects against possible cases
139 * where different Directory instances in one JVM (each
140 * with their own NativeFSLockFactory instance) have set
141 * the same lock dir and lock prefix. However, this will not
142 * work when LockFactorys are created by different
143 * classloaders (eg multiple webapps).
145 * TODO: Java 1.6 tracks system wide locks in a thread safe manner
146 * (same FileChannel instance or not), so we may want to
147 * change this when Lucene moves to Java 1.6.
149 private static HashSet<String> LOCK_HELD = new HashSet<String>();
151 public NativeFSLock(File lockDir, String lockFileName) {
152 this.lockDir = lockDir;
153 path = new File(lockDir, lockFileName);
156 private synchronized boolean lockExists() {
161 public synchronized boolean obtain() throws IOException {
164 // Our instance is already locked:
168 // Ensure that lockDir exists and is a directory.
169 if (!lockDir.exists()) {
170 if (!lockDir.mkdirs())
171 throw new IOException("Cannot create directory: " +
172 lockDir.getAbsolutePath());
173 } else if (!lockDir.isDirectory()) {
174 throw new IOException("Found regular file where directory expected: " +
175 lockDir.getAbsolutePath());
178 String canonicalPath = path.getCanonicalPath();
180 boolean markedHeld = false;
184 // Make sure nobody else in-process has this lock held
185 // already, and, mark it held if not:
187 synchronized(LOCK_HELD) {
188 if (LOCK_HELD.contains(canonicalPath)) {
189 // Someone else in this JVM already has the lock:
192 // This "reserves" the fact that we are the one
193 // thread trying to obtain this lock, so we own
194 // the only instance of a channel against this
196 LOCK_HELD.add(canonicalPath);
202 f = new RandomAccessFile(path, "rw");
203 } catch (IOException e) {
204 // On Windows, we can get intermittent "Access
205 // Denied" here. So, we treat this as failure to
206 // acquire the lock, but, store the reason in case
207 // there is in fact a real error case.
214 channel = f.getChannel();
216 lock = channel.tryLock();
217 } catch (IOException e) {
218 // At least on OS X, we will sometimes get an
219 // intermittent "Permission Denied" IOException,
220 // which seems to simply mean "you failed to get
221 // the lock". But other IOExceptions could be
222 // "permanent" (eg, locking is not supported via
223 // the filesystem). So, we record the failure
224 // reason here; the timeout obtain (usually the
225 // one calling us) will use this as "root cause"
226 // if it fails to get the lock.
238 if (channel == null) {
249 if (markedHeld && !lockExists()) {
250 synchronized(LOCK_HELD) {
251 if (LOCK_HELD.contains(canonicalPath)) {
252 LOCK_HELD.remove(canonicalPath);
261 public synchronized void release() throws IOException {
275 synchronized(LOCK_HELD) {
276 LOCK_HELD.remove(path.getCanonicalPath());
281 // LUCENE-2421: we don't care anymore if the file cannot be deleted
282 // because it's held up by another process (e.g. AntiVirus). NativeFSLock
283 // does not depend on the existence/absence of the lock file
286 // if we don't hold the lock, and somebody still called release(), for
287 // example as a result of calling IndexWriter.unlock(), we should attempt
288 // to obtain the lock and release it. If the obtain fails, it means the
289 // lock cannot be released, and we should throw a proper exception rather
290 // than silently failing/not doing anything.
291 boolean obtained = false;
293 if (!(obtained = obtain())) {
294 throw new LockReleaseFailedException(
295 "Cannot forcefully unlock a NativeFSLock which is held by another indexer component: "
307 public synchronized boolean isLocked() {
308 // The test for is isLocked is not directly possible with native file locks:
310 // First a shortcut, if a lock reference in this instance is available
311 if (lockExists()) return true;
313 // Look if lock file is present; if not, there can definitely be no lock!
314 if (!path.exists()) return false;
316 // Try to obtain and release (if was locked) the lock
318 boolean obtained = obtain();
319 if (obtained) release();
321 } catch (IOException ioe) {
327 public String toString() {
328 return "NativeFSLock@" + path;