From 75580fb432b0bcfc2711a4c6fe3f711744bb44a1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 22 May 2026 09:19:14 -0400 Subject: [PATCH] fix(driver-historian-wonderware-client): resolve Medium code-review finding (Driver.Historian.Wonderware.Client-005) Replace the synchronous non-cancellable _stream.ReadByte() for the kind byte in FrameReader.ReadFrameAsync with an async ReadExactAsync(new byte[1], ct) call so the full frame read honours the EffectiveCallTimeout-linked token and cannot wedge the call gate when the sidecar stalls mid-frame. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Driver.Historian.Wonderware.Client/findings.md | 4 ++-- .../Ipc/FrameReader.cs | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/code-reviews/Driver.Historian.Wonderware.Client/findings.md b/code-reviews/Driver.Historian.Wonderware.Client/findings.md index 068d3f8..5b44b95 100644 --- a/code-reviews/Driver.Historian.Wonderware.Client/findings.md +++ b/code-reviews/Driver.Historian.Wonderware.Client/findings.md @@ -141,7 +141,7 @@ counters under one lock acquisition. | Severity | Medium | | Category | Error handling & resilience | | Location | `Ipc/FrameReader.cs:31-32` | -| Status | Open | +| Status | Resolved | **Description:** After reading the 4-byte length prefix, `ReadFrameAsync` reads the kind byte with the synchronous, blocking `_stream.ReadByte()` and ignores the @@ -158,7 +158,7 @@ prefix read to 5 bytes, or do a second `ReadExactAsync(new byte[1], ct)`. This m whole frame read honor the call-timeout token and matches the async style of the rest of the reader. -**Resolution:** _(open)_ +**Resolution:** Resolved 2026-05-22 — replaced the synchronous, non-cancellable `_stream.ReadByte()` for the kind byte with an async `ReadExactAsync(new byte[1], ct)` call so the full frame read honours the call-timeout token and cannot wedge the channel on a stalled peer. ### Driver.Historian.Wonderware.Client-006 diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client/Ipc/FrameReader.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client/Ipc/FrameReader.cs index 4992651..86a0fc6 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client/Ipc/FrameReader.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client/Ipc/FrameReader.cs @@ -28,14 +28,18 @@ public sealed class FrameReader : IDisposable if (length < 0 || length > Framing.MaxFrameBodyBytes) throw new InvalidDataException($"Sidecar IPC frame length {length} out of range."); - var kindByte = _stream.ReadByte(); - if (kindByte < 0) throw new EndOfStreamException("EOF after length prefix, before kind byte."); + // Read the kind byte asynchronously and cancellably — a synchronous ReadByte() + // blocks the thread-pool thread and cannot be interrupted by the call-timeout token + // if the peer stalls mid-frame (finding 005). + var kindBuffer = new byte[Framing.KindByteSize]; + if (!await ReadExactAsync(kindBuffer, ct).ConfigureAwait(false)) + throw new EndOfStreamException("EOF after length prefix, before kind byte."); var body = new byte[length]; if (!await ReadExactAsync(body, ct).ConfigureAwait(false)) throw new EndOfStreamException("EOF mid-frame."); - return ((MessageKind)(byte)kindByte, body); + return ((MessageKind)kindBuffer[0], body); } public static T Deserialize(byte[] body) => MessagePackSerializer.Deserialize(body);