| NativeFSLockFactory.java |
1 package org.apache.lucene.store;
2
3 /**
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
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
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.
18 */
19
20 import java.nio.channels.FileChannel;
21 import java.nio.channels.FileLock;
22 import java.io.File;
23 import java.io.RandomAccessFile;
24 import java.io.IOException;
25 import java.util.HashSet;
26 import java.util.Random;
27
28 /**
29 * <p>Implements {@link LockFactory} using native OS file
30 * locks. Note that because this LockFactory relies on
31 * java.nio.* APIs for locking, any problems with those APIs
32 * will cause locking to fail. Specifically, on certain NFS
33 * environments the java.nio.* locks will fail (the lock can
34 * incorrectly be double acquired) whereas {@link
35 * SimpleFSLockFactory} worked perfectly in those same
36 * environments. For NFS based access to an index, it's
37 * recommended that you try {@link SimpleFSLockFactory}
38 * first and work around the one limitation that a lock file
39 * could be left when the JVM exits abnormally.</p>
40 *
41 * <p>The primary benefit of {@link NativeFSLockFactory} is
42 * that lock files will be properly removed (by the OS) if
43 * the JVM has an abnormal exit.</p>
44 *
45 * <p>Note that, unlike {@link SimpleFSLockFactory}, the existence of
46 * leftover lock files in the filesystem on exiting the JVM
47 * is fine because the OS will free the locks held against
48 * these files even though the files still remain.</p>
49 *
50 * <p>If you suspect that this or any other LockFactory is
51 * not working properly in your environment, you can easily
52 * test it by using {@link VerifyingLockFactory}, {@link
53 * LockVerifyServer} and {@link LockStressTest}.</p>
54 *
55 * @see LockFactory
56 */
57
58 public class NativeFSLockFactory extends LockFactory {
59
60 /**
61 * Directory specified by <code>org.apache.lucene.lockDir</code>
62 * system property. If that is not set, then <code>java.io.tmpdir</code>
63 * system property is used.
64 */
65
66 private File lockDir;
67
68 // Simple test to verify locking system is "working". On
69 // NFS, if it's misconfigured, you can hit long (35
70 // second) timeouts which cause Lock.obtain to take far
71 // too long (it assumes the obtain() call takes zero
72 // time). Since it's a configuration problem, we test up
73 // front once on creating the LockFactory:
74 private void acquireTestLock() throws IOException {
75 String randomLockName = "lucene-" + Long.toString(new Random().nextInt(), Character.MAX_RADIX) + "-test.lock";
76
77 Lock l = makeLock(randomLockName);
78 try {
79 l.obtain();
80 } catch (IOException e) {
81 IOException e2 = new IOException("Failed to acquire random test lock; please verify filesystem for lock directory '" + lockDir + "' supports locking");
82 e2.initCause(e);
83 throw e2;
84 }
85
86 l.release();
87 }
88
89 /**
90 * Create a NativeFSLockFactory instance, with null (unset)
91 * lock directory. This is package-private and is only
92 * used by FSDirectory when creating this LockFactory via
93 * the System property
94 * org.apache.lucene.store.FSDirectoryLockFactoryClass.
95 */
96 NativeFSLockFactory() throws IOException {
97 this((File) null);
98 }
99
100 /**
101 * Create a NativeFSLockFactory instance, storing lock
102 * files into the specified lockDirName:
103 *
104 * @param lockDirName where lock files are created.
105 */
106 public NativeFSLockFactory(String lockDirName) throws IOException {
107 this(new File(lockDirName));
108 }
109
110 /**
111 * Create a NativeFSLockFactory instance, storing lock
112 * files into the specified lockDir:
113 *
114 * @param lockDir where lock files are created.
115 */
116 public NativeFSLockFactory(File lockDir) throws IOException {
117 setLockDir(lockDir);
118 }
119
120 /**
121 * Set the lock directory. This is package-private and is
122 * only used externally by FSDirectory when creating this
123 * LockFactory via the System property
124 * org.apache.lucene.store.FSDirectoryLockFactoryClass.
125 */
126 void setLockDir(File lockDir) throws IOException {
127 this.lockDir = lockDir;
128 if (lockDir != null) {
129 // Ensure that lockDir exists and is a directory.
130 if (!lockDir.exists()) {
131 if (!lockDir.mkdirs())
132 throw new IOException("Cannot create directory: " +
133 lockDir.getAbsolutePath());
134 } else if (!lockDir.isDirectory()) {
135 throw new IOException("Found regular file where directory expected: " +
136 lockDir.getAbsolutePath());
137 }
138
139 acquireTestLock();
140 }
141 }
142
143 public synchronized Lock makeLock(String lockName) {
144 if (lockPrefix != null)
145 lockName = lockPrefix + "-n-" + lockName;
146 return new NativeFSLock(lockDir, lockName);
147 }
148
149 public void clearLock(String lockName) throws IOException {
150 // Note that this isn't strictly required anymore
151 // because the existence of these files does not mean
152 // they are locked, but, still do this in case people
153 // really want to see the files go away:
154 if (lockDir.exists()) {
155 if (lockPrefix != null) {
156 lockName = lockPrefix + "-n-" + lockName;
157 }
158 File lockFile = new File(lockDir, lockName);
159 if (lockFile.exists() && !lockFile.delete()) {
160 throw new IOException("Cannot delete " + lockFile);
161 }
162 }
163 }
164 };
165
166 class NativeFSLock extends Lock {
167
168 private RandomAccessFile f;
169 private FileChannel channel;
170 private FileLock lock;
171 private File path;
172 private File lockDir;
173
174 /*
175 * The javadocs for FileChannel state that you should have
176 * a single instance of a FileChannel (per JVM) for all
177 * locking against a given file. To ensure this, we have
178 * a single (static) HashSet that contains the file paths
179 * of all currently locked locks. This protects against
180 * possible cases where different Directory instances in
181 * one JVM (each with their own NativeFSLockFactory
182 * instance) have set the same lock dir and lock prefix.
183 */
184 private static HashSet LOCK_HELD = new HashSet();
185
186 public NativeFSLock(File lockDir, String lockFileName) {
187 this.lockDir = lockDir;
188 path = new File(lockDir, lockFileName);
189 }
190
191 public synchronized boolean obtain() throws IOException {
192
193 if (isLocked()) {
194 // Our instance is already locked:
195 return false;
196 }
197
198 // Ensure that lockDir exists and is a directory.
199 if (!lockDir.exists()) {
200 if (!lockDir.mkdirs())
201 throw new IOException("Cannot create directory: " +
202 lockDir.getAbsolutePath());
203 } else if (!lockDir.isDirectory()) {
204 throw new IOException("Found regular file where directory expected: " +
205 lockDir.getAbsolutePath());
206 }
207
208 String canonicalPath = path.getCanonicalPath();
209
210 boolean markedHeld = false;
211
212 try {
213
214 // Make sure nobody else in-process has this lock held
215 // already, and, mark it held if not:
216
217 synchronized(LOCK_HELD) {
218 if (LOCK_HELD.contains(canonicalPath)) {
219 // Someone else in this JVM already has the lock:
220 return false;
221 } else {
222 // This "reserves" the fact that we are the one
223 // thread trying to obtain this lock, so we own
224 // the only instance of a channel against this
225 // file:
226 LOCK_HELD.add(canonicalPath);
227 markedHeld = true;
228 }
229 }
230
231 try {
232 f = new RandomAccessFile(path, "rw");
233 } catch (IOException e) {
234 // On Windows, we can get intermittant "Access
235 // Denied" here. So, we treat this as failure to
236 // acquire the lock, but, store the reason in case
237 // there is in fact a real error case.
238 failureReason = e;
239 f = null;
240 }
241
242 if (f != null) {
243 try {
244 channel = f.getChannel();
245 try {
246 lock = channel.tryLock();
247 } catch (IOException e) {
248 // At least on OS X, we will sometimes get an
249 // intermittant "Permission Denied" IOException,
250 // which seems to simply mean "you failed to get
251 // the lock". But other IOExceptions could be
252 // "permanent" (eg, locking is not supported via
253 // the filesystem). So, we record the failure
254 // reason here; the timeout obtain (usually the
255 // one calling us) will use this as "root cause"
256 // if it fails to get the lock.
257 failureReason = e;
258 } finally {
259 if (lock == null) {
260 try {
261 channel.close();
262 } finally {
263 channel = null;
264 }
265 }
266 }
267 } finally {
268 if (channel == null) {
269 try {
270 f.close();
271 } finally {
272 f = null;
273 }
274 }
275 }
276 }
277
278 } finally {
279 if (markedHeld && !isLocked()) {
280 synchronized(LOCK_HELD) {
281 if (LOCK_HELD.contains(canonicalPath)) {
282 LOCK_HELD.remove(canonicalPath);
283 }
284 }
285 }
286 }
287 return isLocked();
288 }
289
290 public synchronized void release() throws IOException {
291 if (isLocked()) {
292 try {
293 lock.release();
294 } finally {
295 lock = null;
296 try {
297 channel.close();
298 } finally {
299 channel = null;
300 try {
301 f.close();
302 } finally {
303 f = null;
304 synchronized(LOCK_HELD) {
305 LOCK_HELD.remove(path.getCanonicalPath());
306 }
307 }
308 }
309 }
310 if (!path.delete())
311 throw new LockReleaseFailedException("failed to delete " + path);
312 }
313 }
314
315 public synchronized boolean isLocked() {
316 return lock != null;
317 }
318
319 public String toString() {
320 return "NativeFSLock@" + path;
321 }
322
323 public void finalize() throws Throwable {
324 try {
325 if (isLocked()) {
326 release();
327 }
328 } finally {
329 super.finalize();
330 }
331 }
332 }
333 | NativeFSLockFactory.java |