001: package org.apache.lucene.store;
002:
003: /**
004: * Licensed to the Apache Software Foundation (ASF) under one or more
005: * contributor license agreements. See the NOTICE file distributed with
006: * this work for additional information regarding copyright ownership.
007: * The ASF licenses this file to You under the Apache License, Version 2.0
008: * (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019:
020: import java.nio.channels.FileChannel;
021: import java.nio.channels.FileLock;
022: import java.io.File;
023: import java.io.RandomAccessFile;
024: import java.io.IOException;
025: import java.util.HashSet;
026: import java.util.Random;
027:
028: /**
029: * <p>Implements {@link LockFactory} using native OS file
030: * locks. Note that because this LockFactory relies on
031: * java.nio.* APIs for locking, any problems with those APIs
032: * will cause locking to fail. Specifically, on certain NFS
033: * environments the java.nio.* locks will fail (the lock can
034: * incorrectly be double acquired) whereas {@link
035: * SimpleFSLockFactory} worked perfectly in those same
036: * environments. For NFS based access to an index, it's
037: * recommended that you try {@link SimpleFSLockFactory}
038: * first and work around the one limitation that a lock file
039: * could be left when the JVM exits abnormally.</p>
040: *
041: * <p>The primary benefit of {@link NativeFSLockFactory} is
042: * that lock files will be properly removed (by the OS) if
043: * the JVM has an abnormal exit.</p>
044: *
045: * <p>Note that, unlike {@link SimpleFSLockFactory}, the existence of
046: * leftover lock files in the filesystem on exiting the JVM
047: * is fine because the OS will free the locks held against
048: * these files even though the files still remain.</p>
049: *
050: * <p>If you suspect that this or any other LockFactory is
051: * not working properly in your environment, you can easily
052: * test it by using {@link VerifyingLockFactory}, {@link
053: * LockVerifyServer} and {@link LockStressTest}.</p>
054: *
055: * @see LockFactory
056: */
057:
058: public class NativeFSLockFactory extends LockFactory {
059:
060: /**
061: * Directory specified by <code>org.apache.lucene.lockDir</code>
062: * system property. If that is not set, then <code>java.io.tmpdir</code>
063: * system property is used.
064: */
065:
066: private File lockDir;
067:
068: // Simple test to verify locking system is "working". On
069: // NFS, if it's misconfigured, you can hit long (35
070: // second) timeouts which cause Lock.obtain to take far
071: // too long (it assumes the obtain() call takes zero
072: // time). Since it's a configuration problem, we test up
073: // front once on creating the LockFactory:
074: private void acquireTestLock() throws IOException {
075: String randomLockName = "lucene-"
076: + Long.toString(new Random().nextInt(),
077: Character.MAX_RADIX) + "-test.lock";
078:
079: Lock l = makeLock(randomLockName);
080: try {
081: l.obtain();
082: } catch (IOException e) {
083: IOException e2 = new IOException(
084: "Failed to acquire random test lock; please verify filesystem for lock directory '"
085: + lockDir + "' supports locking");
086: e2.initCause(e);
087: throw e2;
088: }
089:
090: l.release();
091: }
092:
093: /**
094: * Create a NativeFSLockFactory instance, with null (unset)
095: * lock directory. This is package-private and is only
096: * used by FSDirectory when creating this LockFactory via
097: * the System property
098: * org.apache.lucene.store.FSDirectoryLockFactoryClass.
099: */
100: NativeFSLockFactory() throws IOException {
101: this ((File) null);
102: }
103:
104: /**
105: * Create a NativeFSLockFactory instance, storing lock
106: * files into the specified lockDirName:
107: *
108: * @param lockDirName where lock files are created.
109: */
110: public NativeFSLockFactory(String lockDirName) throws IOException {
111: this (new File(lockDirName));
112: }
113:
114: /**
115: * Create a NativeFSLockFactory instance, storing lock
116: * files into the specified lockDir:
117: *
118: * @param lockDir where lock files are created.
119: */
120: public NativeFSLockFactory(File lockDir) throws IOException {
121: setLockDir(lockDir);
122: }
123:
124: /**
125: * Set the lock directory. This is package-private and is
126: * only used externally by FSDirectory when creating this
127: * LockFactory via the System property
128: * org.apache.lucene.store.FSDirectoryLockFactoryClass.
129: */
130: void setLockDir(File lockDir) throws IOException {
131: this .lockDir = lockDir;
132: if (lockDir != null) {
133: // Ensure that lockDir exists and is a directory.
134: if (!lockDir.exists()) {
135: if (!lockDir.mkdirs())
136: throw new IOException("Cannot create directory: "
137: + lockDir.getAbsolutePath());
138: } else if (!lockDir.isDirectory()) {
139: throw new IOException(
140: "Found regular file where directory expected: "
141: + lockDir.getAbsolutePath());
142: }
143:
144: acquireTestLock();
145: }
146: }
147:
148: public synchronized Lock makeLock(String lockName) {
149: if (lockPrefix != null)
150: lockName = lockPrefix + "-n-" + lockName;
151: return new NativeFSLock(lockDir, lockName);
152: }
153:
154: public void clearLock(String lockName) throws IOException {
155: // Note that this isn't strictly required anymore
156: // because the existence of these files does not mean
157: // they are locked, but, still do this in case people
158: // really want to see the files go away:
159: if (lockDir.exists()) {
160: if (lockPrefix != null) {
161: lockName = lockPrefix + "-n-" + lockName;
162: }
163: File lockFile = new File(lockDir, lockName);
164: if (lockFile.exists() && !lockFile.delete()) {
165: throw new IOException("Cannot delete " + lockFile);
166: }
167: }
168: }
169: };
170:
171: class NativeFSLock extends Lock {
172:
173: private RandomAccessFile f;
174: private FileChannel channel;
175: private FileLock lock;
176: private File path;
177: private File lockDir;
178:
179: /*
180: * The javadocs for FileChannel state that you should have
181: * a single instance of a FileChannel (per JVM) for all
182: * locking against a given file. To ensure this, we have
183: * a single (static) HashSet that contains the file paths
184: * of all currently locked locks. This protects against
185: * possible cases where different Directory instances in
186: * one JVM (each with their own NativeFSLockFactory
187: * instance) have set the same lock dir and lock prefix.
188: */
189: private static HashSet LOCK_HELD = new HashSet();
190:
191: public NativeFSLock(File lockDir, String lockFileName) {
192: this .lockDir = lockDir;
193: path = new File(lockDir, lockFileName);
194: }
195:
196: public synchronized boolean obtain() throws IOException {
197:
198: if (isLocked()) {
199: // Our instance is already locked:
200: return false;
201: }
202:
203: // Ensure that lockDir exists and is a directory.
204: if (!lockDir.exists()) {
205: if (!lockDir.mkdirs())
206: throw new IOException("Cannot create directory: "
207: + lockDir.getAbsolutePath());
208: } else if (!lockDir.isDirectory()) {
209: throw new IOException(
210: "Found regular file where directory expected: "
211: + lockDir.getAbsolutePath());
212: }
213:
214: String canonicalPath = path.getCanonicalPath();
215:
216: boolean markedHeld = false;
217:
218: try {
219:
220: // Make sure nobody else in-process has this lock held
221: // already, and, mark it held if not:
222:
223: synchronized (LOCK_HELD) {
224: if (LOCK_HELD.contains(canonicalPath)) {
225: // Someone else in this JVM already has the lock:
226: return false;
227: } else {
228: // This "reserves" the fact that we are the one
229: // thread trying to obtain this lock, so we own
230: // the only instance of a channel against this
231: // file:
232: LOCK_HELD.add(canonicalPath);
233: markedHeld = true;
234: }
235: }
236:
237: try {
238: f = new RandomAccessFile(path, "rw");
239: } catch (IOException e) {
240: // On Windows, we can get intermittant "Access
241: // Denied" here. So, we treat this as failure to
242: // acquire the lock, but, store the reason in case
243: // there is in fact a real error case.
244: failureReason = e;
245: f = null;
246: }
247:
248: if (f != null) {
249: try {
250: channel = f.getChannel();
251: try {
252: lock = channel.tryLock();
253: } catch (IOException e) {
254: // At least on OS X, we will sometimes get an
255: // intermittant "Permission Denied" IOException,
256: // which seems to simply mean "you failed to get
257: // the lock". But other IOExceptions could be
258: // "permanent" (eg, locking is not supported via
259: // the filesystem). So, we record the failure
260: // reason here; the timeout obtain (usually the
261: // one calling us) will use this as "root cause"
262: // if it fails to get the lock.
263: failureReason = e;
264: } finally {
265: if (lock == null) {
266: try {
267: channel.close();
268: } finally {
269: channel = null;
270: }
271: }
272: }
273: } finally {
274: if (channel == null) {
275: try {
276: f.close();
277: } finally {
278: f = null;
279: }
280: }
281: }
282: }
283:
284: } finally {
285: if (markedHeld && !isLocked()) {
286: synchronized (LOCK_HELD) {
287: if (LOCK_HELD.contains(canonicalPath)) {
288: LOCK_HELD.remove(canonicalPath);
289: }
290: }
291: }
292: }
293: return isLocked();
294: }
295:
296: public synchronized void release() throws IOException {
297: if (isLocked()) {
298: try {
299: lock.release();
300: } finally {
301: lock = null;
302: try {
303: channel.close();
304: } finally {
305: channel = null;
306: try {
307: f.close();
308: } finally {
309: f = null;
310: synchronized (LOCK_HELD) {
311: LOCK_HELD.remove(path.getCanonicalPath());
312: }
313: }
314: }
315: }
316: if (!path.delete())
317: throw new LockReleaseFailedException(
318: "failed to delete " + path);
319: }
320: }
321:
322: public synchronized boolean isLocked() {
323: return lock != null;
324: }
325:
326: public String toString() {
327: return "NativeFSLock@" + path;
328: }
329:
330: public void finalize() throws Throwable {
331: try {
332: if (isLocked()) {
333: release();
334: }
335: } finally {
336: super.finalize();
337: }
338: }
339: }
|