Skip to content

Commit da2da14

Browse files
authored
Implement Dispose for CData (#1416)
* Implement Dispose for CData * Fix code for multiple Dispose calls scenario
1 parent 6338a72 commit da2da14

2 files changed

Lines changed: 92 additions & 33 deletions

File tree

Src/IronPython.Modules/_ctypes/CData.cs

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Buffers;
1111
using System.Collections.Generic;
1212
using System.Diagnostics;
13+
using System.Threading;
1314

1415
using IronPython.Runtime;
1516
using IronPython.Runtime.Exceptions;
@@ -38,7 +39,7 @@ public static partial class CTypes {
3839
/// Base class for all ctypes interop types.
3940
/// </summary>
4041
[PythonType("_CData"), PythonHidden]
41-
public abstract class CData : IBufferProtocol, IPythonBuffer {
42+
public abstract class CData : IBufferProtocol, IDisposable {
4243
internal MemoryHolder MemHolder {
4344
get => _memHolder ?? throw new InvalidOperationException($"{nameof(CData)} object not fully initialized.");
4445
set {
@@ -47,6 +48,7 @@ internal MemoryHolder MemHolder {
4748
}
4849
}
4950
private MemoryHolder? _memHolder;
51+
private bool _disposed;
5052

5153
// members: __setstate__, __reduce__ _b_needsfree_ __ctypes_from_outparam__ __hash__ _objects _b_base_ __doc__
5254
protected CData() { }
@@ -108,25 +110,37 @@ internal void InitializeFromBufferCopy(object? data, int offset, int size) {
108110
internal virtual PythonTuple GetBufferInfo()
109111
=> PythonTuple.MakeTuple(NativeType.TypeFormat, 0, PythonTuple.EMPTY);
110112

113+
[PythonHidden]
114+
public void Dispose() {
115+
if (!_disposed) {
116+
_disposed = true;
117+
if (_numExports == 0) {
118+
MemoryHolder? holder = Interlocked.Exchange(ref _memHolder, null);
119+
holder?.Dispose();
120+
}
121+
}
122+
}
111123

112-
#region IBufferProtocol Members
113-
114-
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags) => this;
115-
116-
void IDisposable.Dispose() { } // TODO
117-
118-
object IPythonBuffer.Object => this;
119-
120-
unsafe ReadOnlySpan<byte> IPythonBuffer.AsReadOnlySpan()
121-
=> new Span<byte>(MemHolder.UnsafeAddress.ToPointer(), MemHolder.Size);
124+
#region IBufferProtocol
122125

123-
unsafe Span<byte> IPythonBuffer.AsSpan()
124-
=> new Span<byte>(MemHolder.UnsafeAddress.ToPointer(), MemHolder.Size);
126+
private int _numExports;
125127

126-
unsafe MemoryHandle IPythonBuffer.Pin()
127-
=> new MemoryHandle(MemHolder.UnsafeAddress.ToPointer());
128+
IPythonBuffer IBufferProtocol.GetBuffer(BufferFlags flags) {
129+
if (_disposed) throw new ObjectDisposedException(GetType().Name);
130+
_ = MemHolder; // check if fully initialized
131+
Interlocked.Increment(ref _numExports);
132+
return new CDataView(this);
133+
}
128134

129-
int IPythonBuffer.Offset => 0;
135+
// May be executed concurremtly with Dispose(), GetBuffer(), or another ReleaseBuffer()
136+
private void ReleaseBuffer() {
137+
Debug.Assert(_numExports > 0);
138+
int cnt = Interlocked.Decrement(ref _numExports);
139+
if (cnt == 0 && _disposed) {
140+
MemoryHolder? holder = Interlocked.Exchange(ref _memHolder, null);
141+
holder?.Dispose();
142+
}
143+
}
130144

131145
[PythonHidden]
132146
public virtual int ItemCount {
@@ -141,21 +155,53 @@ public virtual int ItemCount {
141155
}
142156
}
143157

144-
string IPythonBuffer.Format => NativeType.TypeFormat;
145-
146158
[PythonHidden]
147159
public virtual int ItemSize => NativeType.Size;
148160

149-
int IPythonBuffer.NumOfDims => Shape?.Count ?? (ItemCount > 1 ? 1 : 0);
150-
151-
bool IPythonBuffer.IsReadOnly => false;
152-
153161
[PythonHidden]
154162
public virtual IReadOnlyList<int>? Shape => null;
155163

156-
IReadOnlyList<int>? IPythonBuffer.Strides => null;
157164

158-
IReadOnlyList<int>? IPythonBuffer.SubOffsets => null;
165+
private sealed class CDataView : IPythonBuffer {
166+
private CData? _cdata;
167+
private CData CData => _cdata ?? throw new ObjectDisposedException(nameof(CDataView));
168+
169+
internal CDataView(CData cdata) => _cdata = cdata;
170+
171+
public void Dispose() {
172+
CData? cdata = Interlocked.Exchange(ref _cdata, null);
173+
cdata?.ReleaseBuffer();
174+
}
175+
176+
public object Object => CData;
177+
178+
unsafe ReadOnlySpan<byte> IPythonBuffer.AsReadOnlySpan()
179+
=> new Span<byte>(CData.MemHolder.UnsafeAddress.ToPointer(), CData.MemHolder.Size);
180+
181+
unsafe Span<byte> IPythonBuffer.AsSpan()
182+
=> new Span<byte>(CData.MemHolder.UnsafeAddress.ToPointer(), CData.MemHolder.Size);
183+
184+
unsafe MemoryHandle IPythonBuffer.Pin()
185+
=> new MemoryHandle(CData.MemHolder.UnsafeAddress.ToPointer());
186+
187+
public int Offset => 0;
188+
189+
public int ItemCount => CData.ItemCount;
190+
191+
public string Format => CData.NativeType.TypeFormat;
192+
193+
public int ItemSize => CData.ItemSize;
194+
195+
public int NumOfDims => Shape?.Count ?? (ItemCount > 1 ? 1 : 0);
196+
197+
public bool IsReadOnly => false;
198+
199+
public IReadOnlyList<int>? Shape => CData.Shape;
200+
201+
IReadOnlyList<int>? IPythonBuffer.Strides => null;
202+
203+
IReadOnlyList<int>? IPythonBuffer.SubOffsets => null;
204+
}
159205

160206
#endregion
161207
}

Src/IronPython.Modules/_ctypes/MemoryHolder.cs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@ internal sealed class MemoryHolder : CriticalFinalizerObject, IDisposable {
2929
private readonly int _size;
3030
private readonly IPythonBuffer? _buffer;
3131
private readonly MemoryHandle _handle;
32+
private readonly MemoryHolder? _parent;
33+
private bool _disposeRequested;
3234
private bool _disposed;
35+
private int _numChildren;
3336
private PythonDictionary? _objects;
34-
#pragma warning disable IDE0052 // Remove unread private members
35-
// Field not accessed but keeps a reference to the parent holder preventing it from being garbage-collected.
36-
private readonly MemoryHolder? _parent;
37-
#pragma warning restore IDE0052 // Remove unread private members
3837

3938
/// <summary>
4039
/// Creates a new MemoryHolder and allocates a buffer of the specified size.
@@ -69,12 +68,12 @@ public MemoryHolder(IntPtr data, int size) {
6968
/// <summary>
7069
/// Creates a new MemoryHolder at the specified address which will keep alive the
7170
/// parent memory holder.
72-
/// TODO: Dispose() usage? Reference-count the parent?
7371
/// </summary>
7472
public MemoryHolder(IntPtr data, int size, MemoryHolder parent) {
7573
GC.SuppressFinalize(this);
7674
_data = data;
7775
_parent = parent;
76+
parent._numChildren++;
7877
_objects = parent._objects;
7978
_size = size;
8079
}
@@ -86,9 +85,14 @@ public MemoryHolder(IntPtr data, int size, MemoryHolder parent) {
8685
public MemoryHolder(IPythonBuffer buffer, int offset, int size) {
8786
if (buffer.IsReadOnly) throw new ArgumentException("Buffer must be writable.");
8887
if (!buffer.IsCContiguous()) throw new ArgumentException("Buffer must be c-contiguous.");
89-
if (size < 0) throw new ArgumentOutOfRangeException(nameof(size), size, $"Non-negative number required.");
90-
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), offset, $"Non-negative number required.");
91-
if (size > buffer.NumBytes() - offset) throw new ArgumentException($"Requested memory block exceeds buffer boundaries.");
88+
if (size < 0) throw new ArgumentOutOfRangeException(nameof(size), size, "Non-negative number required.");
89+
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Non-negative number required.");
90+
91+
int bufSize = buffer.NumBytes();
92+
ReadOnlySpan<byte> memblock = buffer.AsSpan();
93+
if (memblock.Length != bufSize) new ArgumentException("Invalid buffer.");
94+
if (size > bufSize - offset) throw new ArgumentException("Requested memory block exceeds buffer boundaries.");
95+
9296

9397
_buffer = buffer;
9498
_handle = buffer.Pin();
@@ -320,15 +324,23 @@ public void CopyTo(MemoryHolder/*!*/ destAddress, int writeOffset, int size) {
320324
GC.KeepAlive(this);
321325
}
322326

327+
private void ReleaseChild() {
328+
Debug.Assert(_numChildren >= 0);
329+
_numChildren--;
330+
if (_disposeRequested) Dispose();
331+
}
332+
323333
public void Dispose() {
324-
if (!_disposed) {
334+
_disposeRequested = true;
335+
if (!_disposed && _numChildren == 0) {
325336
_disposed = true;
326337

327338
if (_ownsData) {
328339
Marshal.FreeHGlobal(_data);
329340
}
330341
_handle.Dispose();
331342
_buffer?.Dispose();
343+
_parent?.ReleaseChild();
332344

333345
GC.SuppressFinalize(this);
334346
}
@@ -344,6 +356,7 @@ public void Dispose() {
344356
}
345357
try { _handle.Dispose(); } catch { /*ignore*/ }
346358
try { _buffer?.Dispose(); } catch { /*ignore*/ }
359+
try { _parent?.ReleaseChild(); } catch { /*ignore*/ }
347360
}
348361
}
349362
}

0 commit comments

Comments
 (0)