@@ -289,20 +289,23 @@ public class MmapDefault : IWeakReferenceable {
289
289
private readonly MemoryMappedFileAccess _fileAccess ;
290
290
private readonly SafeFileHandle ? _handle ;
291
291
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).
300
302
301
303
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
306
309
}
307
310
308
311
@@ -563,33 +566,74 @@ public void __exit__(CodeContext/*!*/ context, params object[] excinfo) {
563
566
public bool closed => ( _state & StateBits . Closed ) == StateBits . Closed ; // Dispose already requested, will self-dispose when ref count drops to 0.
564
567
565
568
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 ) {
567
584
int oldState , newState ;
568
585
do {
569
586
oldState = _state ;
570
587
if ( ( oldState & StateBits . Closed ) == StateBits . Closed ) {
571
588
// 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 ;
572
600
return false ;
573
601
}
574
602
Debug . Assert ( ( oldState & StateBits . RefCount ) > 0 , "resurrecting disposed mmap object (disposed without being closed)" ) ;
575
603
576
604
newState = oldState + StateBits . RefCountOne ;
605
+ if ( exclusive ) {
606
+ newState |= StateBits . Exclusive ;
607
+ }
577
608
} while ( Interlocked . CompareExchange ( ref _state , newState , oldState ) != oldState ) ;
609
+ reason = 0 ;
578
610
return true ;
579
611
}
580
612
581
613
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 ) {
583
624
bool performDispose ;
584
625
int oldState , newState ;
585
626
do {
586
627
oldState = _state ;
628
+ Debug . Assert ( ! exclusive || ( oldState & StateBits . Exclusive ) == StateBits . Exclusive , "releasing exclusive reference without being exclusive" ) ;
587
629
Debug . Assert ( ( oldState & StateBits . RefCount ) > 0 , "mmap ref count underflow (too many releases)" ) ;
588
630
589
631
performDispose = ( oldState & StateBits . RefCount ) == StateBits . RefCountOne ;
632
+ Debug . Assert ( ! performDispose || ( oldState & StateBits . Closed ) == StateBits . Closed , "disposing mmap object without being closed" ) ;
633
+
590
634
newState = oldState - StateBits . RefCountOne ;
591
- if ( performDispose ) {
592
- newState |= StateBits . Closed ; // most likely already closed
635
+ if ( exclusive ) {
636
+ newState &= ~ StateBits . Exclusive ;
593
637
}
594
638
} while ( Interlocked . CompareExchange ( ref _state , newState , oldState ) != oldState ) ;
595
639
@@ -610,25 +654,17 @@ public void close() {
610
654
#if NET5_0_OR_GREATER
611
655
if ( ( Interlocked . Or ( ref _state , StateBits . Closed ) & StateBits . Closed ) != StateBits . Closed ) {
612
656
// freshly closed, release the construction time reference
613
- Release ( ) ;
657
+ Release ( exclusive : false ) ;
614
658
}
615
659
#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 ) ;
632
668
}
633
669
#endif
634
670
}
@@ -861,8 +897,9 @@ public string readline() {
861
897
}
862
898
}
863
899
900
+
864
901
public void resize ( long newsize ) {
865
- using ( new MmapLocker ( this ) ) {
902
+ using ( new MmapLocker ( this , exclusive : true ) ) {
866
903
if ( _fileAccess is not MemoryMappedFileAccess . ReadWrite and not MemoryMappedFileAccess . ReadWriteExecute ) {
867
904
throw PythonOps . TypeError ( "mmap can't resize a readonly or copy-on-write memory map." ) ;
868
905
}
@@ -961,6 +998,7 @@ public void resize(long newsize) {
961
998
}
962
999
}
963
1000
1001
+
964
1002
public object rfind ( [ NotNone ] IBufferProtocol s ) {
965
1003
using ( new MmapLocker ( this ) ) {
966
1004
return RFindWorker ( s , Position , _view . Capacity ) ;
@@ -1210,18 +1248,32 @@ private static long GetFileSizeUnix(SafeFileHandle handle) {
1210
1248
1211
1249
private readonly struct MmapLocker : IDisposable {
1212
1250
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
+ }
1217
1268
}
1218
1269
_mmap = mmap ;
1270
+ _exclusive = exclusive ;
1219
1271
}
1220
1272
1221
1273
#region IDisposable Members
1222
1274
1223
1275
public readonly void Dispose ( ) {
1224
- _mmap . Release ( ) ;
1276
+ _mmap . Release ( _exclusive ) ;
1225
1277
}
1226
1278
1227
1279
#endregion
0 commit comments