22// The .NET Foundation licenses this file to you under the Apache 2.0 License.
33// See the LICENSE file in the project root for more information.
44
5+ #nullable enable
6+
57#if FEATURE_CTYPES
68#pragma warning disable SYSLIB0004 // The Constrained Execution Region (CER) feature is not supported in .NET 5.0.
79
810using System ;
11+ using System . Buffers ;
12+ using System . Diagnostics ;
913using System . IO ;
1014using System . Runtime . CompilerServices ;
1115using System . Runtime . ConstrainedExecution ;
@@ -19,17 +23,22 @@ namespace IronPython.Modules {
1923 /// A wrapper around allocated memory to ensure it gets released and isn't accessed
2024 /// when it could be finalized.
2125 /// </summary>
22- internal sealed class MemoryHolder : CriticalFinalizerObject {
26+ internal sealed class MemoryHolder : CriticalFinalizerObject , IDisposable {
2327 private readonly IntPtr _data ;
2428 private readonly bool _ownsData ;
2529 private readonly int _size ;
26- private PythonDictionary _objects ;
27- #pragma warning disable 414 // TODO: unused field?
28- private readonly MemoryHolder _parent ;
29- #pragma warning restore 414
30+ private readonly IPythonBuffer ? _buffer ;
31+ private readonly MemoryHandle _handle ;
32+ private bool _disposed ;
33+ 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
3038
3139 /// <summary>
3240 /// Creates a new MemoryHolder and allocates a buffer of the specified size.
41+ /// The buffer will be released on a call to <see cref="Dispose"/> or by the finalizer.
3342 /// </summary>
3443 [ ReliabilityContract ( Consistency . WillNotCorruptState , Cer . MayFail ) ]
3544 public MemoryHolder ( int size ) {
@@ -49,6 +58,7 @@ public MemoryHolder(int size) {
4958 /// <summary>
5059 /// Creates a new MemoryHolder at the specified address which is not tracked
5160 /// by us and we will never free.
61+ /// Therefore no need to call <see cref="Dispose"/>.
5262 /// </summary>
5363 public MemoryHolder ( IntPtr data , int size ) {
5464 GC . SuppressFinalize ( this ) ;
@@ -59,6 +69,7 @@ public MemoryHolder(IntPtr data, int size) {
5969 /// <summary>
6070 /// Creates a new MemoryHolder at the specified address which will keep alive the
6171 /// parent memory holder.
72+ /// TODO: Dispose() usage? Reference-count the parent?
6273 /// </summary>
6374 public MemoryHolder ( IntPtr data , int size , MemoryHolder parent ) {
6475 GC . SuppressFinalize ( this ) ;
@@ -69,39 +80,43 @@ public MemoryHolder(IntPtr data, int size, MemoryHolder parent) {
6980 }
7081
7182 /// <summary>
72- /// Gets the address of the held memory. The caller should ensure the MemoryHolder
73- /// is always alive as long as the address will continue to be accessed .
83+ /// Creates a new MemoryHolder from a Python buffer
84+ /// that will be disposed on a call to <see cref="Dispose"/> or by the finalizer .
7485 /// </summary>
75- public IntPtr UnsafeAddress {
76- get {
77- return _data ;
78- }
86+ public MemoryHolder ( IPythonBuffer buffer , int offset , int size ) {
87+ if ( buffer . IsReadOnly ) throw new ArgumentException ( "Buffer must be writable." ) ;
88+ 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.") ;
92+
93+ _buffer = buffer ;
94+ _handle = buffer . Pin ( ) ;
95+ unsafe { _data = ( IntPtr ) _handle . Pointer + offset ; }
96+ _size = size ;
7997 }
8098
81- public int Size {
82- get {
83- return _size ;
84- }
85- }
99+ /// <summary>
100+ /// Gets the address of the held memory. The caller should ensure the MemoryHolder
101+ /// is always alive and not disposed as long as the address will continue to be accessed.
102+ /// </summary>
103+ public IntPtr UnsafeAddress => ! _disposed ? _data : IntPtr . Zero ;
104+
105+ public int Size => _size ;
86106
87107 /// <summary>
88108 /// Gets a list of objects which need to be kept alive for this MemoryHolder to be
89109 /// remain valid.
90110 /// </summary>
91- public PythonDictionary Objects {
92- get {
93- return _objects ;
94- }
95- set {
96- _objects = value ;
97- }
111+ public PythonDictionary ? Objects {
112+ get => _objects ;
113+ set => _objects = value ;
98114 }
99115
100116 internal PythonDictionary EnsureObjects ( ) {
101- if ( _objects == null ) {
117+ if ( _objects is null ) {
102118 Interlocked . CompareExchange ( ref _objects , new PythonDictionary ( ) , null ) ;
103119 }
104-
105120 return _objects ;
106121 }
107122
@@ -178,15 +193,15 @@ public MemoryHolder ReadMemoryHolder(int offset) {
178193 return new MemoryHolder ( res , IntPtr . Size , this ) ;
179194 }
180195
181- internal Bytes ReadBytes ( int offset ) {
196+ internal Bytes ? ReadBytes ( int offset ) {
182197 try {
183198 return ReadBytes ( _data , offset ) ;
184199 } finally {
185200 GC . KeepAlive ( this ) ;
186201 }
187202 }
188203
189- internal string ReadUnicodeString ( int offset ) {
204+ internal string ? ReadUnicodeString ( int offset ) {
190205 try {
191206 return Marshal . PtrToStringUni ( _data . Add ( offset ) ) ;
192207 } finally {
@@ -214,8 +229,8 @@ internal static Bytes ReadBytes(IntPtr addr, int offset, int length) {
214229 return Bytes . Make ( buffer ) ;
215230 }
216231
217- internal static Bytes ReadBytes ( IntPtr addr , int offset ) {
218- if ( addr == IntPtr . Zero && offset == 0 ) return null ;
232+ internal static Bytes ? ReadBytes ( IntPtr addr , int offset ) {
233+ if ( addr == IntPtr . Zero ) return null ;
219234
220235 // instead of Marshal.PtrToStringAnsi we do this because
221236 // ptrToStringAnsi gives special treatment to values >= 128.
@@ -305,10 +320,30 @@ public void CopyTo(MemoryHolder/*!*/ destAddress, int writeOffset, int size) {
305320 GC . KeepAlive ( this ) ;
306321 }
307322
323+ public void Dispose ( ) {
324+ if ( ! _disposed ) {
325+ _disposed = true ;
326+
327+ if ( _ownsData ) {
328+ Marshal . FreeHGlobal ( _data ) ;
329+ }
330+ _handle . Dispose ( ) ;
331+ _buffer ? . Dispose ( ) ;
332+
333+ GC . SuppressFinalize ( this ) ;
334+ }
335+ }
336+
308337 [ ReliabilityContract ( Consistency . WillNotCorruptState , Cer . Success ) ]
309338 ~ MemoryHolder ( ) {
310- if ( _ownsData ) {
311- Marshal . FreeHGlobal ( _data ) ;
339+ if ( ! _disposed ) {
340+ _disposed = true ;
341+
342+ if ( _ownsData ) {
343+ Marshal . FreeHGlobal ( _data ) ;
344+ }
345+ try { _handle . Dispose ( ) ; } catch { /*ignore*/ }
346+ try { _buffer ? . Dispose ( ) ; } catch { /*ignore*/ }
312347 }
313348 }
314349 }
0 commit comments