88using System . IO ;
99using System . IO . MemoryMappedFiles ;
1010using System . Linq ;
11+ using System . Runtime . CompilerServices ;
1112using System . Runtime . InteropServices ;
1213using System . Threading ;
1314using SCG = System . Collections . Generic ;
@@ -534,7 +535,7 @@ private static SharedMapping CreateAttempt(string file, int chunkSizePower)
534535 // since no MMF will own it and a throwing Dispose must not
535536 // escape this success path.
536537 IOUtils . DisposeWhileHandlingException ( fs ) ;
537- return new SharedMapping ( mmf : null , fileStream : null , chunks : Array . Empty < Chunk > ( ) , length : 0 ) ;
538+ return new SharedMapping ( mmf : null , fileStream : null , chunks : [ ] , length : 0 ) ;
538539 }
539540
540541 // capacity: 0 -> the framework sizes the mapping from the file's
@@ -556,17 +557,10 @@ private static SharedMapping CreateAttempt(string file, int chunkSizePower)
556557 chunks = MapChunks ( mmf , 0 , length , chunkSizePower ) ;
557558 return new SharedMapping ( mmf , fs , chunks , length ) ;
558559 }
559- catch ( Exception /* e */ ) // when (e.IsThrowable())
560+ catch ( Exception e ) // when (e.IsThrowable())
560561 {
561- // Cleanup must not mask e: DisposeChunks swallows internally and
562- // we dispose mmf/fs through the swallowing overload (not the
563- // priorException overload, which would re-throw), then bare-
564- // rethrow to preserve e's stack trace. With leaveOpen: true we
565- // always own fs; dispose mmf first so the mapping is torn down
566- // before the backing handle closes.
567- DisposeChunks ( chunks ) ;
568- IOUtils . DisposeWhileHandlingException ( mmf , fs ) ;
569- throw ;
562+ DisposeResourcesWhileHandlingException ( e , chunks , mmf , fs ) ;
563+ return null ! ; // unreachable
570564 }
571565 }
572566
@@ -580,11 +574,7 @@ public void Dispose()
580574 if ( Interlocked . CompareExchange ( ref disposed , 1 , 0 ) != 0 ) return ;
581575 reclaimer . Close ( ( ) =>
582576 {
583- DisposeChunks ( Chunks ) ;
584- // mmf first so the mapping is torn down before we close the
585- // handle by disposing the FileStream we own. Both are null for
586- // the zero-length edge case, which the overload tolerates.
587- IOUtils . DisposeWhileHandlingException ( memoryMappedFile , fileStream ) ;
577+ DisposeResourcesWhileHandlingException ( null , Chunks , memoryMappedFile , fileStream ) ;
588578 } ) ;
589579 }
590580
@@ -596,7 +586,7 @@ private static Chunk[] MapChunks(MemoryMappedFile? mmf, long offset, long length
596586 {
597587 if ( length == 0 || mmf == null )
598588 {
599- return Array . Empty < Chunk > ( ) ;
589+ return [ ] ;
600590 }
601591
602592 long chunkSize = 1L << chunkSizePower ;
@@ -611,50 +601,40 @@ private static Chunk[] MapChunks(MemoryMappedFile? mmf, long offset, long length
611601 int nChunks = ( int ) ( ( length + chunkSize - 1 ) >> chunkSizePower ) ;
612602 var result = new Chunk [ nChunks ] ;
613603
614- try
604+ for ( int i = 0 ; i < nChunks ; i ++ )
615605 {
616- for ( int i = 0 ; i < nChunks ; i ++ )
617- {
618- long chunkOffset = offset + ( ( long ) i << chunkSizePower ) ;
619- long thisChunkLen = Math . Min ( chunkSize , length - ( ( long ) i << chunkSizePower ) ) ;
620-
621- MemoryMappedViewAccessor accessor = mmf . CreateViewAccessor ( chunkOffset , thisChunkLen , MemoryMappedFileAccess . Read ) ;
622- // The Chunk ctor acquires the view's pointer (a fallible native
623- // call). If it throws, the accessor isn't in result[] yet, so
624- // DisposeChunks below would miss it - dispose it here instead.
625- try
626- {
627- result [ i ] = new Chunk ( accessor , accessor . PointerOffset , thisChunkLen ) ;
628- }
629- catch
630- {
631- IOUtils . DisposeWhileHandlingException ( accessor ) ;
632- throw ;
633- }
634- }
635- return result ;
636- }
637- catch
638- {
639- DisposeChunks ( result ) ;
640- throw ;
641- }
642- }
606+ long chunkOffset = offset + ( ( long ) i << chunkSizePower ) ;
607+ long thisChunkLen = Math . Min ( chunkSize , length - ( ( long ) i << chunkSizePower ) ) ;
643608
644- private static void DisposeChunks ( Chunk [ ] ? chunks )
645- {
646- if ( chunks == null ) return ;
647- foreach ( var c in chunks )
648- {
609+ MemoryMappedViewAccessor accessor = mmf . CreateViewAccessor ( chunkOffset , thisChunkLen , MemoryMappedFileAccess . Read ) ;
610+ // The Chunk ctor acquires the view's pointer (a fallible native
611+ // call). If it throws, the accessor isn't in result[] yet, so
612+ // DisposeChunks in the caller would miss it - dispose it here instead.
649613 try
650614 {
651- c . Release ( ) ;
615+ result [ i ] = new Chunk ( accessor , accessor . PointerOffset , thisChunkLen ) ;
652616 }
653- catch
617+ catch ( Exception e )
654618 {
655- /* never propagate from cleanup */
619+ IOUtils . DisposeWhileHandlingException ( e , accessor ) ;
620+ return null ! ; // unreachable
656621 }
657622 }
623+
624+ return result ;
625+ }
626+
627+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
628+ private static void DisposeResourcesWhileHandlingException ( Exception ? priorException , Chunk [ ] ? chunks , MemoryMappedFile ? mmf , FileStream ? fs )
629+ {
630+ // With leaveOpen: true we always own fs; dispose mmf first after chunks so the mapping is torn down
631+ // before the backing handle closes.
632+ var disposables = ( ( SCG . IEnumerable < IDisposable ? > ) ( chunks ?? [ ] ) )
633+ . Append ( mmf )
634+ . Append ( fs ) ;
635+
636+ // DisposeWhileHandlingException tolerates null disposables
637+ IOUtils . DisposeWhileHandlingException ( priorException , disposables ) ;
658638 }
659639 }
660640
@@ -671,14 +651,14 @@ private static void DisposeChunks(Chunk[]? chunks)
671651 /// the #1151 contention, so it is gone. The drain barrier that keeps the
672652 /// mapping valid under a concurrent close is now the mapping's
673653 /// <see cref="DrainReclaimer"/>: a reader brackets each dereference with
674- /// <c>Enter</c>/<c>Exit</c>, and the reclaimer 's <c>Close</c> defers the
654+ /// <c>Enter</c>/<c>Exit</c>, and the recl aimer 's <c>Close</c> defers the
675655 /// actual <c>UnmapViewOfFile</c>/<c>munmap</c> (this chunk's
676- /// <see cref="Release "/>) until every in-flight reader has drained. So an
656+ /// <see cref="Dispose "/>) until every in-flight reader has drained. So an
677657 /// AVE is still structurally impossible - the unmap cannot run while a
678658 /// reader is mid-dereference - but liveness is proven by the reclaimer's
679659 /// hazard handshake rather than by a per-access SafeHandle refcount.
680660 /// </summary>
681- internal sealed unsafe class Chunk
661+ internal sealed unsafe class Chunk : IDisposable
682662 {
683663 private readonly MemoryMappedViewAccessor accessor ;
684664 private readonly SafeMemoryMappedViewHandle safe ;
@@ -724,7 +704,7 @@ internal Chunk(MemoryMappedViewAccessor accessor, long pointerOffset, long lengt
724704 /// reclaimer only calls this once all in-flight readers have drained,
725705 /// so it never unmaps a view out from under a live reader. Idempotent.
726706 /// </summary>
727- public void Release ( )
707+ public void Dispose ( )
728708 {
729709 if ( Interlocked . CompareExchange ( ref closed , 1 , 0 ) != 0 ) return ;
730710 if ( acquired )
0 commit comments