Skip to content

Commit ac3130c

Browse files
committed
Implement SafeHandle
1 parent 202a38c commit ac3130c

File tree

3 files changed

+188
-108
lines changed

3 files changed

+188
-108
lines changed

LLama/Native/SafeMtmdEmbed.cs

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,25 @@ namespace LLama.Native
88
/// Managed wrapper around <c>mtmd_bitmap*</c> resources. Instances own the native pointer
99
/// and ensure proper cleanup when disposed.
1010
/// </summary>
11-
public sealed class SafeMtmdEmbed : IDisposable
11+
public sealed class SafeMtmdEmbed : SafeLLamaHandleBase
1212
{
1313
/// <summary>
1414
/// Raw pointer to the native bitmap structure. Internal so other wrappers can interop.
1515
/// </summary>
16-
internal IntPtr NativePtr { get; private set; }
17-
18-
private bool _disposed;
16+
internal IntPtr NativePtr
17+
{
18+
get
19+
{
20+
EnsureNotDisposed();
21+
return DangerousGetHandle();
22+
}
23+
}
1924

2025
private SafeMtmdEmbed(IntPtr ptr)
26+
: base(ptr, ownsHandle: true)
2127
{
22-
NativePtr = ptr != IntPtr.Zero
23-
? ptr
24-
: throw new InvalidOperationException("Failed to create MTMD bitmap.");
28+
if (IsInvalid)
29+
throw new InvalidOperationException("Failed to create MTMD bitmap.");
2530
}
2631

2732
/// <summary>
@@ -154,8 +159,7 @@ public uint Nx
154159
{
155160
get
156161
{
157-
EnsureNotDisposed();
158-
return NativeApi.mtmd_bitmap_get_nx(NativePtr);
162+
return WithHandle(ptr => NativeApi.mtmd_bitmap_get_nx(ptr));
159163
}
160164
}
161165

@@ -166,8 +170,7 @@ public uint Ny
166170
{
167171
get
168172
{
169-
EnsureNotDisposed();
170-
return NativeApi.mtmd_bitmap_get_ny(NativePtr);
173+
return WithHandle(ptr => NativeApi.mtmd_bitmap_get_ny(ptr));
171174
}
172175
}
173176

@@ -178,8 +181,7 @@ public bool IsAudio
178181
{
179182
get
180183
{
181-
EnsureNotDisposed();
182-
return NativeApi.mtmd_bitmap_is_audio(NativePtr);
184+
return WithHandle(ptr => NativeApi.mtmd_bitmap_is_audio(ptr));
183185
}
184186
}
185187

@@ -190,14 +192,19 @@ public string? Id
190192
{
191193
get
192194
{
193-
EnsureNotDisposed();
194-
var ptr = NativeApi.mtmd_bitmap_get_id(NativePtr);
195-
return NativeApi.PtrToStringUtf8(ptr);
195+
return WithHandle(ptr =>
196+
{
197+
var idPtr = NativeApi.mtmd_bitmap_get_id(ptr);
198+
return NativeApi.PtrToStringUtf8(idPtr);
199+
});
196200
}
197201
set
198202
{
199-
EnsureNotDisposed();
200-
NativeApi.mtmd_bitmap_set_id(NativePtr, value);
203+
WithHandle(ptr =>
204+
{
205+
NativeApi.mtmd_bitmap_set_id(ptr, value);
206+
return 0;
207+
});
201208
}
202209
}
203210

@@ -210,38 +217,63 @@ public unsafe ReadOnlySpan<byte> GetDataSpan()
210217
{
211218
EnsureNotDisposed();
212219

213-
var dataPtr = (byte*)NativeApi.mtmd_bitmap_get_data(NativePtr);
214-
var length = checked((int)NativeApi.mtmd_bitmap_get_n_bytes(NativePtr).ToUInt64());
215-
return dataPtr == null || length == 0 ? ReadOnlySpan<byte>.Empty : new ReadOnlySpan<byte>(dataPtr, length);
220+
bool added = false;
221+
try
222+
{
223+
DangerousAddRef(ref added);
224+
var ptr = DangerousGetHandle();
225+
var dataPtr = (byte*)NativeApi.mtmd_bitmap_get_data(ptr);
226+
var length = checked((int)NativeApi.mtmd_bitmap_get_n_bytes(ptr).ToUInt64());
227+
return dataPtr == null || length == 0
228+
? ReadOnlySpan<byte>.Empty
229+
: new ReadOnlySpan<byte>(dataPtr, length);
230+
}
231+
finally
232+
{
233+
if (added)
234+
DangerousRelease();
235+
}
216236
}
217237

218238
/// <summary>
219239
/// Release the underlying native bitmap.
220240
/// </summary>
221-
public void Dispose()
241+
protected override bool ReleaseHandle()
222242
{
223-
if (_disposed)
224-
return;
225-
226-
if (NativePtr != IntPtr.Zero)
243+
if (handle != IntPtr.Zero)
227244
{
228-
NativeApi.mtmd_bitmap_free(NativePtr);
229-
NativePtr = IntPtr.Zero;
245+
NativeApi.mtmd_bitmap_free(handle);
246+
SetHandle(IntPtr.Zero);
230247
}
231248

232-
_disposed = true;
233-
GC.SuppressFinalize(this);
249+
return true;
234250
}
235251

236-
/// <summary>
237-
/// Finalizer to ensure native resources are reclaimed when Dispose is not invoked.
238-
/// </summary>
239-
~SafeMtmdEmbed() => Dispose();
240-
241252
private void EnsureNotDisposed()
242253
{
243-
if (_disposed || NativePtr == IntPtr.Zero)
254+
if (IsClosed || IsInvalid)
244255
throw new ObjectDisposedException(nameof(SafeMtmdEmbed));
245256
}
257+
258+
private T WithHandle<T>(Func<IntPtr, T> action)
259+
{
260+
EnsureNotDisposed();
261+
262+
bool added = false;
263+
try
264+
{
265+
DangerousAddRef(ref added);
266+
var ptr = DangerousGetHandle();
267+
if (ptr == IntPtr.Zero)
268+
throw new ObjectDisposedException(nameof(SafeMtmdEmbed));
269+
270+
return action(ptr);
271+
}
272+
finally
273+
{
274+
if (added)
275+
DangerousRelease();
276+
}
277+
}
246278
}
247279
}

LLama/Native/SafeMtmdInputChunk.cs

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace LLama.Native;
88
/// underlying native pointer (when created via <see cref="Copy"/>) or act as non-owning views
99
/// produced by the tokenizer.
1010
/// </summary>
11-
public sealed class SafeMtmdInputChunk : IDisposable
11+
public sealed class SafeMtmdInputChunk : SafeLLamaHandleBase
1212
{
1313
/// <summary>
1414
/// Chunk modality returned by the native tokenizer.
@@ -23,15 +23,18 @@ public enum SafeMtmdInputChunkType
2323
/// <summary>
2424
/// Raw pointer to the native chunk structure.
2525
/// </summary>
26-
public IntPtr NativePtr { get; private set; }
27-
28-
private bool _ownsPtr;
29-
private bool _disposed;
26+
public IntPtr NativePtr
27+
{
28+
get
29+
{
30+
EnsureNotDisposed();
31+
return DangerousGetHandle();
32+
}
33+
}
3034

31-
private SafeMtmdInputChunk(IntPtr ptr, bool owns)
35+
private SafeMtmdInputChunk(IntPtr handle, bool ownsHandle)
36+
: base(handle, ownsHandle)
3237
{
33-
NativePtr = ptr;
34-
_ownsPtr = owns;
3538
}
3639

3740
/// <summary>
@@ -40,7 +43,7 @@ private SafeMtmdInputChunk(IntPtr ptr, bool owns)
4043
/// <param name="ptr">Pointer returned by the native tokenizer.</param>
4144
/// <returns>Managed wrapper, or <c>null</c> when the pointer is null.</returns>
4245
public static SafeMtmdInputChunk Wrap(IntPtr ptr)
43-
=> ptr == IntPtr.Zero ? null : new SafeMtmdInputChunk(ptr, false);
46+
=> ptr == IntPtr.Zero ? null : new SafeMtmdInputChunk(ptr, ownsHandle: false);
4447

4548
/// <summary>
4649
/// Create an owning copy of the current chunk. The caller becomes responsible for disposal.
@@ -49,10 +52,11 @@ public static SafeMtmdInputChunk Wrap(IntPtr ptr)
4952
/// <exception cref="ObjectDisposedException">Thrown when the current wrapper has been disposed.</exception>
5053
public SafeMtmdInputChunk Copy()
5154
{
52-
EnsureNotDisposed();
53-
54-
var p = NativeApi.mtmd_input_chunk_copy(NativePtr);
55-
return p == IntPtr.Zero ? null : new SafeMtmdInputChunk(p, true);
55+
return WithHandle(ptr =>
56+
{
57+
var clone = NativeApi.mtmd_input_chunk_copy(ptr);
58+
return clone == IntPtr.Zero ? null : new SafeMtmdInputChunk(clone, ownsHandle: true);
59+
});
5660
}
5761

5862
/// <summary>
@@ -62,8 +66,7 @@ public SafeMtmdInputChunkType Type
6266
{
6367
get
6468
{
65-
EnsureNotDisposed();
66-
return (SafeMtmdInputChunkType)NativeApi.mtmd_input_chunk_get_type(NativePtr);
69+
return WithHandle(ptr => (SafeMtmdInputChunkType)NativeApi.mtmd_input_chunk_get_type(ptr));
6770
}
6871
}
6972

@@ -74,8 +77,7 @@ public ulong NTokens
7477
{
7578
get
7679
{
77-
EnsureNotDisposed();
78-
return NativeApi.mtmd_input_chunk_get_n_tokens(NativePtr).ToUInt64();
80+
return WithHandle(ptr => NativeApi.mtmd_input_chunk_get_n_tokens(ptr).ToUInt64());
7981
}
8082
}
8183

@@ -86,8 +88,11 @@ public string Id
8688
{
8789
get
8890
{
89-
EnsureNotDisposed();
90-
return Marshal.PtrToStringAnsi(NativeApi.mtmd_input_chunk_get_id(NativePtr)) ?? string.Empty;
91+
return WithHandle(ptr =>
92+
{
93+
var idPtr = NativeApi.mtmd_input_chunk_get_id(ptr);
94+
return Marshal.PtrToStringAnsi(idPtr) ?? string.Empty;
95+
});
9196
}
9297
}
9398

@@ -98,8 +103,7 @@ public long NPos
98103
{
99104
get
100105
{
101-
EnsureNotDisposed();
102-
return NativeApi.mtmd_input_chunk_get_n_pos(NativePtr);
106+
return WithHandle(ptr => NativeApi.mtmd_input_chunk_get_n_pos(ptr));
103107
}
104108
}
105109

@@ -112,39 +116,63 @@ public unsafe ReadOnlySpan<uint> GetTextTokensSpan()
112116
{
113117
EnsureNotDisposed();
114118

115-
UIntPtr n;
116-
var p = (uint*)NativeApi.mtmd_input_chunk_get_tokens_text(NativePtr, out n);
117-
return p == null ? ReadOnlySpan<uint>.Empty : new ReadOnlySpan<uint>(p, checked((int)n.ToUInt64()));
119+
bool added = false;
120+
try
121+
{
122+
DangerousAddRef(ref added);
123+
UIntPtr nTokens;
124+
var tokensPtr = (uint*)NativeApi.mtmd_input_chunk_get_tokens_text(DangerousGetHandle(), out nTokens);
125+
if (tokensPtr == null)
126+
return ReadOnlySpan<uint>.Empty;
127+
128+
var length = checked((int)nTokens.ToUInt64());
129+
return new ReadOnlySpan<uint>(tokensPtr, length);
130+
}
131+
finally
132+
{
133+
if (added)
134+
DangerousRelease();
135+
}
118136
}
119137

120138
/// <summary>
121-
/// Release the underlying native resources if this instance owns them.
139+
/// Releases the native chunk when ownership is held by this instance.
122140
/// </summary>
123-
public void Dispose()
141+
protected override bool ReleaseHandle()
124142
{
125-
if (_disposed)
126-
return;
127-
128-
if (_ownsPtr && NativePtr != IntPtr.Zero)
143+
if (handle != IntPtr.Zero)
129144
{
130-
NativeApi.mtmd_input_chunk_free(NativePtr);
145+
NativeApi.mtmd_input_chunk_free(handle);
146+
SetHandle(IntPtr.Zero);
131147
}
132148

133-
NativePtr = IntPtr.Zero;
134-
_ownsPtr = false;
135-
_disposed = true;
136-
137-
GC.SuppressFinalize(this);
149+
return true;
138150
}
139151

140-
/// <summary>
141-
/// Finalizer to ensure native memory is reclaimed when Dispose is not called by owners.
142-
/// </summary>
143-
~SafeMtmdInputChunk() => Dispose();
144-
145152
private void EnsureNotDisposed()
146153
{
147-
if (_disposed || NativePtr == IntPtr.Zero)
154+
if (IsClosed || IsInvalid)
148155
throw new ObjectDisposedException(nameof(SafeMtmdInputChunk));
149156
}
157+
158+
private T WithHandle<T>(Func<IntPtr, T> action)
159+
{
160+
EnsureNotDisposed();
161+
162+
bool added = false;
163+
try
164+
{
165+
DangerousAddRef(ref added);
166+
var ptr = DangerousGetHandle();
167+
if (ptr == IntPtr.Zero)
168+
throw new ObjectDisposedException(nameof(SafeMtmdInputChunk));
169+
170+
return action(ptr);
171+
}
172+
finally
173+
{
174+
if (added)
175+
DangerousRelease();
176+
}
177+
}
150178
}

0 commit comments

Comments
 (0)