review(Driver.Modbus.Cli): FlushLogging() + interval validation + banner-before-events

Re-review at 7286d320. -009 FlushLogging() in finally; -010 validate --interval-ms positive
(+8 tests); -011 print subscribe banner before wiring OnDataChange (no main/poll-thread
interleave). Parity with AbCip.Cli.
This commit is contained in:
Joseph Doherty
2026-06-19 12:08:45 -04:00
parent 754c5a3684
commit b0f9b8016a
6 changed files with 205 additions and 6 deletions
@@ -68,6 +68,9 @@ public sealed class ProbeCommand : ModbusCommandBase
finally
{
await driver.ShutdownAsync(CancellationToken.None);
// Driver.Modbus.Cli-009: flush Serilog before process exit so buffered log lines
// emitted during driver shutdown are not lost. Matches Driver.AbCip.Cli pattern.
FlushLogging();
}
}
@@ -86,6 +86,9 @@ public sealed class ReadCommand : ModbusCommandBase
finally
{
await driver.ShutdownAsync(CancellationToken.None);
// Driver.Modbus.Cli-009: flush Serilog before process exit so buffered log lines
// emitted during driver shutdown are not lost. Matches Driver.AbCip.Cli pattern.
FlushLogging();
}
}
@@ -63,6 +63,9 @@ public sealed class SubscribeCommand : ModbusCommandBase
{
ConfigureLogging();
ValidateEndpoint();
// Driver.Modbus.Cli-010: reject non-positive interval before opening the driver so
// the operator gets a clean error instead of a confusing PollGroupEngine failure.
ValidateInterval(IntervalMs);
var ct = console.RegisterCancellationHandler();
var tagName = ReadCommand.SynthesiseTagName(Region, Address, DataType);
@@ -87,6 +90,14 @@ public sealed class SubscribeCommand : ModbusCommandBase
{
await driver.InitializeAsync("{}", ct);
// Driver.Modbus.Cli-011: emit the banner BEFORE wiring OnDataChange so the
// main-thread WriteLineAsync cannot interleave with poll-thread change-event
// writes. TextWriter.WriteLine is not guaranteed thread-safe; once the handler
// is attached and SubscribeAsync starts, change events run on the poll thread.
// Mirrors the ordering in Driver.AbCip.Cli.SubscribeCommand.
await console.Output.WriteLineAsync(
$"Subscribed to {tagName} @ {IntervalMs}ms. Ctrl+C to stop.");
// Route every data-change event to the CliFx console (not System.Console — the
// analyzer flags it + IConsole is the testable abstraction).
driver.OnDataChange += (_, e) =>
@@ -114,9 +125,6 @@ public sealed class SubscribeCommand : ModbusCommandBase
};
handle = await driver.SubscribeAsync([tagName], TimeSpan.FromMilliseconds(IntervalMs), ct);
await console.Output.WriteLineAsync(
$"Subscribed to {tagName} @ {IntervalMs}ms. Ctrl+C to stop.");
try
{
await Task.Delay(System.Threading.Timeout.InfiniteTimeSpan, ct);
@@ -134,6 +142,26 @@ public sealed class SubscribeCommand : ModbusCommandBase
catch { /* teardown best-effort */ }
}
await driver.ShutdownAsync(CancellationToken.None);
// Driver.Modbus.Cli-009: flush Serilog before process exit so buffered log lines
// emitted just before Ctrl+C (e.g. during UnsubscribeAsync / ShutdownAsync) are
// not lost. Matches the pattern in Driver.AbCip.Cli commands.
FlushLogging();
}
}
/// <summary>
/// Driver.Modbus.Cli-010: guards <c>--interval-ms</c> against zero or negative values.
/// A non-positive interval would produce a non-positive <see cref="TimeSpan"/> into
/// <c>SubscribeAsync</c>; the CLI should fail fast with an actionable error rather
/// than relying on the downstream <c>PollGroupEngine</c> to clamp the value.
/// Mirrors <c>Driver.AbCip.Cli.SubscribeCommand.ValidateInterval</c>.
/// </summary>
/// <param name="intervalMs">The polling interval in milliseconds.</param>
internal static void ValidateInterval(int intervalMs)
{
if (intervalMs <= 0)
throw new CliFx.Exceptions.CommandException(
$"--interval-ms must be > 0 (got {intervalMs}). " +
"PollGroupEngine floors sub-250ms values, but accepts any positive integer.");
}
}
@@ -112,6 +112,9 @@ public sealed class WriteCommand : ModbusCommandBase
finally
{
await driver.ShutdownAsync(CancellationToken.None);
// Driver.Modbus.Cli-009: flush Serilog before process exit so buffered log lines
// emitted during driver shutdown are not lost. Matches Driver.AbCip.Cli pattern.
FlushLogging();
}
}