@@ -289,8 +289,22 @@ public class MmapDefault : IWeakReferenceable {
289289 private readonly MemoryMappedFileAccess _fileAccess ;
290290 private readonly SafeFileHandle ? _handle ;
291291
292- private volatile bool _isClosed ;
293- private int _refCount = 1 ;
292+ // RefCount | Closed | Meaning
293+ // ---------+--------+--------------------------------
294+ // 0 | 0 | Not fully initialized
295+ // 1 | 0 | Fully initialized, not being used by any threads
296+ // >1 | 0 | Object in use by one or more threads
297+ // >0 | 1 | Close/dispose requested, no more addrefs allowed, some threads may still be using it
298+ // 0 | 1 | Fully disposed
299+ private volatile int _state ; // Combined ref count and closed/disposed flags (so we can atomically modify them).
300+
301+ private static class StateBits {
302+ public const int Closed = 0b_01 ; // close/dispose requested; no more addrefs allowed
303+ public const int Exclusive = 0b_10 ; // TODO: to manage exclusive access for resize
304+ public const int RefCount = unchecked ( ~ 0b_11 ) ; // 2 bits reserved for state management; ref count gets 29 bits (sign bit unused)
305+ public const int RefCountOne = 1 << 2 ; // ref count 1 shifted over 2 state bits
306+ }
307+
294308
295309 public MmapDefault ( CodeContext /*!*/ context , int fileno , long length , string ? tagname , MemoryMappedFileAccess fileAccess , long offset ) {
296310 _fileAccess = fileAccess ;
@@ -401,6 +415,7 @@ public MmapDefault(CodeContext/*!*/ context, int fileno, long length, string? ta
401415 throw ;
402416 }
403417 _position = 0L ;
418+ _state = StateBits . RefCountOne ; // Fully initialized: ref count 1 and not closed or disposed.
404419
405420 void CheckFileAccessAndSize ( Stream stream , bool isWindows ) {
406421 bool isValid = _fileAccess switch {
@@ -544,29 +559,81 @@ public void __exit__(CodeContext/*!*/ context, params object[] excinfo) {
544559 close ( ) ;
545560 }
546561
547- public bool closed => _isClosed ;
548562
549- public void close ( ) {
550- if ( ! _isClosed ) {
551- lock ( this ) {
552- if ( ! _isClosed ) {
553- _isClosed = true ;
554- CloseWorker ( ) ;
555- }
563+ public bool closed => ( _state & StateBits . Closed ) == StateBits . Closed ; // Dispose already requested, will self-dispose when ref count drops to 0.
564+
565+
566+ private bool AddRef ( ) {
567+ int oldState , newState ;
568+ do {
569+ oldState = _state ;
570+ if ( ( oldState & StateBits . Closed ) == StateBits . Closed ) {
571+ // mmap closed, dispose already requested, no more addrefs allowed
572+ return false ;
556573 }
557- }
574+ Debug . Assert ( ( oldState & StateBits . RefCount ) > 0 , "resurrecting disposed mmap object (disposed without being closed)" ) ;
575+
576+ newState = oldState + StateBits . RefCountOne ;
577+ } while ( Interlocked . CompareExchange ( ref _state , newState , oldState ) != oldState ) ;
578+ return true ;
558579 }
559580
560- private void CloseWorker ( ) {
561- if ( Interlocked . Decrement ( ref _refCount ) == 0 ) {
581+
582+ private void Release ( ) {
583+ bool performDispose ;
584+ int oldState , newState ;
585+ do {
586+ oldState = _state ;
587+ Debug . Assert ( ( oldState & StateBits . RefCount ) > 0 , "mmap ref count underflow (too many releases)" ) ;
588+
589+ performDispose = ( oldState & StateBits . RefCount ) == StateBits . RefCountOne ;
590+ newState = oldState - StateBits . RefCountOne ;
591+ if ( performDispose ) {
592+ newState |= StateBits . Closed ; // most likely already closed
593+ }
594+ } while ( Interlocked . CompareExchange ( ref _state , newState , oldState ) != oldState ) ;
595+
596+ if ( performDispose ) {
562597 _view . Flush ( ) ;
563598 _view . Dispose ( ) ;
564599 _file . Dispose ( ) ;
565600 CloseFileHandle ( ) ;
566601 _sourceStream = null ;
602+ _view = null ! ;
603+ _file = null ! ;
567604 }
568605 }
569606
607+
608+ public void close ( ) {
609+ // close is idempotent; it must never block
610+ #if NET5_0_OR_GREATER
611+ if ( ( Interlocked . Or ( ref _state , StateBits . Closed ) & StateBits . Closed ) != StateBits . Closed ) {
612+ // freshly closed, release the construction time reference
613+ Release ( ) ;
614+ }
615+ #else
616+ int current = _state ;
617+ while ( true )
618+ {
619+ int newState = current | StateBits . Closed ;
620+ int oldState = Interlocked . CompareExchange ( ref _state , newState , current ) ;
621+ if ( oldState == current )
622+ {
623+ // didn't change in the meantime, exchange with newState completed
624+ if ( ( oldState & StateBits . Closed ) != StateBits . Closed ) {
625+ // freshly closed, release the construction time reference
626+ Release ( ) ;
627+ }
628+ return ;
629+ }
630+ // try again to set the bit
631+ current = oldState ;
632+ }
633+ #endif
634+ }
635+
636+
570637 private void CloseFileHandle ( ) {
571638 if ( _handle is not null ) {
572639 // mmap owns _sourceStream too (if any) in this case
@@ -1138,34 +1205,31 @@ private static long GetFileSizeUnix(SafeFileHandle handle) {
11381205
11391206 #endregion
11401207
1141- #region Synchronization
11421208
1143- private void EnsureOpen ( ) {
1144- if ( _isClosed ) {
1145- throw PythonOps . ValueError ( "mmap closed or invalid" ) ;
1146- }
1147- }
1209+ #region Synchronization
11481210
11491211 private readonly struct MmapLocker : IDisposable {
11501212 private readonly MmapDefault _mmap ;
11511213
11521214 public MmapLocker ( MmapDefault mmap ) {
1215+ if ( ! mmap . AddRef ( ) ) {
1216+ throw PythonOps . ValueError ( "mmap closed or invalid" ) ;
1217+ }
11531218 _mmap = mmap ;
1154- _mmap . EnsureOpen ( ) ;
1155- Interlocked . Increment ( ref _mmap . _refCount ) ;
11561219 }
11571220
11581221 #region IDisposable Members
11591222
1160- public void Dispose ( ) {
1161- _mmap . CloseWorker ( ) ;
1223+ public readonly void Dispose ( ) {
1224+ _mmap . Release ( ) ;
11621225 }
11631226
11641227 #endregion
11651228 }
11661229
11671230 #endregion
11681231
1232+
11691233 #region IWeakReferenceable Members
11701234
11711235 private WeakRefTracker ? _tracker ;
@@ -1185,6 +1249,7 @@ void IWeakReferenceable.SetFinalizer(WeakRefTracker value) {
11851249 #endregion
11861250 }
11871251
1252+
11881253 #region P/Invoke for allocation granularity
11891254
11901255 [ StructLayout ( LayoutKind . Sequential ) ]
0 commit comments