fix(link): bound the server connect and alternate address families on retry [CI]
This commit is contained in:
1 parent
0d532caf08
commit
34190f5a04
1 file changed
+61
@@ -1,3 +1,6 @@
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
@@ -93,6 +96,9 @@ public sealed class FleetLink
|
||||
private readonly INodeLog _log;
|
||||
private volatile RpcSession<LinkContext>? _session;
|
||||
private volatile bool _stopped;
|
||||
private static readonly TimeSpan s_connectTimeout = TimeSpan.FromSeconds(20);
|
||||
private static readonly TimeSpan s_tcpTimeout = TimeSpan.FromSeconds(4);
|
||||
private int _failedConnects;
|
||||
|
||||
public FleetLink(FleetLinkOptions options, RpcRouter<LinkContext> router, IEnumerable<IStateHandler> handlers, Outbox outbox, TimeProvider time, INodeLog log)
|
||||
{
|
||||
@@ -163,6 +169,61 @@ public sealed class FleetLink
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连上面板:整个握手(TCP、TLS、升级)限时,卡住就放弃重试;每个地址的 TCP 连接也单独限时。
|
||||
/// 失败之后下一次换着先试另一个地址族(IPv6 / IPv4):有的网络上到某个地址族的 TCP 能连上、之后的 TLS 却被卡住,换一个往往就通。
|
||||
/// </summary>
|
||||
private async Task ConnectAsync(ClientWebSocket socket, Uri uri, CancellationToken ct)
|
||||
{
|
||||
int attempt = Volatile.Read(ref _failedConnects);
|
||||
using var handler = new SocketsHttpHandler { ConnectCallback = (context, token) => ConnectTcpAsync(context, attempt, token) };
|
||||
using var invoker = new HttpMessageInvoker(handler, disposeHandler: false);
|
||||
using CancellationTokenSource timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
timeout.CancelAfter(s_connectTimeout);
|
||||
try
|
||||
{
|
||||
await socket.ConnectAsync(uri, invoker, timeout.Token);
|
||||
Volatile.Write(ref _failedConnects, 0);
|
||||
}
|
||||
catch (OperationCanceledException) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
Interlocked.Increment(ref _failedConnects);
|
||||
throw new TimeoutException("connecting to the server timed out");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Interlocked.Increment(ref _failedConnects);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask<Stream> ConnectTcpAsync(SocketsHttpConnectionContext context, int attempt, CancellationToken ct)
|
||||
{
|
||||
IPAddress[] addresses = await Dns.GetHostAddressesAsync(context.DnsEndPoint.Host, ct);
|
||||
// 第一次按 DNS 的顺序;之后奇数次先试 IPv6,偶数次先试 IPv4
|
||||
IEnumerable<IPAddress> ordered = attempt == 0 ? addresses
|
||||
: addresses.OrderBy(a => (a.AddressFamily == AddressFamily.InterNetworkV6) == (attempt % 2 == 1) ? 0 : 1);
|
||||
Exception? last = null;
|
||||
foreach (IPAddress address in ordered)
|
||||
{
|
||||
var tcp = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
|
||||
using CancellationTokenSource limit = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
limit.CancelAfter(s_tcpTimeout);
|
||||
try
|
||||
{
|
||||
await tcp.ConnectAsync(new IPEndPoint(address, context.DnsEndPoint.Port), limit.Token);
|
||||
return new NetworkStream(tcp, ownsSocket: true);
|
||||
}
|
||||
catch (Exception ex) when (ex is SocketException or OperationCanceledException && !ct.IsCancellationRequested)
|
||||
{
|
||||
tcp.Dispose();
|
||||
last = ex;
|
||||
}
|
||||
}
|
||||
|
||||
throw last ?? new SocketException((int)SocketError.HostNotFound);
|
||||
}
|
||||
|
||||
/// <summary>当前这条链路是不是应用层加密的(服务器证明了身份)。</summary>
|
||||
public bool IsSecure => _secure && IsConnected;
|
||||
|
||||
|
||||
Reference in new issue
Block a user