Skip to content

Commit 0bb4dfe

Browse files
committed
Add IBufferProtocol to mmap
1 parent 2aca667 commit 0bb4dfe

File tree

2 files changed

+98
-19
lines changed

2 files changed

+98
-19
lines changed

src/core/IronPython.Modules/mmap.cs

Lines changed: 98 additions & 11 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;
@@ -559,10 +561,8 @@ public void __exit__(CodeContext/*!*/ context, params object[] excinfo) {
559561
close();
560562
}
561563

562-
563564
public bool closed => (_state & StateBits.Closed) == StateBits.Closed; // Dispose already requested, will self-dispose when ref count drops to 0.
564565

565-
566566
private bool AddRef() {
567567
int oldState, newState;
568568
do {
@@ -606,6 +606,14 @@ private void Release() {
606606

607607

608608
public void close() {
609+
if ((_state & StateBits.RefCount) != 0) {
610+
GC.Collect();
611+
GC.WaitForPendingFinalizers();
612+
if ((_state & StateBits.RefCount) != 0) {
613+
throw PythonOps.BufferError("cannot close exported pointers exist");
614+
}
615+
}
616+
609617
// close is idempotent; it must never block
610618
#if NET5_0_OR_GREATER
611619
if ((Interlocked.Or(ref _state, StateBits.Closed) & StateBits.Closed) != StateBits.Closed) {
@@ -614,12 +622,10 @@ public void close() {
614622
}
615623
#else
616624
int current = _state;
617-
while (true)
618-
{
625+
while (true) {
619626
int newState = current | StateBits.Closed;
620627
int oldState = Interlocked.CompareExchange(ref _state, newState, current);
621-
if (oldState == current)
622-
{
628+
if (oldState == current) {
623629
// didn't change in the meantime, exchange with newState completed
624630
if ((oldState & StateBits.Closed) != StateBits.Closed) {
625631
// freshly closed, release the construction time reference
@@ -862,6 +868,14 @@ public string readline() {
862868
}
863869

864870
public void resize(long newsize) {
871+
if ((_state & StateBits.RefCount) != 0) {
872+
GC.Collect();
873+
GC.WaitForPendingFinalizers();
874+
if ((_state & StateBits.RefCount) != 0) {
875+
throw PythonOps.BufferError("mmap can't resize with extant buffers exported.");
876+
}
877+
}
878+
865879
using (new MmapLocker(this)) {
866880
if (_fileAccess is not MemoryMappedFileAccess.ReadWrite and not MemoryMappedFileAccess.ReadWriteExecute) {
867881
throw PythonOps.TypeError("mmap can't resize a readonly or copy-on-write memory map.");
@@ -894,13 +908,13 @@ public void resize(long newsize) {
894908
int fd = unchecked((int)_handle.DangerousGetHandle());
895909
PythonNT.ftruncateUnix(fd, newsize);
896910

897-
#if NET8_0_OR_GREATER
911+
#if NET8_0_OR_GREATER
898912
_file = MemoryMappedFile.CreateFromFile(_handle, _mapName, newsize, _fileAccess, HandleInheritability.None, leaveOpen: true);
899-
#else
913+
#else
900914
_sourceStream?.Dispose();
901915
_sourceStream = new FileStream(new SafeFileHandle((IntPtr)fd, ownsHandle: false), FileAccess.ReadWrite);
902916
_file = CreateFromFile(_sourceStream, _mapName, newsize, _fileAccess, HandleInheritability.None, leaveOpen: true);
903-
#endif
917+
#endif
904918
_view = _file.CreateViewAccessor(_offset, newsize, _fileAccess);
905919
return;
906920
} catch {
@@ -1130,8 +1144,10 @@ private long Position {
11301144
}
11311145
}
11321146

1147+
private bool IsReadOnly => _fileAccess is MemoryMappedFileAccess.Read or MemoryMappedFileAccess.ReadExecute;
1148+
11331149
private void EnsureWritable() {
1134-
if (_fileAccess is MemoryMappedFileAccess.Read or MemoryMappedFileAccess.ReadExecute) {
1150+
if (IsReadOnly) {
11351151
throw PythonOps.TypeError("mmap can't modify a read-only memory map.");
11361152
}
11371153
}
@@ -1247,6 +1263,77 @@ void IWeakReferenceable.SetFinalizer(WeakRefTracker value) {
12471263
}
12481264

12491265
#endregion
1266+
1267+
public IPythonBuffer GetBuffer(BufferFlags flags = BufferFlags.Simple) {
1268+
if (flags.HasFlag(BufferFlags.Writable) && IsReadOnly)
1269+
throw PythonOps.BufferError("Object is not writable.");
1270+
1271+
return new MmapBuffer(this, flags);
1272+
}
1273+
1274+
private sealed unsafe class MmapBuffer : IPythonBuffer {
1275+
private readonly MmapDefault _mmap;
1276+
private readonly BufferFlags _flags;
1277+
private SafeMemoryMappedViewHandle? _handle;
1278+
private byte* _pointer = null;
1279+
1280+
public MmapBuffer(MmapDefault mmap, BufferFlags flags) {
1281+
_mmap = mmap;
1282+
_flags = flags;
1283+
if (!_mmap.AddRef()) {
1284+
throw PythonOps.ValueError("mmap closed or invalid");
1285+
}
1286+
_handle = _mmap._view.SafeMemoryMappedViewHandle;
1287+
ItemCount = _mmap.__len__() is int i ? i : throw new NotImplementedException();
1288+
}
1289+
1290+
public object Object => _mmap;
1291+
1292+
public bool IsReadOnly => _mmap.IsReadOnly;
1293+
1294+
public int Offset => 0;
1295+
1296+
public string? Format => _flags.HasFlag(BufferFlags.Format) ? "B" : null;
1297+
1298+
public int ItemCount { get; }
1299+
1300+
public int ItemSize => 1;
1301+
1302+
public int NumOfDims => 1;
1303+
1304+
public IReadOnlyList<int>? Shape => null;
1305+
1306+
public IReadOnlyList<int>? Strides => null;
1307+
1308+
public IReadOnlyList<int>? SubOffsets => null;
1309+
1310+
public unsafe ReadOnlySpan<byte> AsReadOnlySpan() {
1311+
if (_handle is null) throw new ObjectDisposedException(nameof(MmapBuffer));
1312+
if (_pointer is null) _handle.AcquirePointer(ref _pointer);
1313+
return new ReadOnlySpan<byte>(_pointer, ItemCount);
1314+
}
1315+
1316+
public unsafe Span<byte> AsSpan() {
1317+
if (_handle is null) throw new ObjectDisposedException(nameof(MmapBuffer));
1318+
if (IsReadOnly) throw new InvalidOperationException("object is not writable");
1319+
if (_pointer is null) _handle.AcquirePointer(ref _pointer);
1320+
return new Span<byte>(_pointer, ItemCount);
1321+
}
1322+
1323+
public unsafe MemoryHandle Pin() {
1324+
if (_handle is null) throw new ObjectDisposedException(nameof(MmapBuffer));
1325+
if (_pointer is null) _handle.AcquirePointer(ref _pointer);
1326+
return new MemoryHandle(_pointer);
1327+
}
1328+
1329+
public void Dispose() {
1330+
var handle = Interlocked.Exchange(ref _handle, null);
1331+
if (handle is null) return;
1332+
if (_pointer is not null) handle.ReleasePointer();
1333+
_mmap.Release();
1334+
}
1335+
1336+
}
12501337
}
12511338

12521339

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)