From 01d725f20c715e84669b0591f4947081212b918f Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 14 May 2026 14:19:52 +0100 Subject: [PATCH 1/2] test: widen connect timeouts to fix Windows CI flaps Default ConnectTimeout (2s) is too tight on contended Windows runners. Bump to 30s in the tests that have flapped on Windows non-net8.0 (PingCancellationTest, Utf8SubjectMockServerTest, ProtocolParserSizeCheckTest, TlsPreferTest, PinnedClientTest.Pin_id_from_headers, CancellationTokenTests.NextAsync). Widen the TlsPreferTest exception fallback to also accept TimeoutException as evidence of a TLS attempt (otherwise a stalled handshake under contention slips through). Bump NatsUtils.ConnectRetryAsync overall budget 30s -> 60s for the same reason. --- tests/NATS.Client.Core2.Tests/PingCancellationTest.cs | 2 +- .../NATS.Client.Core2.Tests/ProtocolParserSizeCheckTest.cs | 2 +- tests/NATS.Client.Core2.Tests/TlsPreferTest.cs | 6 +++++- tests/NATS.Client.Core2.Tests/Utf8SubjectTest.cs | 2 +- tests/NATS.Client.JetStream.Tests/CancellationTokenTests.cs | 2 +- tests/NATS.Client.JetStream.Tests/PinnedClientTest.cs | 2 +- tests/NATS.Client.TestUtilities/NatsUtils.cs | 2 +- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/NATS.Client.Core2.Tests/PingCancellationTest.cs b/tests/NATS.Client.Core2.Tests/PingCancellationTest.cs index 81e8d823b..7fa5ac028 100644 --- a/tests/NATS.Client.Core2.Tests/PingCancellationTest.cs +++ b/tests/NATS.Client.Core2.Tests/PingCancellationTest.cs @@ -79,7 +79,7 @@ public async Task PingAsync_throws_when_cancelled_waiting_for_pong() cancellationToken: cts.Token); await server.Ready; - await using var nats = new NatsConnection(new NatsOpts { Url = server.Url }); + await using var nats = new NatsConnection(new NatsOpts { Url = server.Url, ConnectTimeout = TimeSpan.FromSeconds(30) }); await nats.ConnectRetryAsync(); // This ping should time out because the server won't reply PONG diff --git a/tests/NATS.Client.Core2.Tests/ProtocolParserSizeCheckTest.cs b/tests/NATS.Client.Core2.Tests/ProtocolParserSizeCheckTest.cs index 099ba7400..35261ee52 100644 --- a/tests/NATS.Client.Core2.Tests/ProtocolParserSizeCheckTest.cs +++ b/tests/NATS.Client.Core2.Tests/ProtocolParserSizeCheckTest.cs @@ -24,7 +24,7 @@ public async Task Msg_with_payload_exceeding_max_payload_does_not_oom() await using var server = new FakeServer(output); await server.Ready; - await using var nats = new NatsConnection(new NatsOpts { Url = server.Url, LoggerFactory = logFactory, ConnectTimeout = TimeSpan.FromSeconds(10) }); + await using var nats = new NatsConnection(new NatsOpts { Url = server.Url, LoggerFactory = logFactory, ConnectTimeout = TimeSpan.FromSeconds(30) }); await nats.ConnectAsync(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); diff --git a/tests/NATS.Client.Core2.Tests/TlsPreferTest.cs b/tests/NATS.Client.Core2.Tests/TlsPreferTest.cs index 746e0f707..7fa4c73d1 100644 --- a/tests/NATS.Client.Core2.Tests/TlsPreferTest.cs +++ b/tests/NATS.Client.Core2.Tests/TlsPreferTest.cs @@ -265,6 +265,7 @@ public async Task Prefer_mode_attempts_tls_upgrade_when_server_advertises_tls_av Url = $"nats://127.0.0.1:{port}", TlsOpts = new NatsTlsOpts { Mode = TlsMode.Prefer }, MaxReconnectRetry = 0, + ConnectTimeout = TimeSpan.FromSeconds(30), }); Exception? connectException = null; @@ -301,7 +302,10 @@ public async Task Prefer_mode_attempts_tls_upgrade_when_server_advertises_tls_av output.WriteLine($"[{e.GetType().Name}] {e.Message}"); } - var tlsRelated = causes.Any(c => c is AuthenticationException || c is SocketException || c is IOException); + // A connect-timeout while the client was already past the INFO read is also evidence the + // TLS path was attempted: the plaintext path would have completed the handshake within the + // timeout, while a stalled TLS handshake under runner contention shows up as a timeout. + var tlsRelated = causes.Any(c => c is AuthenticationException || c is SocketException || c is IOException || c is TimeoutException); tlsRelated.Should().BeTrue( "Prefer mode should attempt TLS upgrade when server advertises tls_available=true"); } diff --git a/tests/NATS.Client.Core2.Tests/Utf8SubjectTest.cs b/tests/NATS.Client.Core2.Tests/Utf8SubjectTest.cs index 73211e873..c42472fbc 100644 --- a/tests/NATS.Client.Core2.Tests/Utf8SubjectTest.cs +++ b/tests/NATS.Client.Core2.Tests/Utf8SubjectTest.cs @@ -80,7 +80,7 @@ public async Task Emoji_subject_and_reply_to_are_decoded_correctly() cancellationToken: cts.Token); await server.Ready; - await using var nats = new NatsConnection(new NatsOpts { Url = server.Url }); + await using var nats = new NatsConnection(new NatsOpts { Url = server.Url, ConnectTimeout = TimeSpan.FromSeconds(30) }); await nats.ConnectRetryAsync(); await foreach (var msg in nats.SubscribeAsync(">", cancellationToken: cts.Token)) diff --git a/tests/NATS.Client.JetStream.Tests/CancellationTokenTests.cs b/tests/NATS.Client.JetStream.Tests/CancellationTokenTests.cs index 771ad7dc1..5b4a25426 100644 --- a/tests/NATS.Client.JetStream.Tests/CancellationTokenTests.cs +++ b/tests/NATS.Client.JetStream.Tests/CancellationTokenTests.cs @@ -73,7 +73,7 @@ await Assert.ThrowsAsync(async () => public async Task NextAsync_with_cancelled_token_throws_immediately() { using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); - await using var nats = new NatsConnection(new NatsOpts { Url = server.Url }); + await using var nats = new NatsConnection(new NatsOpts { Url = server.Url, ConnectTimeout = TimeSpan.FromSeconds(30) }); var prefix = server.GetNextId(); var js = new NatsJSContext(nats); await js.CreateStreamAsync($"{prefix}s1", [$"{prefix}s1.*"], cts.Token); diff --git a/tests/NATS.Client.JetStream.Tests/PinnedClientTest.cs b/tests/NATS.Client.JetStream.Tests/PinnedClientTest.cs index 8407e5c71..73c1c2d89 100644 --- a/tests/NATS.Client.JetStream.Tests/PinnedClientTest.cs +++ b/tests/NATS.Client.JetStream.Tests/PinnedClientTest.cs @@ -537,7 +537,7 @@ public async Task Pin_id_from_headers_should_use_last_value_when_multiple_header }); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); - await using var nats = new NatsConnection(new NatsOpts { Url = ms.Url }); + await using var nats = new NatsConnection(new NatsOpts { Url = ms.Url, ConnectTimeout = TimeSpan.FromSeconds(30) }); var js = nats.CreateJetStreamContext(); var consumer = (NatsJSConsumer)await js.GetConsumerAsync("x", "x", cts.Token); var headers = new NatsHeaders diff --git a/tests/NATS.Client.TestUtilities/NatsUtils.cs b/tests/NATS.Client.TestUtilities/NatsUtils.cs index 0bb868026..f67c9d828 100644 --- a/tests/NATS.Client.TestUtilities/NatsUtils.cs +++ b/tests/NATS.Client.TestUtilities/NatsUtils.cs @@ -9,7 +9,7 @@ public static class NatsUtils { public static async Task ConnectRetryAsync(this INatsClient client, TimeSpan? timeout = null) { - timeout ??= TimeSpan.FromSeconds(30); + timeout ??= TimeSpan.FromSeconds(60); Exception? exception = null; var stopwatch = Stopwatch.StartNew(); From 13d62deaa1070abdd04b6f9a0b6f86f3af3e7e66 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 14 May 2026 16:37:51 +0100 Subject: [PATCH 2/2] test: drop xunit MaxParallelThreads 4.0x -> 1.0x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #1145 run 1 showed PingCancellation/Utf8Subject mock-server tests still hanging 75s+ even with bumped ConnectTimeout. Stack always at SetupReaderWriterAsync waiting for INFO — the NatsReadProtocolProcessor's Task.Run never gets a thread. Under 4.0x parallelism we run 16 tests at once on 4-core Windows runners; many doing TCP mock-servers at the same time starves the thread pool. Drop to 1.0x to remove the contention. --- tests/xunit.runsettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/xunit.runsettings b/tests/xunit.runsettings index db71244a6..1bc835271 100644 --- a/tests/xunit.runsettings +++ b/tests/xunit.runsettings @@ -4,6 +4,6 @@ 600000 - 4.0x + 1.0x