From 455dcd8f9e1d05ca454a8445f80bcb4e3d27434f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 12 Jul 2026 10:38:45 +0100 Subject: [PATCH] Report CultNet failures during connection --- src/GameCult.Mesh/CultMeshSnapshots.cs | 16 +++++++++-- .../CultMeshStreamingTests.cs | 27 +++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/GameCult.Mesh/CultMeshSnapshots.cs b/src/GameCult.Mesh/CultMeshSnapshots.cs index 939f33f1..3364e2a3 100644 --- a/src/GameCult.Mesh/CultMeshSnapshots.cs +++ b/src/GameCult.Mesh/CultMeshSnapshots.cs @@ -764,7 +764,11 @@ namespace GameCult.Mesh var (host, port) = CultNetSchemaWriteForwarder.ParseEndpoint(endpoint); client.Connect(host, port); - await WaitForSnapshotClientConnectionAsync(client, endpoint, resolvedOptions.ConnectTimeout) + await WaitForSnapshotClientConnectionAsync( + client, + endpoint, + resolvedOptions.ConnectTimeout, + (client as ICultNetSchemaClientHealth)?.BackgroundFailure) .ConfigureAwait(false); client.SendCultNet(new CultNetSnapshotRequestMessage { @@ -871,11 +875,19 @@ namespace GameCult.Mesh internal static async Task WaitForSnapshotClientConnectionAsync( ICultNetSchemaClient client, string endpoint, - TimeSpan timeout) + TimeSpan timeout, + Task? backgroundFailure = null) { var deadline = DateTimeOffset.UtcNow + timeout; while (!client.Connected) { + if (backgroundFailure?.IsCompleted == true) + { + var error = await backgroundFailure.ConfigureAwait(false); + throw new InvalidOperationException( + $"CultNet snapshot client failed while connecting to {endpoint}.", + error); + } if (DateTimeOffset.UtcNow >= deadline) throw new TimeoutException($"Timed out connecting to CultNet snapshot endpoint {endpoint}."); diff --git a/tests/GameCult.Mesh.Tests/CultMeshStreamingTests.cs b/tests/GameCult.Mesh.Tests/CultMeshStreamingTests.cs index 5062d0e0..46116d72 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshStreamingTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshStreamingTests.cs @@ -2674,6 +2674,26 @@ public sealed class CultMeshStreamingTests failure.Which.Message.Should().Contain("127.0.0.1:3075"); } + [Test] + public async Task SnapshotHelpers_SurfaceBackgroundClientFailureWhileConnecting() + { + var options = new CultMeshSnapshotRequestOptions + { + ConnectTimeout = TimeSpan.FromSeconds(30), + CreateClient = () => new FailedBackgroundSchemaClient( + new FormatException("malformed handshake"), + connected: false) + }; + + Func act = async () => await CultMesh.FetchSnapshotAsync( + "rudp://127.0.0.1:3075", + options); + + var failure = await act.Should().ThrowAsync(); + failure.Which.InnerException.Should().BeOfType(); + failure.Which.Message.Should().Contain("connecting"); + } + [Test] public async Task SnapshotHelpers_DecodeSameSchemaAliasFromForeignSchemaId() { @@ -3838,15 +3858,18 @@ internal sealed class MeshSnapshotSchemaClient : ICultNetSchemaClient internal sealed class FailedBackgroundSchemaClient : ICultNetSchemaClient, ICultNetSchemaClientHealth { - public FailedBackgroundSchemaClient(Exception error) + private readonly bool _connected; + + public FailedBackgroundSchemaClient(Exception error, bool connected = true) { + _connected = connected; BackgroundFailure = Task.FromResult(error); } public bool Connected { get; private set; } public Task BackgroundFailure { get; } - public void Connect(string host, int port) => Connected = true; + public void Connect(string host, int port) => Connected = _connected; public void SendCultNet(T message) where T : ICultNetSchemaMessage {