fix(historian-client): dispose TcpClient/SslStream on connect+TLS failure (review)

This commit is contained in:
Joseph Doherty
2026-06-12 11:30:39 -04:00
parent 6e152047eb
commit fd4d05534e
2 changed files with 19 additions and 3 deletions
@@ -8,7 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client;
/// <remarks>
/// <para>
/// <b>Retry / backoff ownership (finding 006):</b> this module performs exactly one
/// in-place transport reconnect inside <c>PipeChannel.InvokeAsync</c> with no delay,
/// in-place transport reconnect inside <c>FrameChannel.InvokeAsync</c> with no delay,
/// and does NOT implement exponential reconnect backoff. Broader retry/backoff is the
/// caller's responsibility — the alarm drain worker
/// (<c>Core.AlarmHistorian.SqliteStoreAndForwardSink</c>) and the read-side
@@ -59,13 +59,21 @@ internal sealed class FrameChannel : IAsyncDisposable
throw new InvalidOperationException("WonderwareHistorianClientOptions.Host is required for the TCP transport.");
var tcp = new TcpClient();
using (var connectCts = CancellationTokenSource.CreateLinkedTokenSource(ct))
try
{
using var connectCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
connectCts.CancelAfter(opts.EffectiveConnectTimeout);
await tcp.ConnectAsync(opts.Host!, opts.Port, connectCts.Token).ConfigureAwait(false);
}
catch
{
tcp.Dispose();
throw;
}
tcp.NoDelay = true;
// The returned NetworkStream owns the socket (TcpClient.GetStream() uses ownsSocket: true),
// so FrameChannel.ResetTransport() disposing this stream closes the underlying socket.
Stream stream = tcp.GetStream();
if (!opts.UseTls) return stream;
@@ -75,7 +83,15 @@ internal sealed class FrameChannel : IAsyncDisposable
return string.Equals(cert?.GetCertHashString(), opts.ServerCertThumbprint, StringComparison.OrdinalIgnoreCase);
return errors == SslPolicyErrors.None;
});
await ssl.AuthenticateAsClientAsync(opts.Host!).ConfigureAwait(false);
try
{
await ssl.AuthenticateAsClientAsync(opts.Host!).ConfigureAwait(false);
}
catch
{
await ssl.DisposeAsync().ConfigureAwait(false);
throw;
}
return ssl;
};