LUCENE-3658: fix bad asserts and concurrency issue in NRTCachingDir

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1221368 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Michael McCandless 2011-12-20 17:52:00 +00:00
parent 4dc3278d0d
commit b13b9ab632
6 changed files with 83 additions and 49 deletions

View File

@ -766,6 +766,10 @@ Bug fixes
double precision and to compute age to be how long ago the searcher double precision and to compute age to be how long ago the searcher
was replaced with a new searcher (Mike McCandless) was replaced with a new searcher (Mike McCandless)
* LUCENE-3658: Corrected potential concurrency issues with
NRTCachingDir, fixed createOutput to overwrite any previous file,
and removed invalid asserts (Robert Muir, Mike McCandless)
Optimizations Optimizations
* LUCENE-3653: Improve concurrency in VirtualMethod and AttributeSource by * LUCENE-3653: Improve concurrency in VirtualMethod and AttributeSource by

View File

@ -167,7 +167,7 @@ public class NRTCachingDirectory extends Directory {
System.out.println("nrtdir.deleteFile name=" + name); System.out.println("nrtdir.deleteFile name=" + name);
} }
if (cache.fileExists(name)) { if (cache.fileExists(name)) {
assert !delegate.fileExists(name); assert !delegate.fileExists(name): "name=" + name;
cache.deleteFile(name); cache.deleteFile(name);
} else { } else {
delegate.deleteFile(name); delegate.deleteFile(name);
@ -196,8 +196,18 @@ public class NRTCachingDirectory extends Directory {
if (VERBOSE) { if (VERBOSE) {
System.out.println(" to cache"); System.out.println(" to cache");
} }
try {
delegate.deleteFile(name);
} catch (IOException ioe) {
// This is fine: file may not exist
}
return cache.createOutput(name, context); return cache.createOutput(name, context);
} else { } else {
try {
cache.deleteFile(name);
} catch (IOException ioe) {
// This is fine: file may not exist
}
return delegate.createOutput(name, context); return delegate.createOutput(name, context);
} }
} }
@ -247,6 +257,11 @@ public class NRTCachingDirectory extends Directory {
* to the delegate and then closes the delegate. */ * to the delegate and then closes the delegate. */
@Override @Override
public void close() throws IOException { public void close() throws IOException {
// NOTE: technically we shouldn't have to do this, ie,
// IndexWriter should have sync'd all files, but we do
// it for defensive reasons... or in case the app is
// doing something custom (creating outputs directly w/o
// using IndexWriter):
for(String fileName : cache.listAll()) { for(String fileName : cache.listAll()) {
unCache(fileName); unCache(fileName);
} }
@ -262,29 +277,40 @@ public class NRTCachingDirectory extends Directory {
return !name.equals(IndexFileNames.SEGMENTS_GEN) && (merge == null || merge.estimatedMergeBytes <= maxMergeSizeBytes) && cache.sizeInBytes() <= maxCachedBytes; return !name.equals(IndexFileNames.SEGMENTS_GEN) && (merge == null || merge.estimatedMergeBytes <= maxMergeSizeBytes) && cache.sizeInBytes() <= maxCachedBytes;
} }
private void unCache(String fileName) throws IOException { private final Object uncacheLock = new Object();
final IndexOutput out;
IOContext context = IOContext.DEFAULT;
synchronized(this) {
if (!delegate.fileExists(fileName)) {
assert cache.fileExists(fileName);
out = delegate.createOutput(fileName, context);
} else {
out = null;
}
}
if (out != null) { private void unCache(String fileName) throws IOException {
// Only let one thread uncache at a time; this only
// happens during commit() or close():
IndexOutput out = null;
IndexInput in = null; IndexInput in = null;
try { try {
synchronized(uncacheLock) {
if (VERBOSE) {
System.out.println("nrtdir.unCache name=" + fileName);
}
if (!cache.fileExists(fileName)) {
// Another thread beat us...
return;
}
IOContext context = IOContext.DEFAULT;
if (delegate.fileExists(fileName)) {
throw new IOException("cannot uncache file=\"" + fileName + "\": it was separately also created in the delegate directory");
}
out = delegate.createOutput(fileName, context);
in = cache.openInput(fileName, context); in = cache.openInput(fileName, context);
in.copyBytes(out, in.length()); in.copyBytes(out, in.length());
} finally {
IOUtils.close(in, out); // Lock order: uncacheLock -> this
}
synchronized(this) { synchronized(this) {
// Must sync here because other sync methods have
// if (cache.fileExists(name)) { ... } else { ... }:
cache.deleteFile(fileName); cache.deleteFile(fileName);
} }
} }
} finally {
IOUtils.close(in, out);
}
} }
} }

View File

@ -220,9 +220,31 @@ public class MockDirectoryWrapper extends Directory {
} else if (damage == 2) { } else if (damage == 2) {
action = "partially truncated"; action = "partially truncated";
// Partially Truncate the file: // Partially Truncate the file:
IndexOutput out = delegate.createOutput(name, LuceneTestCase.newIOContext(randomState));
out.setLength(fileLength(name)/2); // First, make temp file and copy only half this
// file over:
String tempFileName;
while (true) {
tempFileName = ""+randomState.nextInt();
if (!delegate.fileExists(tempFileName)) {
break;
}
}
final IndexOutput tempOut = delegate.createOutput(tempFileName, LuceneTestCase.newIOContext(randomState));
IndexInput in = delegate.openInput(name, LuceneTestCase.newIOContext(randomState));
tempOut.copyBytes(in, in.length()/2);
tempOut.close();
in.close();
// Delete original and copy bytes back:
deleteFile(name, true);
final IndexOutput out = delegate.createOutput(name, LuceneTestCase.newIOContext(randomState));
in = delegate.openInput(tempFileName, LuceneTestCase.newIOContext(randomState));
out.copyBytes(in, in.length());
out.close(); out.close();
in.close();
deleteFile(tempFileName, true);
} else if (damage == 3) { } else if (damage == 3) {
// The file survived intact: // The file survived intact:
action = "didn't change"; action = "didn't change";

View File

@ -1007,18 +1007,8 @@ public abstract class LuceneTestCase extends Assert {
* See {@link #newDirectory()} for more information. * See {@link #newDirectory()} for more information.
*/ */
public static MockDirectoryWrapper newDirectory(Random r) throws IOException { public static MockDirectoryWrapper newDirectory(Random r) throws IOException {
return newDirectory(r, true);
}
/**
* Returns a new Directory instance, using the specified random. You
* can specify maybeWrap as to whether the directory might be also
* wrapped by NRTCachingDirectory or FileSwitchDirectory
* See {@link #newDirectory()} for more information.
*/
public static MockDirectoryWrapper newDirectory(Random r, boolean maybeWrap) throws IOException {
Directory impl = newDirectoryImpl(r, TEST_DIRECTORY); Directory impl = newDirectoryImpl(r, TEST_DIRECTORY);
MockDirectoryWrapper dir = new MockDirectoryWrapper(r, maybeWrap ? maybeNRTWrap(r, impl) : impl); MockDirectoryWrapper dir = new MockDirectoryWrapper(r, maybeNRTWrap(r, impl));
stores.put(dir, Thread.currentThread().getStackTrace()); stores.put(dir, Thread.currentThread().getStackTrace());
dir.setThrottling(TEST_THROTTLING); dir.setThrottling(TEST_THROTTLING);
return dir; return dir;
@ -1030,7 +1020,7 @@ public abstract class LuceneTestCase extends Assert {
* information. * information.
*/ */
public static MockDirectoryWrapper newDirectory(Directory d) throws IOException { public static MockDirectoryWrapper newDirectory(Directory d) throws IOException {
return newDirectory(random, d, true); return newDirectory(random, d);
} }
/** Returns a new FSDirectory instance over the given file, which must be a folder. */ /** Returns a new FSDirectory instance over the given file, which must be a folder. */
@ -1040,11 +1030,6 @@ public abstract class LuceneTestCase extends Assert {
/** Returns a new FSDirectory instance over the given file, which must be a folder. */ /** Returns a new FSDirectory instance over the given file, which must be a folder. */
public static MockDirectoryWrapper newFSDirectory(File f, LockFactory lf) throws IOException { public static MockDirectoryWrapper newFSDirectory(File f, LockFactory lf) throws IOException {
return newFSDirectory(f, lf, true);
}
/** Returns a new FSDirectory instance over the given file, which must be a folder. */
public static MockDirectoryWrapper newFSDirectory(File f, LockFactory lf, boolean maybeWrap) throws IOException {
String fsdirClass = TEST_DIRECTORY; String fsdirClass = TEST_DIRECTORY;
if (fsdirClass.equals("random")) { if (fsdirClass.equals("random")) {
fsdirClass = FS_DIRECTORIES[random.nextInt(FS_DIRECTORIES.length)]; fsdirClass = FS_DIRECTORIES[random.nextInt(FS_DIRECTORIES.length)];
@ -1061,7 +1046,7 @@ public abstract class LuceneTestCase extends Assert {
} }
Directory fsdir = newFSDirectoryImpl(clazz, f); Directory fsdir = newFSDirectoryImpl(clazz, f);
MockDirectoryWrapper dir = new MockDirectoryWrapper(random, maybeWrap ? maybeNRTWrap(random, fsdir) : fsdir); MockDirectoryWrapper dir = new MockDirectoryWrapper(random, maybeNRTWrap(random, fsdir));
if (lf != null) { if (lf != null) {
dir.setLockFactory(lf); dir.setLockFactory(lf);
} }
@ -1078,12 +1063,12 @@ public abstract class LuceneTestCase extends Assert {
* with contents copied from the provided directory. See * with contents copied from the provided directory. See
* {@link #newDirectory()} for more information. * {@link #newDirectory()} for more information.
*/ */
public static MockDirectoryWrapper newDirectory(Random r, Directory d, boolean maybeWrap) throws IOException { public static MockDirectoryWrapper newDirectory(Random r, Directory d) throws IOException {
Directory impl = newDirectoryImpl(r, TEST_DIRECTORY); Directory impl = newDirectoryImpl(r, TEST_DIRECTORY);
for (String file : d.listAll()) { for (String file : d.listAll()) {
d.copy(impl, file, file, newIOContext(r)); d.copy(impl, file, file, newIOContext(r));
} }
MockDirectoryWrapper dir = new MockDirectoryWrapper(r, maybeWrap ? maybeNRTWrap(r, impl) : impl); MockDirectoryWrapper dir = new MockDirectoryWrapper(r, maybeNRTWrap(r, impl));
stores.put(dir, Thread.currentThread().getStackTrace()); stores.put(dir, Thread.currentThread().getStackTrace());
dir.setThrottling(TEST_THROTTLING); dir.setThrottling(TEST_THROTTLING);
return dir; return dir;

View File

@ -30,9 +30,7 @@ import org.apache.lucene.document.TextField;
public class TestCrash extends LuceneTestCase { public class TestCrash extends LuceneTestCase {
private IndexWriter initIndex(Random random, boolean initialCommit) throws IOException { private IndexWriter initIndex(Random random, boolean initialCommit) throws IOException {
// note: we pass 'false' here so our crashing/deleting won't trigger assertions in NRTCachingDir return initIndex(random, newDirectory(random), initialCommit);
// TODO: don't remember why this is ok... maybe we should check again that it really actually is.
return initIndex(random, newDirectory(random, false), initialCommit);
} }
private IndexWriter initIndex(Random random, MockDirectoryWrapper dir, boolean initialCommit) throws IOException { private IndexWriter initIndex(Random random, MockDirectoryWrapper dir, boolean initialCommit) throws IOException {

View File

@ -114,8 +114,7 @@ public class TestDoc extends LuceneTestCase {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw, true); PrintWriter out = new PrintWriter(sw, true);
// TODO: why does this test trigger NRTCachingDirectory's assert? Directory directory = newFSDirectory(indexDir, null);
Directory directory = newFSDirectory(indexDir, null, false);
IndexWriter writer = new IndexWriter( IndexWriter writer = new IndexWriter(
directory, directory,
newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)). newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).
@ -143,14 +142,14 @@ public class TestDoc extends LuceneTestCase {
directory.close(); directory.close();
out.close(); out.close();
sw.close(); sw.close();
String multiFileOutput = sw.getBuffer().toString(); String multiFileOutput = sw.getBuffer().toString();
//System.out.println(multiFileOutput); //System.out.println(multiFileOutput);
sw = new StringWriter(); sw = new StringWriter();
out = new PrintWriter(sw, true); out = new PrintWriter(sw, true);
// TODO: why does this test trigger NRTCachingDirectory's assert? directory = newFSDirectory(indexDir, null);
directory = newFSDirectory(indexDir, null, false);
writer = new IndexWriter( writer = new IndexWriter(
directory, directory,
newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)). newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).