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,12 @@ import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; @@ -256,6 +262,10 @@ private ByteBuffer curBuf; // redundant for speed: buffers[curBufIndex] private boolean isClone = false; + private ConcurrentHashMap,Boolean> clones = + new ConcurrentHashMap,Boolean>(); + private ReferenceQueue clonesRefQueue = + new ReferenceQueue(); MMapIndexInput(String resourceDescription, RandomAccessFile raf, long offset, long length, int chunkSizePower) throws IOException { super(resourceDescription); @@ -396,9 +406,9 @@ } final MMapIndexInput clone = (MMapIndexInput)super.clone(); clone.isClone = true; + clone.clones = new ConcurrentHashMap,Boolean>(); + clone.clonesRefQueue = new ReferenceQueue(); 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 +417,37 @@ } catch(IOException ioe) { throw new RuntimeException("Should never happen: " + this, ioe); } + + // cleanup the clones list by removing refs to clones that were GC'ed + Reference ref; + while ((ref = this.clonesRefQueue.poll()) != null) { + final Boolean v = this.clones.remove(ref); + assert v == Boolean.TRUE; + //System.out.println("MMapIndexInput removed clone reference: "+ref); + } + // happens-before: The clone added to map is completely built, as ConcurrentHashMap => memory barrier + this.clones.put(new WeakReference(clone, this.clonesRefQueue), 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 + for (final Iterator> it = clones.keySet().iterator(); it.hasNext();) { + final MMapIndexInput clone = it.next().get(); + it.remove(); + if (clone != null) { + assert clone.isClone; + clone.close(); // should never throw Exception, as clones will not try to unmap + } + } + + 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 +458,8 @@ } } finally { buffers = null; + clonesRefQueue = null; + clones = null; } } }