Index: src/java/org/apache/lucene/store/FSDirectory.java =================================================================== --- src/java/org/apache/lucene/store/FSDirectory.java (revision 447480) +++ src/java/org/apache/lucene/store/FSDirectory.java (working copy) @@ -86,11 +86,18 @@ /** The default class which implements filesystem-based directories. */ private static Class IMPL; + private static FSDirectory INSTANCE; static { + String name = + System.getProperty("org.apache.lucene.FSDirectory.class", + FSDirectory.class.getName()); + loadImpl(name); + } + + //load impl class and create a "bare" instance of it. + private static void loadImpl(String name) { + //load the implementation class try { - String name = - System.getProperty("org.apache.lucene.FSDirectory.class", - FSDirectory.class.getName()); IMPL = Class.forName(name); } catch (ClassNotFoundException e) { throw new RuntimeException("cannot load FSDirectory class: " + e.toString(), e); @@ -101,8 +108,32 @@ throw new RuntimeException("cannot load default FSDirectory class: " + e.toString(), e); } } + //create bare instance, to allow overiding the static method fileModified(). + try { + INSTANCE = (FSDirectory) IMPL.newInstance(); + } catch (IllegalAccessException e) { + throw new RuntimeException("cannot instantiate default FSDirectory class: " + e.toString(), e); + } catch (InstantiationException e) { + throw new RuntimeException("cannot instantiate default FSDirectory class: " + e.toString(), e); + } } + /** + * Replace the class implementating FSDirectory. + * @param className full qualified name of FSDirectory implementation + */ + public static void setFSDirImplClass(String className) { + loadImpl(className); + } + + /** + * Return the name of the class implementating FSDirectory. + * @return name of the class implementating FSDirectory. + */ + public static String getFSDirImplClass() { + return IMPL.getName(); + } + private static MessageDigest DIGESTER; static { @@ -265,7 +296,7 @@ } else { lockDir = new File(LOCK_DIR); } - lockFactory = new SimpleFSLockFactory(lockDir); + lockFactory = createDefaultFSLockFactory(lockDir); } } } @@ -282,6 +313,17 @@ init(path, create); } + /** + * Create a default lock factory, to be used when no lock factory is specified. + * A subclass may override this method to have a different default lock factory. + * @param lockDir directory where the locks should be maintained + * @return a default lock factory, to be used when no lock factory is specified. + * @throws IOException + */ + protected LockFactory createDefaultFSLockFactory(File lockDir) throws IOException { + return new SimpleFSLockFactory(lockDir); + } + private synchronized void create() throws IOException { if (!directory.exists()) if (!directory.mkdirs()) @@ -316,12 +358,17 @@ /** Returns the time the named file was last modified. */ public long fileModified(String name) { File file = new File(directory, name); - return file.lastModified(); + return fileModified(file); } /** Returns the time the named file was last modified. */ public static long fileModified(File directory, String name) { File file = new File(directory, name); + return INSTANCE.fileModified(file); + } + + /** Returns the time the named file was last modified. */ + protected long fileModified(File file) { return file.lastModified(); } Index: src/java/org/apache/lucene/store/FSWinDirectory.java =================================================================== --- src/java/org/apache/lucene/store/FSWinDirectory.java (revision 0) +++ src/java/org/apache/lucene/store/FSWinDirectory.java (revision 0) @@ -0,0 +1,515 @@ +package org.apache.lucene.store; + +/* + * Copyright 2006 The Apache Software Foundation + * + * Licensed 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.io.File; +import java.io.IOException; + +/** + * Windows specific implementation of FSDirectory, with retry in case of IO exceptions. + *
+ * This class differs from FSDirectory in that for certain IO operations, + * if they fail with an IO exception, a retry logic is applied. + *
+ * The motivation for retry is that, apparently, Windows FS behavior + * differs from other platforms. Windows API allows Windows applications to + * monitor files, in which case the change or creation of a file can cause the + * monitoring application to examine that file. As result, there might be very + * short intervals in which these files cannot be accessed. In the unfortunate case + * that this is a Lucene index file, the result might be an IOEception thrown, most + * probably saying "Access Denied", and an index can become corrupted if this happens in + * the middle of updating the index. + *
+ * The simple retry applied here would occur only in these rare cases when that + * problem occurs. If the problem does not occur, no retry would take place and there + * should be no performance implications. In those cases that no error occured the + * retry is not applied at all. + *
+ * For more info on this problem see discussion for + * Lucene Issue 665. + *
+ * In order to use this class, it should be made the implementation class for FSDirectorey. + * There are two ways to do this: + *
org.apache.lucene.FSDirectory.class
+ * to org.apache.lucene.store.FSWinDirectory.
+ * For instance, when starting a Java application in the cmomand line, the following command line option
+ * would do it:
+ * java -Dorg.apache.lucene.FSDirectory.class=org.apache.lucene.store.FSWinDirectory.
+ * + * This class does not verify that it is invoked in a Windows platform - this is the user + * responsibilty. It would work in other platforms as well, but the retry logic would not be applied, + * so there is not much point in using it in other platforms. + *
+ * There are three (static) configuration setups when this class is in use: + *
+ * There is an additional configuration setup for the lock: + * {@link SimpleFSWinLockFactory#enableLockReleaseRetry(boolean)}. + */ +public class FSWinDirectory extends FSDirectory { + + /** Default total retry delay */ + public static final long DEFAULT_MAX_TOTAL_DELAY = 240; + /** Default retry delay interval */ + public static final long DEFAULT_INTERVAL_DELAY = 30; + /** Default mode for delete retry (disabled) */ + public static final boolean DEFAULT_DELETE_RETRY = false; + + private static long maxTotalDelay = DEFAULT_MAX_TOTAL_DELAY; + private static long intervalDelay = DEFAULT_INTERVAL_DELAY; + private static boolean enableDeleteRetry = DEFAULT_DELETE_RETRY; + + /** Maximal number of trials by total delay and interval delay, assuming + * an operation fails in no time. Computed once. Used for preventing + * too long delays if system clock is moved backwards. */ + private static int maxTrials = (int) Math.ceil((double)maxTotalDelay / (double)intervalDelay); + + public FSWinDirectory() { + super(); + } + + /** + * Returns the intervalDelay. + * @return the intervalDelay. + * @see #setIntervalDelay(long). + */ + public static long getIntervalDelay() { + return intervalDelay; + } + + /** + * Sets the interval before next retry. + * @param intervalDelay The intervalDelay to set (millis). + */ + public static void setIntervalDelay(long intervalDelay) { + FSWinDirectory.intervalDelay = intervalDelay; + maxTrials = (int) Math.ceil((double)maxTotalDelay / (double)intervalDelay); + } + + /** + * Returns the maxTotalDelay. + * @return Returns the maxTotalDelay. + * @see #setMaxTotalDelay(long). + */ + public static long getMaxTotalDelay() { + return maxTotalDelay; + } + + /** + * Sets the maximum allowed total delay due to retry. + * @param maxTotalDelay The maxTotalDelay to set (millis). + */ + public static void setMaxTotalDelay(long maxTotalDelay) { + FSWinDirectory.maxTotalDelay = maxTotalDelay; + maxTrials = (int) Math.ceil((double)maxTotalDelay / (double)intervalDelay); + } + + /** + * Checks if retry is enabled for file delete. + * @return true iff retry is enabled for file delete. + * @see #enableDeleteRetry(boolean). + */ + public static boolean isEnableDeleteRetry() { + return enableDeleteRetry; + } + + /** + * Enables or disables retry for file delete. + * By default retry is disabled for file delete. + * This is because in some calls to this method in Lucene, if a file cannot + * be deleted, it is simply saved in a "delete later" list. + * For those cases, the retry logic might impose an unnecessary delay in program execution. + * But there are other usages where this "delete later" logic is not applied, + * where it could be worth to retry. + * This is disabled by default. + * @param enabled true to enable, false to disable. + */ + public static void enableDeleteRetry(boolean enabled) { + enableDeleteRetry = enabled; + } + + /* (non-Javadoc) + * @see org.apache.lucene.store.FSDirectory#createOutput(java.lang.String) + */ + public IndexOutput createOutput(String name) throws IOException { + int trialsSoFar = 0; + long maxTime = System.currentTimeMillis() + maxTotalDelay; + while (true) { + try { + return super.createOutput(name); + } catch (IOException e) { + if (!waitForRetry(maxTime, trialsSoFar++, e)) { + throw e; + } + } + } + } + + /** + * Wait an interval for retry. + * Returns true if should retry, false if should not retry. + * @param maxTime Maximal clock time by end of waiting + * @param trialsSoFar Number of trials so far, for deciding if to wait at all. + * @param e Exception that caused the retry. Now used for debug only, + * but can also be used for analyzing the exception, to decide if to retry. + * @param intervalDelay how long to wait + * @param maxTrials how many time allowed to retry + * @return true if should retry, false if should not retry. + */ + private static boolean waitForRetry( + long maxTime, int trialsSoFar, Exception e, long intervalDelay, int maxTrials) { + if (trialsSoFar >= maxTrials) { + return false; //waited too many times already + } + long timeLefe = maxTime - System.currentTimeMillis(); + long delay = Math.min(timeLefe, intervalDelay); + if (delay <= 0) { + return false; //waited too long already + } + + //debug + //System.out.println("Retry no. "+trialsSoFar+" because of: "+e); + //e.printStackTrace(System.out); + + try { + Thread.sleep(delay); + return true; + } catch (InterruptedException e1) { + return false; + } + } + /** @see #waitForRetry(long, int, Exception, long, int) */ + private static boolean waitForRetry(long maxTime, int trialsSoFar, Exception e) { + return waitForRetry(maxTime, trialsSoFar, e, intervalDelay, maxTrials); + } + + /* (non-Javadoc) + * @see org.apache.lucene.store.FSDirectory#deleteFile(java.lang.String) + */ + public void deleteFile(String name) throws IOException { + int trialsSoFar = 0; + long maxTime = System.currentTimeMillis() + maxTotalDelay; + while (true) { + try { + super.deleteFile(name); + return; + } catch (IOException e) { + if (!isEnableDeleteRetry() || !waitForRetry(maxTime, trialsSoFar++, e)) { + throw e; + } + } + } + } + + /* (non-Javadoc) + * @see org.apache.lucene.store.FSDirectory#createDefaultFSLockFactory(java.io.File) + */ + protected LockFactory createDefaultFSLockFactory(File lockDir) throws IOException { + return new SimpleFSWinLockFactory(lockDir); + } + + /* (non-Javadoc) + * @see org.apache.lucene.store.FSDirectory#fileExists(java.lang.String) + */ + public boolean fileExists(String name) { + int trialsSoFar = 0; + long maxTime = System.currentTimeMillis() + maxTotalDelay; + while (true) { + try { + return super.fileExists(name); + } catch (Exception e) { + if (!waitForRetry(maxTime, trialsSoFar++, e)) { + throw runtimeException(e); + } + } + } + } + + //make a runtime exception out of the input exception + private static RuntimeException runtimeException (Exception e) { + if (e instanceof RuntimeException) { + return (RuntimeException) e; + } + RuntimeException ex = new RuntimeException(e.getMessage()); + ex.initCause(e); + return ex; + } + + /* (non-Javadoc) + * @see org.apache.lucene.store.FSDirectory#fileLength(java.lang.String) + */ + + public long fileLength(String name) { + int trialsSoFar = 0; + long maxTime = System.currentTimeMillis() + maxTotalDelay; + while (true) { + try { + return super.fileLength(name); + } catch (Exception e) { + if (!waitForRetry(maxTime, trialsSoFar++, e)) { + throw runtimeException(e); + } + } + } + } + + /* (non-Javadoc) + * @see org.apache.lucene.store.FSDirectory#fileModified(java.io.File) + */ + protected long fileModified(File file) { + int trialsSoFar = 0; + long maxTime = System.currentTimeMillis() + maxTotalDelay; + while (true) { + try { + return super.fileModified(file); + } catch (Exception e) { + if (!waitForRetry(maxTime, trialsSoFar++, e)) { + throw runtimeException(e); + } + } + } + } + + /* (non-Javadoc) + * @see org.apache.lucene.store.FSDirectory#list() + */ + public String[] list() { + int trialsSoFar = 0; + long maxTime = System.currentTimeMillis() + maxTotalDelay; + while (true) { + try { + return super.list(); + } catch (Exception e) { + if (!waitForRetry(maxTime, trialsSoFar++, e)) { + throw runtimeException(e); + } + } + } + } + + /* (non-Javadoc) + * @see org.apache.lucene.store.FSDirectory#openInput(java.lang.String) + */ + public IndexInput openInput(String name) throws IOException { + int trialsSoFar = 0; + long maxTime = System.currentTimeMillis() + maxTotalDelay; + while (true) { + try { + return super.openInput(name); + } catch (IOException e) { + if (!waitForRetry(maxTime, trialsSoFar++, e)) { + throw e; + } + } + } + } + + /* (non-Javadoc) + * @see org.apache.lucene.store.FSDirectory#renameFile(java.lang.String, java.lang.String) + */ + public synchronized void renameFile(String from, String to) throws IOException { + int trialsSoFar = 0; + long maxTime = System.currentTimeMillis() + maxTotalDelay; + while (true) { + try { + super.renameFile(from, to); + return; + } catch (IOException e) { + if (!waitForRetry(maxTime, trialsSoFar++, e)) { + throw e; + } + } + } + } + + /* (non-Javadoc) + * @see org.apache.lucene.store.FSDirectory#touchFile(java.lang.String) + */ + public void touchFile(String name) { + int trialsSoFar = 0; + long maxTime = System.currentTimeMillis() + maxTotalDelay; + while (true) { + try { + super.touchFile(name); + return; + } catch (Exception e) { + if (!waitForRetry(maxTime, trialsSoFar++, e)) { + throw runtimeException(e); + } + } + } + } + + + /** + * Windows specific implementation of SimpleFSLockFactory. + *
+ * This class differs from {@link org.apache.lucene.store.SimpleFSLockFactory} in that + * for certain IO operations, if they fail, a retry logic is applied. + *
+ * See {@link org.apache.lucene.store.FSWinDirectory} for the motivation for retry.
+ */
+ public static class SimpleFSWinLockFactory extends SimpleFSLockFactory {
+
+ public static final boolean DEFAULT_LOCK_RELEASE_RETRY = false;
+
+ /** Default mode for lock delete retry (disabled) */
+ private static boolean enableLockReleaseRetry = DEFAULT_LOCK_RELEASE_RETRY;
+
+ /**
+ * Checks if retry is enabled for lock file delete.
+ * @return true iff retry is enabled for lock file delete.
+ * @see #enableLockReleaseRetry(boolean).
+ */
+ public static boolean isEnableLockReleaseRetry() {
+ return enableLockReleaseRetry;
+ }
+
+ /**
+ * Enables or disables retry for lock release.
+ * By default retry is disabled for lock file release.
+ * This is because a possible risk in NFS mounted file systems.
+ * In NFS systems, a file delete attempt might return failure code due to NFS timeout,
+ * although actually the delete succeeded. There is usually no harm in trying to
+ * delete again a file that was already deleted. But here this would be dangerous, because
+ * the file deletion stands for releasing a "co-operative lock file".
+ * Therefore the recommendation is to enable this feature only in a non-NFS env.
+ * This is disabled by default.
+ * @param enabled true to enable, false to disable.
+ */
+ public static void enableLockReleaseRetry(boolean enabled) {
+ enableLockReleaseRetry = enabled;
+ }
+
+ /**
+ * @param lockDir
+ * @throws IOException
+ */
+ public SimpleFSWinLockFactory(File lockDir) throws IOException {
+ super(lockDir);
+ }
+
+ /**
+ * @param lockDirName
+ * @throws IOException
+ */
+ public SimpleFSWinLockFactory(String lockDirName) throws IOException {
+ super(lockDirName);
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.lucene.store.SimpleFSLockFactory#clearAllLocks()
+ */
+ public void clearAllLocks() throws IOException {
+ int trialsSoFar = 0;
+ long maxTime = System.currentTimeMillis() + maxTotalDelay;
+ while (true) {
+ try {
+ super.clearAllLocks();
+ return;
+ } catch (IOException e) {
+ if (!isEnableLockReleaseRetry() || !waitForRetry(maxTime, trialsSoFar++, e)) {
+ throw e;
+ }
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.lucene.store.SimpleFSLockFactory#makeLock(java.lang.String)
+ */
+ public Lock makeLock(String lockName) {
+ return new SimpleWinFSLock(lockDir, lockPrefix + "-" + lockName);
+ }
+
+ }
+
+ /** Inner class implementing the Win FS Lock */
+ private static class SimpleWinFSLock extends SimpleFSLock {
+
+ public SimpleWinFSLock(File lockDir, String lockFileName) {
+ super(lockDir, lockFileName);
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.lucene.store.SimpleFSLock#isLocked()
+ */
+ public boolean isLocked() {
+ int trialsSoFar = 0;
+ long maxTime = System.currentTimeMillis() + maxTotalDelay;
+ while (true) {
+ try {
+ return super.isLocked();
+ } catch (Exception e) {
+ if (!waitForRetry(maxTime, trialsSoFar++, e)) {
+ throw runtimeException(e);
+ }
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.lucene.store.Lock#obtain(long)
+ */
+ public boolean obtain(long lockWaitTimeout) throws IOException {
+ int trialsSoFar = 0;
+ long maxTime = System.currentTimeMillis() + lockWaitTimeout;
+ int maxTrials = (int) Math.ceil((double)lockWaitTimeout / (double)LOCK_POLL_INTERVAL);
+ while (true) {
+ try {
+ return obtain();
+ } catch (IOException e) {
+ if (!waitForRetry(maxTime, trialsSoFar++, e, LOCK_POLL_INTERVAL, maxTrials)) {
+ throw e;
+ }
+ }
+ }
+ }
+
+
+ /* (non-Javadoc)
+ * @see org.apache.lucene.store.SimpleFSLock#release()
+ */
+ public void release() {
+ int trialsSoFar = 0;
+ long maxTime = System.currentTimeMillis() + maxTotalDelay;
+ while (true) {
+ try {
+ super.release();
+ return;
+ } catch (Exception e) {
+ if (!SimpleFSWinLockFactory.isEnableLockReleaseRetry() || !waitForRetry(maxTime, trialsSoFar++, e)) {
+ throw runtimeException(e);
+ }
+ }
+ }
+ }
+
+ }
+
+}
Index: src/java/org/apache/lucene/store/SimpleFSLockFactory.java
===================================================================
--- src/java/org/apache/lucene/store/SimpleFSLockFactory.java (revision 447480)
+++ src/java/org/apache/lucene/store/SimpleFSLockFactory.java (working copy)
@@ -41,7 +41,7 @@
System.getProperty("org.apache.lucene.lockDir",
System.getProperty("java.io.tmpdir"));
- private File lockDir;
+ protected File lockDir;
/**
* Instantiate using default LOCK_DIR: org.apache.lucene.lockDir
Index: src/test/org/apache/lucene/store/TestLockFactory.java
===================================================================
--- src/test/org/apache/lucene/store/TestLockFactory.java (revision 447480)
+++ src/test/org/apache/lucene/store/TestLockFactory.java (working copy)
@@ -130,7 +130,7 @@
IndexWriter writer = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), true);
assertTrue("FSDirectory did not use correct LockFactory: got " + writer.getDirectory().getLockFactory(),
- SimpleFSLockFactory.class.isInstance(writer.getDirectory().getLockFactory()));
+ isSimpleFsLockFactory(writer.getDirectory().getLockFactory()));
IndexWriter writer2 = null;
@@ -157,7 +157,7 @@
IndexWriter writer = new IndexWriter(indexDirName, new WhitespaceAnalyzer(), true);
assertTrue("FSDirectory did not use correct LockFactory: got " + writer.getDirectory().getLockFactory(),
- SimpleFSLockFactory.class.isInstance(writer.getDirectory().getLockFactory()));
+ isSimpleFsLockFactory(writer.getDirectory().getLockFactory()));
// Create a 2nd IndexWriter. This should not fail:
IndexWriter writer2 = null;
@@ -175,6 +175,10 @@
rmDir(indexDirName);
}
+ //verify that the correct simple fs lock factory is in use
+ protected boolean isSimpleFsLockFactory(LockFactory lockFactory) {
+ return SimpleFSLockFactory.class.isInstance(lockFactory);
+ }
// Verify: setting custom lock factory class (as system property) works:
// Verify: FSDirectory does basic locking correctly
Index: src/test/org/apache/lucene/store/TestWinLockfactory.java
===================================================================
--- src/test/org/apache/lucene/store/TestWinLockfactory.java (revision 0)
+++ src/test/org/apache/lucene/store/TestWinLockfactory.java (revision 0)
@@ -0,0 +1,40 @@
+package org.apache.lucene.store;
+
+public class TestWinLockfactory extends TestLockFactory {
+
+ String origFsDirImplName = null;
+
+ public TestWinLockfactory() {
+ super();
+ }
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ origFsDirImplName = FSDirectory.getFSDirImplClass();
+ FSDirectory.setFSDirImplClass("org.apache.lucene.store.FSWinDirectory");
+ }
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ try {
+ super.tearDown();
+ } finally {
+ FSDirectory.setFSDirImplClass(origFsDirImplName);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.lucene.store.TestLockFactory#isSimpleFsLockFactory(org.apache.lucene.store.LockFactory)
+ */
+ protected boolean isSimpleFsLockFactory(LockFactory lockFactory) {
+ return FSWinDirectory.SimpleFSWinLockFactory.class.isInstance(lockFactory);
+ }
+
+
+
+}