Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions src/Temporalio/Bridge/ByteArrayRef.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Temporalio.Bridge
/// Representation of a byte array owned by .NET. Users should usually use a
/// <see cref="Scope" /> instead of creating this directly.
/// </summary>
internal class ByteArrayRef
internal sealed class ByteArrayRef : IDisposable
{
private readonly GCHandle bytesHandle;

Expand Down Expand Up @@ -42,18 +42,10 @@ public ByteArrayRef(byte[] bytes, int length)
}
}

/// <summary>
/// Finalizes an instance of the <see cref="ByteArrayRef"/> class.
/// </summary>
~ByteArrayRef()
{
bytesHandle.Free();
}

/// <summary>
/// Gets empty byte array.
/// </summary>
public static ByteArrayRef Empty { get; } = new(Array.Empty<byte>());
public static ByteArrayRef Empty { get; } = new(Array.Empty<byte>()) { DisableDispose = true };

/// <summary>
/// Gets current byte array for this ref.
Expand All @@ -70,6 +62,8 @@ public ByteArrayRef(byte[] bytes, int length)
/// </summary>
internal static UTF8Encoding StrictUTF8 { get; } = new(false, true);

private bool DisableDispose { get; set; }

/// <summary>
/// Convert a string to a UTF-8 byte array.
/// </summary>
Expand Down Expand Up @@ -210,5 +204,20 @@ public static ByteArrayRef FromNewlineDelimited(IEnumerable<string> values)
return new ByteArrayRef(stream.GetBuffer(), (int)stream.Length);
}
}

/// <inheritdoc/>
public void Dispose()
{
// Note, we intentionally do not free or call Dispose from a finalizer. When working
// with native objects, best practice is to not free in finalizer. On process shutdown,
// finalizers can be called even when the object is still referenced.

// Does not need to be thread safe
if (!DisableDispose)
{
DisableDispose = true;
bytesHandle.Free();
}
}
}
}
39 changes: 11 additions & 28 deletions src/Temporalio/Bridge/Scope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,10 @@ internal sealed class Scope : IDisposable
size = UIntPtr.Zero,
};

private readonly List<ByteArrayRef> byteArrayRefs = new();
private readonly List<GCHandle> gcHandles = new();
private readonly List<IDisposable> disposables = new();
private bool disposed;

/// <summary>
/// Finalizes an instance of the <see cref="Scope"/> class.
/// </summary>
~Scope() => Dispose(false);

/// <summary>
/// Create a byte array ref.
/// </summary>
Expand All @@ -39,7 +33,7 @@ public Interop.TemporalCoreByteArrayRef ByteArray(byte[]? bytes)
return ByteArrayRef.Empty.Ref;
}
var val = new ByteArrayRef(bytes);
byteArrayRefs.Add(val);
disposables.Add(val);
return val.Ref;
}

Expand All @@ -55,7 +49,7 @@ public Interop.TemporalCoreByteArrayRef ByteArray(string? str)
return ByteArrayRef.Empty.Ref;
}
var val = ByteArrayRef.FromUTF8(str);
byteArrayRefs.Add(val);
disposables.Add(val);
return val.Ref;
}

Expand All @@ -67,7 +61,7 @@ public Interop.TemporalCoreByteArrayRef ByteArray(string? str)
public Interop.TemporalCoreByteArrayRef ByteArray(KeyValuePair<string, string> pair)
{
var val = ByteArrayRef.FromKeyValuePair(pair);
byteArrayRefs.Add(val);
disposables.Add(val);
return val.Ref;
}

Expand All @@ -79,7 +73,7 @@ public Interop.TemporalCoreByteArrayRef ByteArray(KeyValuePair<string, string> p
public Interop.TemporalCoreByteArrayRef ByteArray(KeyValuePair<string, byte[]> pair)
{
var val = ByteArrayRef.FromKeyValuePair(pair);
byteArrayRefs.Add(val);
disposables.Add(val);
return val.Ref;
}

Expand All @@ -95,7 +89,7 @@ public Interop.TemporalCoreByteArrayRef NewlineDelimited(IReadOnlyCollection<str
return ByteArrayRef.Empty.Ref;
}
var val = ByteArrayRef.FromNewlineDelimited(values);
byteArrayRefs.Add(val);
disposables.Add(val);
return val.Ref;
}

Expand All @@ -111,7 +105,7 @@ public Interop.TemporalCoreByteArrayRef NewlineDelimited(IReadOnlyCollection<Key
return ByteArrayRef.Empty.Ref;
}
var val = ByteArrayRef.FromNewlineDelimited(values);
byteArrayRefs.Add(val);
disposables.Add(val);
return val.Ref;
}

Expand Down Expand Up @@ -249,34 +243,23 @@ public IntPtr FunctionPointer<T>(T func)
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

private void Dispose(bool disposing)
{
// Note, we intentionally do not free or call Dispose from a finalizer. When working
// with native objects, best practice is to not free in finalizer. On process shutdown,
// finalizers can be called even when the object is still referenced.
if (disposed)
{
return;
}

byteArrayRefs.Clear();

foreach (var handle in gcHandles)
{
handle.Free();
}
gcHandles.Clear();

if (disposing)
foreach (var disposable in disposables)
{
foreach (var disposable in disposables)
{
disposable.Dispose();
}
disposable.Dispose();
}
disposables.Clear();

disposed = true;
}
}
Expand Down
Loading