fix(probe): bound OpcUaClient/AbCip handshakes by timeout CTS; IPv4 preflight; stop sw (code-review)
This commit is contained in:
@@ -59,12 +59,17 @@ public sealed class AbCipDriverProbe : IDriverProbe
|
|||||||
var host = parsed.Gateway;
|
var host = parsed.Gateway;
|
||||||
var port = parsed.Port;
|
var port = parsed.Port;
|
||||||
|
|
||||||
|
// Bound both phases by the caller timeout, independent of `ct`, so a device that accepts
|
||||||
|
// TCP but stalls the CIP Forward Open cannot hang the probe.
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
cts.CancelAfter(timeout);
|
||||||
|
|
||||||
// Phase 1: bare TCP preflight — fast rejection for unreachable hosts.
|
// Phase 1: bare TCP preflight — fast rejection for unreachable hosts.
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
await socket.ConnectAsync(host, port, ct);
|
await socket.ConnectAsync(host, port, cts.Token);
|
||||||
}
|
}
|
||||||
catch (SocketException ex)
|
catch (SocketException ex)
|
||||||
{
|
{
|
||||||
@@ -106,7 +111,7 @@ public sealed class AbCipDriverProbe : IDriverProbe
|
|||||||
var rt = new LibplctagTagRuntime(p);
|
var rt = new LibplctagTagRuntime(p);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await rt.InitializeAsync(ct);
|
await rt.InitializeAsync(cts.Token);
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|
||||||
// InitializeAsync completed without throwing — either the tag was found (Ok) or
|
// InitializeAsync completed without throwing — either the tag was found (Ok) or
|
||||||
@@ -131,7 +136,10 @@ public sealed class AbCipDriverProbe : IDriverProbe
|
|||||||
{
|
{
|
||||||
// LibPlcTagException carries the Status; classify as reachable vs transport failure.
|
// LibPlcTagException carries the Status; classify as reachable vs transport failure.
|
||||||
if (IsReachableException(ex))
|
if (IsReachableException(ex))
|
||||||
|
{
|
||||||
|
sw.Stop();
|
||||||
return new(true, "CIP session OK (controller reachable; probe tag not found)", sw.Elapsed);
|
return new(true, "CIP session OK (controller reachable; probe tag not found)", sw.Elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
return new(false, $"Reachable at {host}:{port} but CIP handshake failed: {ex.Message}", null);
|
return new(false, $"Reachable at {host}:{port} but CIP handshake failed: {ex.Message}", null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,15 +43,18 @@ public sealed class OpcUaClientDriverProbe : IDriverProbe
|
|||||||
if (string.IsNullOrWhiteSpace(host) || port <= 0)
|
if (string.IsNullOrWhiteSpace(host) || port <= 0)
|
||||||
return new(false, "Config has no host/port to probe.", null);
|
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 ---
|
// --- TCP preflight: fast-fail for closed ports / unreachable hosts ---
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp)
|
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
{
|
await socket.ConnectAsync(host, port, cts.Token);
|
||||||
DualMode = true,
|
|
||||||
};
|
|
||||||
await socket.ConnectAsync(host, port, ct);
|
|
||||||
}
|
}
|
||||||
catch (SocketException ex)
|
catch (SocketException ex)
|
||||||
{
|
{
|
||||||
@@ -74,8 +77,8 @@ public sealed class OpcUaClientDriverProbe : IDriverProbe
|
|||||||
{
|
{
|
||||||
var appConfig = BuildMinimalAppConfig();
|
var appConfig = BuildMinimalAppConfig();
|
||||||
using var client = await DiscoveryClient.CreateAsync(
|
using var client = await DiscoveryClient.CreateAsync(
|
||||||
appConfig, new Uri(endpointUrl), DiagnosticsMasks.None, ct).ConfigureAwait(false);
|
appConfig, new Uri(endpointUrl), DiagnosticsMasks.None, cts.Token).ConfigureAwait(false);
|
||||||
var endpoints = await client.GetEndpointsAsync(null, ct).ConfigureAwait(false);
|
var endpoints = await client.GetEndpointsAsync(null, cts.Token).ConfigureAwait(false);
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|
||||||
if (endpoints is { Count: > 0 })
|
if (endpoints is { Count: > 0 })
|
||||||
|
|||||||
Reference in New Issue
Block a user