@@ -498,14 +498,31 @@ private void AssertChunking(Random random, int chunkSize)
498498 // extends a file on disk while the foreground thread repeatedly
499499 // opens it with MMapDirectory.OpenInput. The original failure
500500 // mode was ArgumentOutOfRangeException(paramName="capacity")
501- // from MemoryMappedFile.CreateFromFile, because we were passing
502- // a caller-computed capacity (fc.Length) that could be smaller
503- // than the actual file size by the time the framework did its
504- // internal stat. The current design passes capacity: 0, which
505- // tells the framework to size the mapping from the file's
506- // current on-disk length atomically — there is no caller-side
507- // capacity to disagree with the file. This test asserts that
508- // OpenInput continues to succeed under concurrent file extension.
501+ // from MemoryMappedFile.CreateFromFile, because the on-disk file
502+ // size could exceed our caller-computed capacity by the time the
503+ // framework did its internal stat. .NET Framework's
504+ // CreateFromFile reads fileStream.Length multiple times
505+ // non-atomically (referencesource MemoryMappedFile.cs L192-L243);
506+ // modern .NET snapshots it into a single local
507+ // (dotnet/runtime MemoryMappedFile.cs L237-L268). Even when we
508+ // pass capacity: 0 the .NET Framework path still races because
509+ // the length is re-read for both the defaulting step and the
510+ // capacity-vs-size guard. SharedMapping.Create handles the
511+ // residual race with a retry loop. This test asserts that
512+ // OpenInput continues to succeed under concurrent file
513+ // extension.
514+ //
515+ // Test design notes:
516+ // - The writer extends only (never truncates). Truncating a
517+ // user-mapped file on Windows fails with ERROR_USER_MAPPED_FILE
518+ // and is unrelated to what we're verifying here.
519+ // - The reader runs a bounded number of iterations rather than a
520+ // wall-clock loop. Sustained mmap churn (thousands of
521+ // map/unmap pairs per second) can transiently exhaust Windows
522+ // kernel resources (ERROR_NO_SYSTEM_RESOURCES,
523+ // ERROR_ACCESS_DENIED on view creation), which is also
524+ // unrelated to the capacity race. A few hundred iterations
525+ // are plenty to repeatedly hit the race window.
509526 [ Test , LuceneNetSpecific , Slow ]
510527 public void TestOpenInputConcurrentFileExtension_Issue1090 ( )
511528 {
@@ -518,7 +535,7 @@ public void TestOpenInputConcurrentFileExtension_Issue1090()
518535
519536 using var mmapDir = new MMapDirectory ( dir ) ;
520537
521- const long maxFileSize = 1L * 1024 * 1024 ; // 1 MiB cap
538+ const long maxFileSize = 64L * 1024 * 1024 ; // 64 MiB safety cap
522539 var stop = new ManualResetEventSlim ( false ) ;
523540 Exception writerError = null ;
524541
@@ -531,15 +548,14 @@ public void TestOpenInputConcurrentFileExtension_Issue1090()
531548 {
532549 using var fs = new FileStream ( filePath , FileMode . Open , FileAccess . Write , FileShare . ReadWrite ) ;
533550 fs . Seek ( 0 , SeekOrigin . End ) ;
534- if ( fs . Length < maxFileSize )
535- {
536- fs . Write ( chunk , 0 , chunk . Length ) ;
537- }
538- else
551+ if ( fs . Length >= maxFileSize )
539552 {
540- // Keep the file bounded: truncate back and grow again.
541- fs . SetLength ( 64 ) ;
553+ // Stop extending if we somehow reach the cap. The
554+ // reader's bounded iteration count guarantees this
555+ // is far above what we'll hit in a normal run.
556+ break ;
542557 }
558+ fs . Write ( chunk , 0 , chunk . Length ) ;
543559 }
544560 }
545561 catch ( Exception e )
@@ -552,23 +568,17 @@ public void TestOpenInputConcurrentFileExtension_Issue1090()
552568
553569 try
554570 {
555- var sw = Stopwatch . StartNew ( ) ;
556- int iterations = 0 ;
557- // Stress OpenInput while the background thread extends/truncates the
558- // file. Each call must succeed cleanly. A short window is plenty:
559- // before the capacity:0 fix this race fired in well under a second.
560- const int maxSeconds = 5 ;
561- while ( sw . Elapsed < TimeSpan . FromSeconds ( maxSeconds ) )
571+ // Bounded iteration count keeps mmap churn well below the
572+ // Windows kernel-resource threshold while still exercising
573+ // the capacity race many times over.
574+ const int iterations = 500 ;
575+ for ( int i = 0 ; i < iterations ; i ++ )
562576 {
563577 using ( var _ = mmapDir . OpenInput ( name , NewIOContext ( Random ) ) )
564578 {
565- // Just open and dispose; the race occurred during construction.
579+ // Just open and dispose; the race occurs during construction.
566580 }
567- iterations ++ ;
568581 }
569-
570- TestContext . Progress . WriteLine (
571- $ "TestOpenInputConcurrentFileExtension: completed { iterations } OpenInput calls in { sw . Elapsed . TotalSeconds : F1} s") ;
572582 }
573583 finally
574584 {
0 commit comments