From 51268aaa0f54fa705538104fac99e6e75e119485 Mon Sep 17 00:00:00 2001 From: rekhoff Date: Fri, 20 Feb 2026 15:36:53 -0800 Subject: [PATCH 1/3] Update RawTableIterBase to use `ArrayPool` for buffer --- .../bindings-csharp/Runtime/Internal/ITable.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/bindings-csharp/Runtime/Internal/ITable.cs b/crates/bindings-csharp/Runtime/Internal/ITable.cs index ed48106ef44..26ad780a1bd 100644 --- a/crates/bindings-csharp/Runtime/Internal/ITable.cs +++ b/crates/bindings-csharp/Runtime/Internal/ITable.cs @@ -1,14 +1,16 @@ namespace SpacetimeDB.Internal; using SpacetimeDB.BSATN; +using System.Buffers; internal abstract class RawTableIterBase where T : IStructuralReadWrite, new() { public sealed class Enumerator(FFI.RowIter handle) : IDisposable { - byte[] buffer = new byte[0x20_000]; - public byte[] Current { get; private set; } = []; + private const int InitialBufferSize = 1024; + private byte[] buffer = ArrayPool.Shared.Rent(InitialBufferSize); + public ArraySegment Current { get; private set; } = ArraySegment.Empty; public bool MoveNext() { @@ -38,11 +40,10 @@ public bool MoveNext() { // Iterator advanced and may also be `EXHAUSTED`. // When `OK`, we'll need to advance the iterator in the next call to `MoveNext`. - // In both cases, copy over the row data to `Current` from the scratch `buffer`. + // In both cases, update `Current` to point at the valid range in the scratch `buffer`. case Errno.EXHAUSTED or Errno.OK: - Current = new byte[buffer_len]; - Array.Copy(buffer, 0, Current, 0, buffer_len); + Current = new ArraySegment(buffer, 0, (int)buffer_len); return buffer_len != 0; // Couldn't find the iterator, error! case Errno.NO_SUCH_ITER: @@ -51,7 +52,8 @@ public bool MoveNext() // Grow `buffer` and try again. // The `buffer_len` will have been updated with the necessary size. case Errno.BUFFER_TOO_SMALL: - buffer = new byte[buffer_len]; + ArrayPool.Shared.Return(buffer); + buffer = ArrayPool.Shared.Rent((int)buffer_len); continue; default: throw new UnknownException(ret); @@ -66,6 +68,8 @@ public void Dispose() FFI.row_iter_bsatn_close(handle); handle = FFI.RowIter.INVALID; } + + ArrayPool.Shared.Return(buffer); } public void Reset() @@ -87,7 +91,7 @@ public IEnumerable Parse() { foreach (var chunk in this) { - using var stream = new MemoryStream(chunk); + using var stream = new MemoryStream(chunk.Array!, chunk.Offset, chunk.Count, writable: false, publiclyVisible: true); using var reader = new BinaryReader(stream); while (stream.Position < stream.Length) { From 7cc642b77361cce121b460bf84811e7cad596996 Mon Sep 17 00:00:00 2001 From: rekhoff Date: Fri, 20 Feb 2026 15:55:10 -0800 Subject: [PATCH 2/3] Updated lints --- crates/bindings-csharp/Runtime/Internal/ITable.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/bindings-csharp/Runtime/Internal/ITable.cs b/crates/bindings-csharp/Runtime/Internal/ITable.cs index 26ad780a1bd..dd68a9b553e 100644 --- a/crates/bindings-csharp/Runtime/Internal/ITable.cs +++ b/crates/bindings-csharp/Runtime/Internal/ITable.cs @@ -1,7 +1,7 @@ namespace SpacetimeDB.Internal; -using SpacetimeDB.BSATN; using System.Buffers; +using SpacetimeDB.BSATN; internal abstract class RawTableIterBase where T : IStructuralReadWrite, new() @@ -91,7 +91,13 @@ public IEnumerable Parse() { foreach (var chunk in this) { - using var stream = new MemoryStream(chunk.Array!, chunk.Offset, chunk.Count, writable: false, publiclyVisible: true); + using var stream = new MemoryStream( + chunk.Array!, + chunk.Offset, + chunk.Count, + writable: false, + publiclyVisible: true + ); using var reader = new BinaryReader(stream); while (stream.Position < stream.Length) { From 90746bd3f70824f0dbf671cd264382d18efd14d4 Mon Sep 17 00:00:00 2001 From: rekhoff Date: Sat, 21 Feb 2026 12:48:25 -0800 Subject: [PATCH 3/3] Makes buffer nullable preventing double Dispose issue --- crates/bindings-csharp/Runtime/Internal/ITable.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/bindings-csharp/Runtime/Internal/ITable.cs b/crates/bindings-csharp/Runtime/Internal/ITable.cs index dd68a9b553e..4878de71762 100644 --- a/crates/bindings-csharp/Runtime/Internal/ITable.cs +++ b/crates/bindings-csharp/Runtime/Internal/ITable.cs @@ -9,7 +9,7 @@ internal abstract class RawTableIterBase public sealed class Enumerator(FFI.RowIter handle) : IDisposable { private const int InitialBufferSize = 1024; - private byte[] buffer = ArrayPool.Shared.Rent(InitialBufferSize); + private byte[]? buffer = ArrayPool.Shared.Rent(InitialBufferSize); public ArraySegment Current { get; private set; } = ArraySegment.Empty; public bool MoveNext() @@ -19,6 +19,11 @@ public bool MoveNext() return false; } + if (buffer is null) + { + return false; + } + uint buffer_len; while (true) { @@ -69,7 +74,11 @@ public void Dispose() handle = FFI.RowIter.INVALID; } - ArrayPool.Shared.Return(buffer); + if (buffer is not null) + { + ArrayPool.Shared.Return(buffer); + buffer = null; + } } public void Reset()