@@ -289,20 +289,23 @@ public class MmapDefault : IWeakReferenceable {
289289 private readonly MemoryMappedFileAccess _fileAccess ;
290290 private readonly SafeFileHandle ? _handle ;
291291
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).
292+ // RefCount | Closed | Exclusive |Meaning
293+ // ---------+--------+-----------+---------------------
294+ // 0 | 0 | 0 | Not fully initialized
295+ // 1 | 0 | 0 | Fully initialized, not being used by any threads
296+ // >1 | 0 | 0 | Object in regular use by one or more threads
297+ // 2 | 0 | 1 | Object in exclusive use (`resize` in progress)
298+ // >0 | 1 | - | Close/dispose requested, no more addrefs allowed, some threads may still be using it
299+ // 0 | 1 | 0 | Fully disposed
300+ // Other combinations are invalid state
301+ private volatile int _state ; // Combined ref count and state flags (so we can atomically modify them).
300302
301303 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
304+ public const int Closed = 0b_001 ; // close/dispose requested; no more addrefs allowed
305+ public const int Exclusive = 0b_010 ; // exclusive access for resize requested/in progress
306+ public const int Exporting = 0b_100 ; // TODO: buffer exports extant; exclusive addrefs temporarily not allowed
307+ public const int RefCount = unchecked ( ~ 0b_111 ) ; // 3 bits reserved for state management; ref count gets 29 bits (sign bit unused)
308+ public const int RefCountOne = 1 << 3 ; // ref count 1 shifted over 3 state bits
306309 }
307310
308311
@@ -563,33 +566,74 @@ public void __exit__(CodeContext/*!*/ context, params object[] excinfo) {
563566 public bool closed => ( _state & StateBits . Closed ) == StateBits . Closed ; // Dispose already requested, will self-dispose when ref count drops to 0.
564567
565568
566- private bool AddRef ( ) {
569+ /// <summary>
570+ /// Try to add a reference to the mmap object. Return <c>true</c> on success.
571+ /// </summary>
572+ /// <remarks>
573+ /// The reference count is incremented atomically and kept in the mmap state variable.
574+ /// The reference count is not incremented if the state variable indicates that the mmap
575+ /// in in the process of closing or currently in exclusive use.
576+ /// </remarks>
577+ /// <param name="exclusive">
578+ /// If true, requests an exclusive reference.
579+ /// </param>
580+ /// <param name="reason">
581+ /// If the reference could not be added, this parameter will contain the bit that was set preventing the addref.
582+ /// </param>
583+ private bool TryAddRef ( bool exclusive , out int reason ) {
567584 int oldState , newState ;
568585 do {
569586 oldState = _state ;
570587 if ( ( oldState & StateBits . Closed ) == StateBits . Closed ) {
571588 // mmap closed, dispose already requested, no more addrefs allowed
589+ reason = StateBits . Closed ;
590+ return false ;
591+ }
592+ if ( ( oldState & StateBits . Exclusive ) == StateBits . Exclusive ) {
593+ // mmap in exclusive use, temporarily no more addrefs allowed
594+ reason = StateBits . Exclusive ;
595+ return false ;
596+ }
597+ if ( exclusive && ( oldState & StateBits . Exporting ) == StateBits . Exporting ) {
598+ // mmap exporting, exclusive addrefs temporarily not allowed
599+ reason = StateBits . Exporting ;
572600 return false ;
573601 }
574602 Debug . Assert ( ( oldState & StateBits . RefCount ) > 0 , "resurrecting disposed mmap object (disposed without being closed)" ) ;
575603
576604 newState = oldState + StateBits . RefCountOne ;
605+ if ( exclusive ) {
606+ newState |= StateBits . Exclusive ;
607+ }
577608 } while ( Interlocked . CompareExchange ( ref _state , newState , oldState ) != oldState ) ;
609+ reason = 0 ;
578610 return true ;
579611 }
580612
581613
582- private void Release ( ) {
614+ /// <summary>
615+ /// Atomically release a reference to the mmap object, and optionally reset the exclusive state flag.
616+ /// </summary>
617+ /// <remarks>
618+ /// If the reference count drops to 0, the mmap object is disposed.
619+ /// </remarks>
620+ /// <param name="exclusive">
621+ /// If true, the exclusive reference is released.
622+ /// </param>
623+ private void Release ( bool exclusive ) {
583624 bool performDispose ;
584625 int oldState , newState ;
585626 do {
586627 oldState = _state ;
628+ Debug . Assert ( ! exclusive || ( oldState & StateBits . Exclusive ) == StateBits . Exclusive , "releasing exclusive reference without being exclusive" ) ;
587629 Debug . Assert ( ( oldState & StateBits . RefCount ) > 0 , "mmap ref count underflow (too many releases)" ) ;
588630
589631 performDispose = ( oldState & StateBits . RefCount ) == StateBits . RefCountOne ;
632+ Debug . Assert ( ! performDispose || ( oldState & StateBits . Closed ) == StateBits . Closed , "disposing mmap object without being closed" ) ;
633+
590634 newState = oldState - StateBits . RefCountOne ;
591- if ( performDispose ) {
592- newState |= StateBits . Closed ; // most likely already closed
635+ if ( exclusive ) {
636+ newState &= ~ StateBits . Exclusive ;
593637 }
594638 } while ( Interlocked . CompareExchange ( ref _state , newState , oldState ) != oldState ) ;
595639
@@ -610,25 +654,17 @@ public void close() {
610654#if NET5_0_OR_GREATER
611655 if ( ( Interlocked . Or ( ref _state , StateBits . Closed ) & StateBits . Closed ) != StateBits . Closed ) {
612656 // freshly closed, release the construction time reference
613- Release ( ) ;
657+ Release ( exclusive : false ) ;
614658 }
615659#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 ;
660+ int oldState , newState ;
661+ do {
662+ oldState = _state ;
663+ newState = oldState | StateBits . Closed ;
664+ } while ( Interlocked . CompareExchange ( ref _state , newState , oldState ) != oldState ) ;
665+ if ( ( oldState & StateBits . Closed ) != StateBits . Closed ) {
666+ // freshly closed, release the construction time reference
667+ Release ( exclusive : false ) ;
632668 }
633669#endif
634670 }
@@ -861,8 +897,9 @@ public string readline() {
861897 }
862898 }
863899
900+
864901 public void resize ( long newsize ) {
865- using ( new MmapLocker ( this ) ) {
902+ using ( new MmapLocker ( this , exclusive : true ) ) {
866903 if ( _fileAccess is not MemoryMappedFileAccess . ReadWrite and not MemoryMappedFileAccess . ReadWriteExecute ) {
867904 throw PythonOps . TypeError ( "mmap can't resize a readonly or copy-on-write memory map." ) ;
868905 }
@@ -961,6 +998,7 @@ public void resize(long newsize) {
961998 }
962999 }
9631000
1001+
9641002 public object rfind ( [ NotNone ] IBufferProtocol s ) {
9651003 using ( new MmapLocker ( this ) ) {
9661004 return RFindWorker ( s , Position , _view . Capacity ) ;
@@ -1210,18 +1248,32 @@ private static long GetFileSizeUnix(SafeFileHandle handle) {
12101248
12111249 private readonly struct MmapLocker : IDisposable {
12121250 private readonly MmapDefault _mmap ;
1213-
1214- public MmapLocker ( MmapDefault mmap ) {
1215- if ( ! mmap . AddRef ( ) ) {
1216- throw PythonOps . ValueError ( "mmap closed or invalid" ) ;
1251+ private readonly bool _exclusive ;
1252+
1253+ public MmapLocker ( MmapDefault mmap , bool exclusive = false ) {
1254+ if ( ! mmap . TryAddRef ( exclusive , out int reason ) ) {
1255+ if ( reason == StateBits . Closed ) {
1256+ // mmap is permanently closed
1257+ throw PythonOps . ValueError ( "mmap closed or invalid" ) ;
1258+ } else if ( reason == StateBits . Exporting ) {
1259+ // map is temporarily exporting buffers obtained through the buffer protocol
1260+ throw PythonOps . BufferError ( "mmap can't perform the operation with extant buffers exported" ) ;
1261+ } else if ( reason == StateBits . Exclusive ) {
1262+ // mmap is temporarily in exclusive use
1263+ throw PythonNT . GetOsError ( PythonErrno . EAGAIN ) ;
1264+ } else {
1265+ // should not happen
1266+ throw new InvalidOperationException ( "mmap state error" ) ;
1267+ }
12171268 }
12181269 _mmap = mmap ;
1270+ _exclusive = exclusive ;
12191271 }
12201272
12211273 #region IDisposable Members
12221274
12231275 public readonly void Dispose ( ) {
1224- _mmap . Release ( ) ;
1276+ _mmap . Release ( _exclusive ) ;
12251277 }
12261278
12271279 #endregion
0 commit comments