Index: CHANGES.txt =================================================================== --- CHANGES.txt (revision 1154302) +++ CHANGES.txt (working copy) @@ -7,6 +7,9 @@ Bug fixes +* LUCENE-3365: Create or Append mode determined before obtaining write lock + (Geoff Cooney) + * LUCENE-3251: Directory#copy failed to close target output if opening the source stream failed. (Simon Willnauer) Index: src/test/org/apache/lucene/index/TestIndexWriterWithThreads.java =================================================================== --- src/test/org/apache/lucene/index/TestIndexWriterWithThreads.java (revision 1154302) +++ src/test/org/apache/lucene/index/TestIndexWriterWithThreads.java (working copy) @@ -24,6 +24,7 @@ import org.apache.lucene.document.Field; import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.store.Directory; +import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.store.MockDirectoryWrapper; import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.ThreadInterruptedException; @@ -396,4 +397,105 @@ public void testIOExceptionDuringWriteSegmentWithThreadsOnlyOnce() throws Exception { _testMultipleThreadsFailure(new FailOnlyInWriteSegment(true)); } + + // Test adding two documents with the same field from two different IndexWriters + // that we attempt to open at the same time. As long as the first IndexWriter completes + // and closes before the second IndexWriter time's out trying to get the Lock, + // we should see both documents + public void testOpenTwoIndexWritersOnDifferentThreads() throws IOException { + final MockDirectoryWrapper dir = newDirectory(); + try { + DelayedIndexAndCloseRunnable runnable1 = new DelayedIndexAndCloseRunnable(dir); + DelayedIndexAndCloseRunnable runnable2 = new DelayedIndexAndCloseRunnable(dir); + + Thread writerThread1 = new Thread(runnable1); + Thread writerThread2 = new Thread(runnable2); + + writerThread1.start(); + writerThread2.start(); + + //wait a short while to make sure both indexWriters have time to enter their constructors + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + + runnable1.startIndexing(); + runnable2.startIndexing(); + + int WAIT_TOLERANCE = 1000; + waitForThreads(WAIT_TOLERANCE, writerThread1, writerThread2); + + //now verify that we have two documents in the index + IndexReader reader = IndexReader.open(dir, true); + try { + assertEquals("IndexReader should have one document per thread running", 2, reader.numDocs()); + } finally { + reader.close(); + } + } finally { + dir.close(); + } + } + + private void waitForThreads(long timeToWait, Thread... threadsToWaitFor) { + long currentTime = System.currentTimeMillis(); + for (Thread threadToWaitFor: threadsToWaitFor) { + while (threadToWaitFor.isAlive()) { + long elapsedTime = System.currentTimeMillis() - currentTime; + long timeToWaitLeft = timeToWait - elapsedTime; + if (timeToWaitLeft <= 0) { + fail("Thread is taking too long to complete. It is probably hung."); + } + + try { + threadToWaitFor.join(timeToWait); + } catch (InterruptedException e) { + } + } + } + } + + private class DelayedIndexAndCloseRunnable implements Runnable { + private final Directory dir; + private volatile boolean startIndexing = false; + + public DelayedIndexAndCloseRunnable(Directory dir) { + this.dir = dir; + } + + public void startIndexing() { + this.startIndexing = true; + } + + @Override + public void run() { + Document doc = new Document(); + Field field = newField("field", "testData", Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); + doc.add(field); + IndexWriter writer = null; + try { + writer = new IndexWriter(dir, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random))); + } catch (LockObtainFailedException e) { + fail("Unable to obtain write lock"); + } catch (IOException e) { + fail("Failed trying to open IndexWriter on thread"); + } + + while (!startIndexing) { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + } + + try { + writer.addDocument(doc); + writer.commit(); + writer.close(); + } catch (IOException e) { + fail("Unable to index and close on thread"); + } + } + } } Index: src/java/org/apache/lucene/index/IndexWriter.java =================================================================== --- src/java/org/apache/lucene/index/IndexWriter.java (revision 1154302) +++ src/java/org/apache/lucene/index/IndexWriter.java (working copy) @@ -1100,17 +1100,6 @@ bufferedDeletesStream.setInfoStream(infoStream); poolReaders = conf.getReaderPooling(); - OpenMode mode = conf.getOpenMode(); - boolean create; - if (mode == OpenMode.CREATE) { - create = true; - } else if (mode == OpenMode.APPEND) { - create = false; - } else { - // CREATE_OR_APPEND - create only if an index does not exist - create = !IndexReader.indexExists(directory); - } - writeLock = directory.makeLock(WRITE_LOCK_NAME); if (!writeLock.obtain(writeLockTimeout)) // obtain write lock @@ -1125,6 +1114,17 @@ // oldest segment's version in segments_N. try { + OpenMode mode = conf.getOpenMode(); + boolean create; + if (mode == OpenMode.CREATE) { + create = true; + } else if (mode == OpenMode.APPEND) { + create = false; + } else { + // CREATE_OR_APPEND - create only if an index does not exist + create = !IndexReader.indexExists(directory); + } + if (create) { // Try to read first. This is to allow create // against an index that's currently open for