@@ -103,6 +103,13 @@ public MMapDirectory(DirectoryInfo path)
103103 /// <param name="maxChunkSize"> maximum chunk size (default is 1 GiBytes for
104104 /// 64 bit runtimes and 256 MiBytes for 32 bit runtimes) used for memory mapping.
105105 /// <para/>
106+ /// <b>LUCENENET note:</b> this parameter is retained for API compatibility
107+ /// but is effectively a no-op in the current implementation. Each file is
108+ /// mapped as a single <see cref="System.IO.MemoryMappedFiles.MemoryMappedViewAccessor"/>
109+ /// regardless of <paramref name="maxChunkSize"/>; see the class remarks for
110+ /// the rationale (issues #1013 and #1151). The value is still validated and
111+ /// retained so that <see cref="MaxChunkSize"/> reflects what the caller passed.
112+ /// <para/>
106113 /// Especially on 32 bit platform, the address space can be very fragmented,
107114 /// so large index files cannot be mapped. Using a lower chunk size makes
108115 /// the directory implementation a little bit slower (as the correct chunk
@@ -162,6 +169,13 @@ public MMapDirectory(string path)
162169 /// <param name="maxChunkSize"> maximum chunk size (default is 1 GiBytes for
163170 /// 64 bit runtimes and 256 MiBytes for 32 bit runtimes) used for memory mapping.
164171 /// <para/>
172+ /// <b>LUCENENET note:</b> this parameter is retained for API compatibility
173+ /// but is effectively a no-op in the current implementation. Each file is
174+ /// mapped as a single <see cref="System.IO.MemoryMappedFiles.MemoryMappedViewAccessor"/>
175+ /// regardless of <paramref name="maxChunkSize"/>; see the class remarks for
176+ /// the rationale (issues #1013 and #1151). The value is still validated and
177+ /// retained so that <see cref="MaxChunkSize"/> reflects what the caller passed.
178+ /// <para/>
165179 /// Especially on 32 bit platform, the address space can be very fragmented,
166180 /// so large index files cannot be mapped. Using a lower chunk size makes
167181 /// the directory implementation a little bit slower (as the correct chunk
@@ -182,7 +196,15 @@ public MMapDirectory(string path, LockFactory lockFactory, int maxChunkSize)
182196 // indeed "release all resources". Therefore unmap hack is not needed in .NET.
183197
184198 /// <summary>
185- /// Returns the current mmap chunk size. </summary>
199+ /// Returns the current mmap chunk size.
200+ /// <para/>
201+ /// <b>LUCENENET note:</b> this value is retained for API compatibility
202+ /// but does not affect runtime behavior in the current implementation.
203+ /// Each file is mapped as a single
204+ /// <see cref="System.IO.MemoryMappedFiles.MemoryMappedViewAccessor"/>
205+ /// regardless of this setting; see the class remarks (issues #1013 and
206+ /// #1151) for the rationale.
207+ /// </summary>
186208 /// <seealso cref="MMapDirectory(DirectoryInfo, LockFactory, int)"/>
187209 public int MaxChunkSize => 1 << chunkSizePower ;
188210
@@ -204,7 +226,10 @@ public override IndexInputSlicer CreateSlicer(string name, IOContext context)
204226 EnsureOpen ( ) ;
205227 var file = Path . Combine ( Directory . FullName , name ) ;
206228 var descriptor = new FileStream ( file , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) ;
207- return new IndexInputSlicerAnonymousClass ( this , context , file , descriptor ) ;
229+ // Cache the length eagerly so OpenFullSlice never has to touch
230+ // the FileStream — which would race with Dispose closing it.
231+ long descriptorLength = descriptor . Length ;
232+ return new IndexInputSlicerAnonymousClass ( this , context , file , descriptor , descriptorLength ) ;
208233 }
209234
210235 private sealed class IndexInputSlicerAnonymousClass : IndexInputSlicer
@@ -213,6 +238,7 @@ private sealed class IndexInputSlicerAnonymousClass : IndexInputSlicer
213238 private readonly IOContext context ;
214239 private readonly string file ;
215240 private readonly FileStream descriptor ;
241+ private readonly long descriptorLength ;
216242 private int disposed = 0 ; // LUCENENET specific - allow double-dispose
217243 // Track issued slices so that Dispose cascades. Lucene's
218244 // contract is that after slicer.Dispose, reads from any slice
@@ -221,12 +247,13 @@ private readonly SCG.List<MemoryMappedViewAccessorIndexInput> issuedSlices
221247 = new SCG . List < MemoryMappedViewAccessorIndexInput > ( ) ;
222248 private readonly object issuedSlicesLock = new object ( ) ;
223249
224- public IndexInputSlicerAnonymousClass ( MMapDirectory outerInstance , IOContext context , string file , FileStream descriptor )
250+ public IndexInputSlicerAnonymousClass ( MMapDirectory outerInstance , IOContext context , string file , FileStream descriptor , long descriptorLength )
225251 {
226252 this . outerInstance = outerInstance ;
227253 this . context = context ;
228254 this . file = file ;
229255 this . descriptor = descriptor ;
256+ this . descriptorLength = descriptorLength ;
230257 }
231258
232259 public override IndexInput OpenSlice ( string sliceDescription , long offset , long length )
@@ -257,16 +284,11 @@ public override IndexInput OpenSlice(string sliceDescription, long offset, long
257284 public override IndexInput OpenFullSlice ( )
258285 {
259286 outerInstance . EnsureOpen ( ) ;
260- // Check disposed before touching descriptor. Without this,
261- // a concurrent Dispose could close the FileStream between
262- // EnsureOpen and the descriptor.Length read, and the user
263- // would see an ObjectDisposedException from FileStream
264- // instead of the conventional AlreadyClosedException.
265- if ( Volatile . Read ( ref disposed ) != 0 )
266- {
267- throw AlreadyClosedException . Create ( nameof ( IndexInputSlicer ) , "this IndexInputSlicer is closed" ) ;
268- }
269- return OpenSlice ( "full-slice" , 0 , descriptor . Length ) ;
287+ // File length is captured eagerly in the slicer ctor, so we
288+ // never touch the descriptor FileStream here. OpenSlice does
289+ // its own disposed-check under the lock, which will throw
290+ // AlreadyClosed deterministically if we raced with Dispose.
291+ return OpenSlice ( "full-slice" , 0 , descriptorLength ) ;
270292 }
271293
272294 protected override void Dispose ( bool disposing )
0 commit comments