@@ -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