fix(net): bound every request with a timeout
A server that stops answering without closing the socket left a running sync waiting forever. Requests now fail with TimeoutException after 30 s (10 s for the handshake, both configurable in ConnectAsync) and the connection is marked dead so the app reconnects. See ADR 0034.
This commit is contained in:
1 parent
cb551efce4
commit
3be761d97e
5 files changed
+147
-6
No files matched your search
@@ -147,6 +147,86 @@ public class ElectrumClientTests
|
||||
Assert.Equal(big, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_request_without_an_answer_fails_with_TimeoutException()
|
||||
{
|
||||
await using var server = new FakeElectrumServer();
|
||||
server.Handle("server.banner", _ => FakeElectrumServer.NoResponse);
|
||||
await using var client = await ElectrumClient.ConnectAsync(server.Host, server.Port,
|
||||
useSsl: false, requestTimeout: TimeSpan.FromMilliseconds(300));
|
||||
|
||||
var ex = await Assert.ThrowsAsync<TimeoutException>(() => client.BannerAsync().WaitAsync(Timeout));
|
||||
Assert.Contains("server.banner", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task After_a_timeout_the_client_is_disconnected_and_later_requests_fail_fast()
|
||||
{
|
||||
await using var server = new FakeElectrumServer();
|
||||
server.Handle("server.banner", _ => FakeElectrumServer.NoResponse);
|
||||
await using var client = await ElectrumClient.ConnectAsync(server.Host, server.Port,
|
||||
useSsl: false, requestTimeout: TimeSpan.FromMilliseconds(300));
|
||||
var disconnected = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
client.Disconnected += _ => disconnected.TrySetResult();
|
||||
|
||||
await Assert.ThrowsAsync<TimeoutException>(() => client.BannerAsync().WaitAsync(Timeout));
|
||||
await disconnected.Task.WaitAsync(Timeout);
|
||||
|
||||
Assert.False(client.IsConnected);
|
||||
await Assert.ThrowsAsync<IOException>(() => client.BannerAsync().WaitAsync(Timeout));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_timeout_fails_the_other_pending_requests_too()
|
||||
{
|
||||
await using var server = new FakeElectrumServer();
|
||||
server.Handle("server.banner", _ => FakeElectrumServer.NoResponse);
|
||||
await using var client = await ElectrumClient.ConnectAsync(server.Host, server.Port,
|
||||
useSsl: false, requestTimeout: TimeSpan.FromMilliseconds(300));
|
||||
|
||||
var a = client.BannerAsync();
|
||||
var b = client.BannerAsync();
|
||||
await Assert.ThrowsAnyAsync<Exception>(() => a.WaitAsync(Timeout));
|
||||
await Assert.ThrowsAnyAsync<Exception>(() => b.WaitAsync(Timeout));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_slow_answer_within_the_timeout_still_succeeds()
|
||||
{
|
||||
await using var server = new FakeElectrumServer();
|
||||
server.Handle("server.banner", _ => { Thread.Sleep(200); return "hello"; });
|
||||
await using var client = await ElectrumClient.ConnectAsync(server.Host, server.Port,
|
||||
useSsl: false, requestTimeout: TimeSpan.FromSeconds(10));
|
||||
|
||||
Assert.Equal("hello", await client.BannerAsync().WaitAsync(Timeout));
|
||||
Assert.True(client.IsConnected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_handshake_that_never_completes_times_out()
|
||||
{
|
||||
await using var server = new FakeElectrumServer();
|
||||
server.Handle("server.version", _ => FakeElectrumServer.NoResponse);
|
||||
|
||||
await Assert.ThrowsAsync<TimeoutException>(() => ElectrumClient.ConnectAsync(
|
||||
server.Host, server.Port, useSsl: false,
|
||||
handshakeTimeout: TimeSpan.FromMilliseconds(300)).WaitAsync(Timeout));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Waiting_for_an_in_flight_slot_does_not_count_towards_the_timeout()
|
||||
{
|
||||
await using var server = new FakeElectrumServer();
|
||||
server.Handle("server.banner", _ => { Thread.Sleep(250); return "ok"; });
|
||||
await using var client = await ElectrumClient.ConnectAsync(server.Host, server.Port,
|
||||
useSsl: false, maxInFlight: 1, requestTimeout: TimeSpan.FromMilliseconds(600));
|
||||
|
||||
// Three serial requests of ~250 ms each: the last one waits ~500 ms for the slot.
|
||||
var all = await Task.WhenAll(client.BannerAsync(), client.BannerAsync(), client.BannerAsync())
|
||||
.WaitAsync(Timeout);
|
||||
Assert.All(all, b => Assert.Equal("ok", b));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task La_cancellazione_del_token_annulla_la_richiesta_pendente()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user