Skip to content

Commit 144146d

Browse files
authored
Add IBufferProtocol to mmap (#1866)
* Add IBufferProtocol to mmap * Clean up interlocked or * Add missing TryAddRef case
1 parent e7a937d commit 144146d

File tree

2 files changed

+103
-28
lines changed

2 files changed

+103
-28
lines changed

src/core/IronPython.Modules/mmap.cs

Lines changed: 103 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#if FEATURE_MMAP
88

99
using System;
10+
using System.Buffers;
11+
using System.Collections.Generic;
1012
using System.Diagnostics;
1113
using System.Globalization;
1214
using System.IO;
@@ -278,7 +280,7 @@ private static MemoryMappedFileAccess ToMmapFileAccess(int access) {
278280
}
279281

280282
[PythonHidden]
281-
public class MmapDefault : IWeakReferenceable {
283+
public class MmapDefault : IWeakReferenceable, IBufferProtocol {
282284
private MemoryMappedFile _file;
283285
private MemoryMappedViewAccessor _view;
284286
private long _position;
@@ -599,6 +601,11 @@ private bool TryAddRef(bool exclusive, out int reason) {
599601
reason = StateBits.Exporting;
600602
return false;
601603
}
604+
if (exclusive && ((oldState & StateBits.RefCount) > StateBits.RefCountOne)) {
605+
// mmap in non-exclusive use, temporarily no exclusive use allowed
606+
reason = StateBits.Exclusive;
607+
return false;
608+
}
602609
Debug.Assert((oldState & StateBits.RefCount) > 0, "resurrecting disposed mmap object (disposed without being closed)");
603610

604611
newState = oldState + StateBits.RefCountOne;
@@ -635,6 +642,9 @@ private void Release(bool exclusive) {
635642
if (exclusive) {
636643
newState &= ~StateBits.Exclusive;
637644
}
645+
if ((newState & StateBits.RefCount) == StateBits.RefCountOne) {
646+
newState &= ~StateBits.Exporting;
647+
}
638648
} while (Interlocked.CompareExchange(ref _state, newState, oldState) != oldState);
639649

640650
if (performDispose) {
@@ -648,25 +658,28 @@ private void Release(bool exclusive) {
648658
}
649659
}
650660

661+
private int InterlockedOrState(int value) {
662+
#if NET5_0_OR_GREATER
663+
return Interlocked.Or(ref _state, value);
664+
#else
665+
int current = _state;
666+
while (true) {
667+
int newValue = current | value;
668+
int oldValue = Interlocked.CompareExchange(ref _state, newValue, current);
669+
if (oldValue == current) {
670+
return oldValue;
671+
}
672+
current = oldValue;
673+
}
674+
#endif
675+
}
651676

652677
public void close() {
653678
// close is idempotent; it must never block
654-
#if NET5_0_OR_GREATER
655-
if ((Interlocked.Or(ref _state, StateBits.Closed) & StateBits.Closed) != StateBits.Closed) {
679+
if ((InterlockedOrState(StateBits.Closed) & StateBits.Closed) != StateBits.Closed) {
656680
// freshly closed, release the construction time reference
657681
Release(exclusive: false);
658682
}
659-
#else
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);
668-
}
669-
#endif
670683
}
671684

672685

@@ -897,7 +910,6 @@ public string readline() {
897910
}
898911
}
899912

900-
901913
public void resize(long newsize) {
902914
using (new MmapLocker(this, exclusive: true)) {
903915
if (_fileAccess is not MemoryMappedFileAccess.ReadWrite and not MemoryMappedFileAccess.ReadWriteExecute) {
@@ -931,13 +943,13 @@ public void resize(long newsize) {
931943
int fd = unchecked((int)_handle.DangerousGetHandle());
932944
PythonNT.ftruncateUnix(fd, newsize);
933945

934-
#if NET8_0_OR_GREATER
946+
#if NET8_0_OR_GREATER
935947
_file = MemoryMappedFile.CreateFromFile(_handle, _mapName, newsize, _fileAccess, HandleInheritability.None, leaveOpen: true);
936-
#else
948+
#else
937949
_sourceStream?.Dispose();
938950
_sourceStream = new FileStream(new SafeFileHandle((IntPtr)fd, ownsHandle: false), FileAccess.ReadWrite);
939951
_file = CreateFromFile(_sourceStream, _mapName, newsize, _fileAccess, HandleInheritability.None, leaveOpen: true);
940-
#endif
952+
#endif
941953
_view = _file.CreateViewAccessor(_offset, newsize, _fileAccess);
942954
return;
943955
} catch {
@@ -1168,8 +1180,10 @@ private long Position {
11681180
}
11691181
}
11701182

1183+
private bool IsReadOnly => _fileAccess is MemoryMappedFileAccess.Read or MemoryMappedFileAccess.ReadExecute;
1184+
11711185
private void EnsureWritable() {
1172-
if (_fileAccess is MemoryMappedFileAccess.Read or MemoryMappedFileAccess.ReadExecute) {
1186+
if (IsReadOnly) {
11731187
throw PythonOps.TypeError("mmap can't modify a read-only memory map.");
11741188
}
11751189
}
@@ -1299,8 +1313,77 @@ void IWeakReferenceable.SetFinalizer(WeakRefTracker value) {
12991313
}
13001314

13011315
#endregion
1302-
}
13031316

1317+
public IPythonBuffer GetBuffer(BufferFlags flags = BufferFlags.Simple) {
1318+
if (flags.HasFlag(BufferFlags.Writable) && IsReadOnly)
1319+
throw PythonOps.BufferError("Object is not writable.");
1320+
1321+
return new MmapBuffer(this, flags);
1322+
}
1323+
1324+
private sealed unsafe class MmapBuffer : IPythonBuffer {
1325+
private readonly MmapDefault _mmap;
1326+
private readonly MmapLocker _locker;
1327+
private readonly BufferFlags _flags;
1328+
private SafeMemoryMappedViewHandle? _handle;
1329+
private byte* _pointer = null;
1330+
1331+
public MmapBuffer(MmapDefault mmap, BufferFlags flags) {
1332+
_mmap = mmap;
1333+
_flags = flags;
1334+
_locker = new MmapLocker(mmap);
1335+
mmap.InterlockedOrState(StateBits.Exporting);
1336+
_handle = _mmap._view.SafeMemoryMappedViewHandle;
1337+
ItemCount = _mmap.__len__() is int i ? i : throw new NotImplementedException();
1338+
}
1339+
1340+
public object Object => _mmap;
1341+
1342+
public bool IsReadOnly => _mmap.IsReadOnly;
1343+
1344+
public int Offset => 0;
1345+
1346+
public string? Format => _flags.HasFlag(BufferFlags.Format) ? "B" : null;
1347+
1348+
public int ItemCount { get; }
1349+
1350+
public int ItemSize => 1;
1351+
1352+
public int NumOfDims => 1;
1353+
1354+
public IReadOnlyList<int>? Shape => null;
1355+
1356+
public IReadOnlyList<int>? Strides => null;
1357+
1358+
public IReadOnlyList<int>? SubOffsets => null;
1359+
1360+
public unsafe ReadOnlySpan<byte> AsReadOnlySpan() {
1361+
if (_handle is null) throw new ObjectDisposedException(nameof(MmapBuffer));
1362+
if (_pointer is null) _handle.AcquirePointer(ref _pointer);
1363+
return new ReadOnlySpan<byte>(_pointer, ItemCount);
1364+
}
1365+
1366+
public unsafe Span<byte> AsSpan() {
1367+
if (_handle is null) throw new ObjectDisposedException(nameof(MmapBuffer));
1368+
if (IsReadOnly) throw new InvalidOperationException("object is not writable");
1369+
if (_pointer is null) _handle.AcquirePointer(ref _pointer);
1370+
return new Span<byte>(_pointer, ItemCount);
1371+
}
1372+
1373+
public unsafe MemoryHandle Pin() {
1374+
if (_handle is null) throw new ObjectDisposedException(nameof(MmapBuffer));
1375+
if (_pointer is null) _handle.AcquirePointer(ref _pointer);
1376+
return new MemoryHandle(_pointer);
1377+
}
1378+
1379+
public void Dispose() {
1380+
var handle = Interlocked.Exchange(ref _handle, null);
1381+
if (handle is null) return;
1382+
if (_pointer is not null) handle.ReleasePointer();
1383+
_locker.Dispose();
1384+
}
1385+
}
1386+
}
13041387

13051388
#region P/Invoke for allocation granularity
13061389

src/core/IronPython.Modules/re.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -516,11 +516,6 @@ private string ValidateString(object? @string) {
516516
case IList<byte> b:
517517
str = b.MakeString();
518518
break;
519-
#if FEATURE_MMAP
520-
case MmapModule.MmapDefault mmapFile:
521-
str = mmapFile.GetSearchString().MakeString();
522-
break;
523-
#endif
524519
case string _:
525520
case ExtensibleString _:
526521
throw PythonOps.TypeError("cannot use a bytes pattern on a string-like object");
@@ -537,9 +532,6 @@ private string ValidateString(object? @string) {
537532
break;
538533
case IBufferProtocol _:
539534
case IList<byte> _:
540-
#if FEATURE_MMAP
541-
case MmapModule.MmapDefault _:
542-
#endif
543535
throw PythonOps.TypeError("cannot use a string pattern on a bytes-like object");
544536
default:
545537
throw PythonOps.TypeError("expected string or bytes-like object");

0 commit comments

Comments
 (0)