Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 5 additions & 2 deletions crates/bindings-csharp/Runtime/AuthCtx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ namespace SpacetimeDB;

public sealed class AuthCtx
{
private static byte[] jwtBuffer = new byte[0x10_000];

private readonly bool _isInternal;
private readonly Lazy<JwtClaims?> _jwtLazy;

Expand Down Expand Up @@ -47,11 +49,12 @@ private static AuthCtx FromConnectionId(ConnectionId connectionId, Identity iden
{
var result = SpacetimeDB.Internal.FFI.get_jwt(ref connectionId, out var source);
SpacetimeDB.Internal.FFI.CheckedStatus.Marshaller.ConvertToManaged(result);
var bytes = SpacetimeDB.Internal.Module.Consume(source);
if (bytes == null || bytes.Length == 0)
using var stream = SpacetimeDB.Internal.Module.Consume(source, ref jwtBuffer);
if (stream.Length == 0)
{
return null;
}
var bytes = stream.ToArray();
var jwt = System.Text.Encoding.UTF8.GetString(bytes);
return jwt != null ? new JwtClaims(jwt, identity) : null;
}
Expand Down
9 changes: 6 additions & 3 deletions crates/bindings-csharp/Runtime/Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ public sealed class HttpError(string message) : Exception(message)
public sealed class HttpClient
{
private static readonly TimeSpan MaxTimeout = TimeSpan.FromMilliseconds(500);
private static byte[] responseWireBuffer = new byte[0x10_000];
private static byte[] responseBodyBuffer = new byte[0x10_000];
private static byte[] errorWireBuffer = new byte[0x10_000];

/// <summary>
/// Send a simple <c>GET</c> request to <paramref name="uri"/> with no headers.
Expand Down Expand Up @@ -341,10 +344,10 @@ out var out_
{
case Errno.OK:
{
var responseWireBytes = out_.A.Consume();
var responseWireBytes = out_.A.Consume(ref responseWireBuffer).ToArray();
var responseWire = FromBytes(new HttpResponseWire.BSATN(), responseWireBytes);

var body = new HttpBody(out_.B.Consume());
var body = new HttpBody(out_.B.Consume(ref responseBodyBuffer).ToArray());
var (statusCode, version, headers) = FromWireResponse(responseWire);

return Result<HttpResponse, HttpError>.Ok(
Expand All @@ -353,7 +356,7 @@ out var out_
}
case Errno.HTTP_ERROR:
{
var errorWireBytes = out_.A.Consume();
var errorWireBytes = out_.A.Consume(ref errorWireBuffer).ToArray();
var err = FromBytes(new SpacetimeDB.BSATN.String(), errorWireBytes);
return Result<HttpResponse, HttpError>.Err(new HttpError(err));
}
Expand Down
5 changes: 5 additions & 0 deletions crates/bindings-csharp/Runtime/Internal/FFI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public enum Errno : short
HTTP_ERROR = 21,
}

internal static class ErrnoExtensions
{
public static void Check(this Errno status) => FFI.ErrnoHelpers.ThrowIfError(status);
}

#pragma warning disable IDE1006 // Naming Styles - Not applicable to FFI stuff.
internal static partial class FFI
{
Expand Down
12 changes: 6 additions & 6 deletions crates/bindings-csharp/Runtime/Internal/IIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ out ReadOnlySpan<byte> rend
}

protected IEnumerable<Row> DoFilter<Bounds>(Bounds bounds)
where Bounds : IBTreeIndexBounds => new RawTableIter<Bounds>(indexId, bounds).Parse();
where Bounds : IBTreeIndexBounds => new RawTableIter<Bounds>(indexId, bounds);

protected uint DoDelete<Bounds>(Bounds bounds)
where Bounds : IBTreeIndexBounds
Expand Down Expand Up @@ -129,7 +129,7 @@ out var numDeleted
new RW().Write(w, key);
var point = s.ToArray();

using var e = new RawPointIter(indexId, point).Parse().GetEnumerator();
using var e = new RawPointIter(indexId, point).GetEnumerator();
if (!e.MoveNext())
{
return null;
Expand Down Expand Up @@ -192,7 +192,7 @@ out var numDeleted
new RW().Write(w, key);
var point = s.ToArray();

using var e = new RawPointIter(indexId, point).Parse().GetEnumerator();
using var e = new RawPointIter(indexId, point).GetEnumerator();
if (!e.MoveNext())
{
return null;
Expand Down Expand Up @@ -241,7 +241,7 @@ protected override void IterStart(out FFI.RowIter handle) =>
new RW().Write(w, key);
var point = s.ToArray();

using var e = new RawPointIter(indexId, point).Parse().GetEnumerator();
using var e = new RawPointIter(indexId, point).GetEnumerator();
if (!e.MoveNext())
{
return null;
Expand Down Expand Up @@ -280,7 +280,7 @@ protected override void IterStart(out FFI.RowIter handle) =>
new RW().Write(w, key);
var point = s.ToArray();

using var e = new RawPointIter(indexId, point).Parse().GetEnumerator();
using var e = new RawPointIter(indexId, point).GetEnumerator();
if (!e.MoveNext())
{
return null;
Expand Down Expand Up @@ -319,5 +319,5 @@ protected ulong DoCount()
return count;
}

protected IEnumerable<Row> DoIter() => new TableIter(tableId).Parse();
protected IEnumerable<Row> DoIter() => new TableIter(tableId);
}
110 changes: 35 additions & 75 deletions crates/bindings-csharp/Runtime/Internal/ITable.cs
Original file line number Diff line number Diff line change
@@ -1,115 +1,75 @@
namespace SpacetimeDB.Internal;

using System.Buffers;
using System.Collections;
using SpacetimeDB.BSATN;

internal abstract class RawTableIterBase<T>
internal abstract class RawTableIterBase<T> : IEnumerable<T>
where T : IStructuralReadWrite, new()
{
public sealed class Enumerator(FFI.RowIter handle) : IDisposable
{
private const int InitialBufferSize = 1024;
private byte[]? buffer = ArrayPool<byte>.Shared.Rent(InitialBufferSize);
public ArraySegment<byte> Current { get; private set; } = ArraySegment<byte>.Empty;

public bool MoveNext()
{
if (handle == FFI.RowIter.INVALID)
{
return false;
}
private const int InitialBufferSize = 1024;

if (buffer is null)
{
return false;
}
protected abstract void IterStart(out FFI.RowIter handle);

uint buffer_len;
while (true)
public IEnumerator<T> GetEnumerator()
{
IterStart(out var handle);
var buffer = ArrayPool<byte>.Shared.Rent(InitialBufferSize);
try
{
while (handle != FFI.RowIter.INVALID)
{
var requested_len = (uint)buffer.Length;
buffer_len = requested_len;
var buffer_len = requested_len;
var ret = FFI.row_iter_bsatn_advance(handle, buffer, ref buffer_len);
if (ret == Errno.EXHAUSTED)
{
handle = FFI.RowIter.INVALID;
}

// On success, the only way `buffer_len == 0` is for the iterator to be exhausted.
// This happens when the host iterator was empty from the start.
System.Diagnostics.Debug.Assert(!(ret == Errno.OK && buffer_len == 0));
switch (ret)
{
// 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, update `Current` to point at the valid range in the scratch `buffer`.
case Errno.EXHAUSTED
or Errno.OK:
Current = new ArraySegment<byte>(buffer, 0, (int)buffer_len);
return buffer_len != 0;
// Couldn't find the iterator, error!
case Errno.NO_SUCH_ITER:
throw new NoSuchIterException();
// The scratch `buffer` is too small to fit a row / chunk.
// Grow `buffer` and try again.
// The `buffer_len` will have been updated with the necessary size.
{
using var stream = new MemoryStream(
buffer,
0,
(int)buffer_len,
writable: false,
publiclyVisible: true
);
using var reader = new BinaryReader(stream);
while (stream.Position < stream.Length)
{
yield return IStructuralReadWrite.Read<T>(reader);
}
break;
}
case Errno.BUFFER_TOO_SMALL:
ArrayPool<byte>.Shared.Return(buffer);
buffer = ArrayPool<byte>.Shared.Rent((int)buffer_len);
continue;
break;
default:
throw new UnknownException(ret);
ret.Check();
break;
}
}
}

public void Dispose()
finally
{
if (handle != FFI.RowIter.INVALID)
{
FFI.row_iter_bsatn_close(handle);
handle = FFI.RowIter.INVALID;
}

if (buffer is not null)
{
ArrayPool<byte>.Shared.Return(buffer);
buffer = null;
}
}

public void Reset()
{
throw new NotImplementedException();
ArrayPool<byte>.Shared.Return(buffer);
}
}

protected abstract void IterStart(out FFI.RowIter handle);

// Note: using the GetEnumerator() duck-typing protocol instead of IEnumerable to avoid extra boxing.
public Enumerator GetEnumerator()
{
IterStart(out var handle);
return new(handle);
}

public IEnumerable<T> Parse()
{
foreach (var chunk in this)
{
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)
{
yield return IStructuralReadWrite.Read<T>(reader);
}
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public interface ITableView<View, T>
Expand Down Expand Up @@ -162,7 +122,7 @@ protected static ulong DoCount()
return count;
}

protected static IEnumerable<T> DoIter() => new RawTableIter(tableId).Parse();
protected static IEnumerable<T> DoIter() => new RawTableIter(tableId);

protected static T DoInsert(T row)
{
Expand Down
Loading
Loading