fix(probe): bound OpcUaClient/AbCip handshakes by timeout CTS; IPv4 preflight; stop sw (code-review)

This commit is contained in:
Joseph Doherty
2026-06-16 06:56:16 -04:00
parent 2d688c2a6d
commit 9bfbbb0fd8
2 changed files with 20 additions and 9 deletions
@@ -43,15 +43,18 @@ public sealed class OpcUaClientDriverProbe : IDriverProbe
if (string.IsNullOrWhiteSpace(host) || port <= 0)
return new(false, "Config has no host/port to probe.", null);
// Bound the whole probe (both phases) by the caller timeout, independent of `ct` — a
// stalled OPC UA endpoint that accepts TCP but never answers GetEndpoints must not block
// the probe when the caller passes CancellationToken.None.
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(timeout);
// --- TCP preflight: fast-fail for closed ports / unreachable hosts ---
var sw = Stopwatch.StartNew();
try
{
using var socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp)
{
DualMode = true,
};
await socket.ConnectAsync(host, port, ct);
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(host, port, cts.Token);
}
catch (SocketException ex)
{
@@ -74,8 +77,8 @@ public sealed class OpcUaClientDriverProbe : IDriverProbe
{
var appConfig = BuildMinimalAppConfig();
using var client = await DiscoveryClient.CreateAsync(
appConfig, new Uri(endpointUrl), DiagnosticsMasks.None, ct).ConfigureAwait(false);
var endpoints = await client.GetEndpointsAsync(null, ct).ConfigureAwait(false);
appConfig, new Uri(endpointUrl), DiagnosticsMasks.None, cts.Token).ConfigureAwait(false);
var endpoints = await client.GetEndpointsAsync(null, cts.Token).ConfigureAwait(false);
sw.Stop();
if (endpoints is { Count: > 0 })