Files
Assistant/backend.tests/ScriptedRealtimeUpstream.cs
T
tes 1a36c8096d Diagnostics: surface upstream WS close reason + break the null-spin loop
The Pi journal showed 170 000 'upstream silent NxN' messages within one
second — i.e. ReceiveJsonAsync was returning null in zero time, so the
upstream WS to OpenAI was closed. The relay was spinning until idle
watchdog (30 s) fired. Add IRealtimeUpstream.IsClosed + CloseReason,
populated by OpenAIRealtimeUpstream from the close status / WS exception,
and have PumpAsync stop on close and forward
  error code=upstream_closed message=close status=<X> desc=<Y>
to the device. Closes the spin AND gives us the OpenAI close reason.
2026-06-12 07:17:04 +00:00

56 lines
1.7 KiB
C#

using System.Text.Json.Nodes;
using System.Threading.Channels;
using backend.Realtime;
namespace backend.tests;
public class ScriptedRealtimeUpstream : IRealtimeUpstream
{
private readonly Channel<JsonObject> _toRelay =
Channel.CreateUnbounded<JsonObject>(new UnboundedChannelOptions { SingleReader = true });
private readonly Channel<JsonObject> _fromRelay =
Channel.CreateUnbounded<JsonObject>(new UnboundedChannelOptions { SingleWriter = true });
public IList<JsonObject> Sent { get; } = new List<JsonObject>();
public bool IsClosed => _toRelay.Reader.Completion.IsCompleted;
public string? CloseReason => IsClosed ? "scripted upstream closed" : null;
public Task SendJsonAsync(JsonObject envelope, CancellationToken ct)
{
Sent.Add(envelope);
return _fromRelay.Writer.WriteAsync(envelope, ct).AsTask();
}
public async Task<JsonObject?> ReceiveJsonAsync(CancellationToken ct)
{
try
{
return await _toRelay.Reader.ReadAsync(ct);
}
catch (ChannelClosedException)
{
return null;
}
}
public void Push(JsonObject evt) => _toRelay.Writer.TryWrite(evt);
public void CloseUpstream() => _toRelay.Writer.TryComplete();
public async Task<JsonObject> WaitForSentAsync(string type, CancellationToken ct)
{
await foreach (var item in _fromRelay.Reader.ReadAllAsync(ct))
{
if ((string?)item["type"] == type) return item;
}
throw new TimeoutException($"never saw outbound {type}");
}
public ValueTask DisposeAsync()
{
_toRelay.Writer.TryComplete();
_fromRelay.Writer.TryComplete();
return ValueTask.CompletedTask;
}
}