Index: lucene/src/java/org/apache/lucene/store/MMapDirectory.java =================================================================== --- lucene/src/java/org/apache/lucene/store/MMapDirectory.java (revision 1205157) +++ lucene/src/java/org/apache/lucene/store/MMapDirectory.java (working copy) @@ -26,6 +26,9 @@ import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; +import java.util.Map; +import java.util.WeakHashMap; + import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; @@ -256,6 +259,7 @@ private ByteBuffer curBuf; // redundant for speed: buffers[curBufIndex] private boolean isClone = false; + private Map clones = new WeakHashMap(); MMapIndexInput(String resourceDescription, RandomAccessFile raf, long offset, long length, int chunkSizePower) throws IOException { super(resourceDescription); @@ -396,9 +400,8 @@ } final MMapIndexInput clone = (MMapIndexInput)super.clone(); clone.isClone = true; + clone.clones = new WeakHashMap(); clone.buffers = new ByteBuffer[buffers.length]; - // Since most clones will use only one buffer, duplicate() could also be - // done lazy in clones, e.g. when adapting curBuf. for (int bufNr = 0; bufNr < buffers.length; bufNr++) { clone.buffers[bufNr] = buffers[bufNr].duplicate(); } @@ -407,14 +410,31 @@ } catch(IOException ioe) { throw new RuntimeException("Should never happen: " + this, ioe); } + + // register the new clone in our clone list to clean it up on closing: + synchronized(this.clones) { + this.clones.put(clone, Boolean.TRUE); + } + return clone; } @Override public void close() throws IOException { - curBuf = null; curBufIndex = 0; try { - if (isClone || buffers == null) return; + curBuf = null; curBufIndex = 0; + if (buffers == null) return; + + // for extra safety close also all clones, so their buffers are unset + synchronized(this.clones) { + for (final MMapIndexInput clone : this.clones.keySet()) { + assert clone.isClone; + clone.close(); // should never throw Exception, as clones will not try to unmap + } + this.clones.clear(); + } + + if (isClone) return; for (int bufNr = 0; bufNr < buffers.length; bufNr++) { // unmap the buffer (if enabled) and at least unset it for GC try { @@ -425,6 +445,7 @@ } } finally { buffers = null; + clones = null; } } } Index: lucene/src/test/org/apache/lucene/store/TestMultiMMap.java =================================================================== --- lucene/src/test/org/apache/lucene/store/TestMultiMMap.java (revision 1205157) +++ lucene/src/test/org/apache/lucene/store/TestMultiMMap.java (working copy) @@ -18,6 +18,7 @@ */ import java.io.File; +import java.io.IOException; import java.util.Random; import org.apache.lucene.analysis.MockAnalyzer; @@ -47,6 +48,32 @@ workDir = _TestUtil.getTempDir("TestMultiMMap"); workDir.mkdirs(); } + + public void testCloneSafety() throws Exception { + MMapDirectory mmapDir = new MMapDirectory(_TestUtil.getTempDir("testCloneSafety")); + IndexOutput io = mmapDir.createOutput("bytes", newIOContext(random)); + io.writeVInt(5); + io.close(); + IndexInput one = mmapDir.openInput("bytes", IOContext.DEFAULT); + IndexInput two = (IndexInput) one.clone(); + one.close(); + try { + one.readVInt(); + fail("Must throw IOException or NullPointerException"); + } catch (IOException ignored1) { + // pass + } catch (NullPointerException ignored2) { + // pass + } + try { + two.readVInt(); + fail("Must throw IOException or NullPointerException"); + } catch (IOException ignored1) { + // pass + } catch (NullPointerException ignored2) { + // pass + } + } public void testSeekZero() throws Exception { for (int i = 0; i < 31; i++) {