perf(tests): isolate network E2E and await QUIC cleanup
ci/woodpecker/push/verify Pipeline failed
ci/woodpecker/push/e2e unknown status
ci/woodpecker/push/web-build Pipeline was successful

This commit is contained in:
justme committed 2026-10-10 19:33:36 +08:00
1 parent 047df83a84
commit 0d08e90dab
20 files changed
+231 -181

No files matched your search

@@ -70,8 +70,10 @@ public sealed class ProbeSimulator : IAsyncDisposable
public async Task ConnectAsync(string host, int controlPort, byte[] agentPublicKey, int udpPort, NodeId agentId, TimeProvider? time = null, CancellationToken ct = default)
{
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeout.CancelAfter(TimeSpan.FromSeconds(10));
_agentId = agentId;
(PeerSessionInfo info, TlsFrameTransport transport) = await PeerConnector.ConnectAsync(host, controlPort, agentPublicKey, _identity, ProbeId, "0.0.0-sim", 8 << 20, time ?? TimeProvider.System, ct);
(PeerSessionInfo info, TlsFrameTransport transport) = await PeerConnector.ConnectAsync(host, controlPort, agentPublicKey, _identity, ProbeId, "0.0.0-sim", 8 << 20, time ?? TimeProvider.System, timeout.Token);
_info = info;
_agentKey = agentPublicKey;
_dataSession = new DataSession(info.Credentials.SessionIdValue, info.Credentials.Key);
@@ -88,8 +90,8 @@ public sealed class ProbeSimulator : IAsyncDisposable
_rpc.OnBinary = (_, _) => ValueTask.CompletedTask;
_run = _rpc.RunAsync(_stop.Token);
_receiver = _quic.RunAsync(_stop.Token);
await SyncClockAsync();
await _quic.ConnectAsync(ct);
await SyncClockAsync(ct: timeout.Token);
await _quic.ConnectAsync(timeout.Token);
}
public async Task<LineTaskSet> WaitForTasksAsync(Func<LineTaskSet, bool> predicate, TimeSpan? timeout = null)
@@ -111,16 +113,27 @@ public sealed class ProbeSimulator : IAsyncDisposable
}
/// <summary>NTP 式的时钟同步:偏移 = Agent 时钟 − (往返中点的) Probe 时钟。取最小往返的一次。</summary>
public async Task SyncClockAsync(int rounds = 5)
public async Task SyncClockAsync(int rounds = 3, CancellationToken ct = default)
{
long bestRoundTrip = long.MaxValue;
for (int i = 0; i < rounds; i++)
{
long t0 = NowUs();
Result<ClockResult> reply = await _rpc!.RequestAsync(MoShiroMethods.Clock, new ClockParams { ProbeMonoUs = t0 }, MoShiroJsonContext.Default.ClockParams, MoShiroJsonContext.Default.ClockResult, CancellationToken.None);
Result<ClockResult> reply = await _rpc!.RequestAsync(
MoShiroMethods.Clock,
new ClockParams { ProbeMonoUs = t0 },
MoShiroJsonContext.Default.ClockParams,
MoShiroJsonContext.Default.ClockResult,
ct,
TimeSpan.FromSeconds(2));
long t1 = NowUs();
if (!reply.IsSuccess)
{
continue;
}
long roundTrip = t1 - t0;
if (reply.IsSuccess && roundTrip < bestRoundTrip)
if (roundTrip < bestRoundTrip)
{
bestRoundTrip = roundTrip;
ClockOffsetUs = reply.Value.AgentMonoUs - ((t0 + t1) / 2);
@@ -164,6 +177,9 @@ public sealed class ProbeSimulator : IAsyncDisposable
/// <summary>对一个 tcp_echo 线路任务建立一条 TCP(可选 TLS)连接,发 <paramref name="count"/> 条回显消息,然后交出一个列块。</summary>
public async Task<(int Sent, int Received)> RunTcpEchoAsync(LineTask task, int tcpPort, int count, TimeSpan interval, bool tls, CancellationToken ct = default)
{
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeout.CancelAfter(TimeSpan.FromSeconds(15));
ct = timeout.Token;
using var client = new System.Net.Sockets.TcpClient();
await client.ConnectAsync(IPAddress.Loopback, tcpPort, ct);
Stream stream = client.GetStream();
@@ -246,10 +262,20 @@ public sealed class ProbeSimulator : IAsyncDisposable
public async ValueTask DisposeAsync()
{
await _stop.CancelAsync();
_quic?.Dispose();
if (_quic is not null)
{
await _quic.DisposeAsync();
}
if (_rpc is not null)
{
await _rpc.DisposeAsync();
try
{
await _rpc.DisposeAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(2));
}
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException or IOException or ObjectDisposedException)
{
// 测试连接关闭属于尽力清理,不能让一条卡住的流拖住整个测试程序集。
}
}
foreach (Task? task in new[] { _run, _receiver })