fix(driver-ablegacy-cli): resolve Low code-review findings (Driver.AbLegacy.Cli-002,003,004,005,006,007)
- Driver.AbLegacy.Cli-002: WriteCommand.Value description lists the full true/false, 1/0, on/off, yes/no alias set. - Driver.AbLegacy.Cli-003: SubscribeCommand serialises every WriteLine via a per-execution consoleGate lock so the poll-thread OnDataChange handler can't interleave with the banner. - Driver.AbLegacy.Cli-004: dropped 'await using var driver' in favour of a plain 'var driver' + explicit await ShutdownAsync in finally; the driver is no longer shut down twice. - Driver.AbLegacy.Cli-005: SubscribeCommand.IntervalMs description carries the PollGroupEngine 250ms-floor caveat; docs/Driver.AbLegacy.Cli.md spells out the same. - Driver.AbLegacy.Cli-006: ProbeCommand --type now carries the short alias 't' to match the other commands. - Driver.AbLegacy.Cli-007: BuildOptionsTests cover the probe-disabled, device-shape, tag-passthrough, timeout-propagation, and empty-tag-list paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ public sealed class ProbeCommand : AbLegacyCommandBase
|
||||
"the pre-populated register every SLC / MicroLogix / PLC-5 ships with.")]
|
||||
public string Address { get; init; } = "N7:0";
|
||||
|
||||
[CommandOption("type", Description =
|
||||
[CommandOption("type", 't', Description =
|
||||
"PCCC data type of the probe address (default Int — matches N files).")]
|
||||
public AbLegacyDataType DataType { get; init; } = AbLegacyDataType.Int;
|
||||
|
||||
@@ -34,7 +34,10 @@ public sealed class ProbeCommand : AbLegacyCommandBase
|
||||
Writable: false);
|
||||
var options = BuildOptions([probeTag]);
|
||||
|
||||
await using var driver = new AbLegacyDriver(options, DriverInstanceId);
|
||||
// Plain `var driver`: explicit ShutdownAsync(CancellationToken.None) in the
|
||||
// finally is the deliberate teardown path; combining it with `await using`
|
||||
// (which itself calls ShutdownAsync) would tear the driver down twice.
|
||||
var driver = new AbLegacyDriver(options, DriverInstanceId);
|
||||
try
|
||||
{
|
||||
await driver.InitializeAsync("{}", ct);
|
||||
|
||||
@@ -36,7 +36,10 @@ public sealed class ReadCommand : AbLegacyCommandBase
|
||||
Writable: false);
|
||||
var options = BuildOptions([tag]);
|
||||
|
||||
await using var driver = new AbLegacyDriver(options, DriverInstanceId);
|
||||
// Plain `var driver`: explicit ShutdownAsync(CancellationToken.None) in the
|
||||
// finally is the deliberate teardown path; combining it with `await using`
|
||||
// (which itself calls ShutdownAsync) would tear the driver down twice.
|
||||
var driver = new AbLegacyDriver(options, DriverInstanceId);
|
||||
try
|
||||
{
|
||||
await driver.InitializeAsync("{}", ct);
|
||||
|
||||
@@ -21,7 +21,8 @@ public sealed class SubscribeCommand : AbLegacyCommandBase
|
||||
public AbLegacyDataType DataType { get; init; } = AbLegacyDataType.Int;
|
||||
|
||||
[CommandOption("interval-ms", 'i', Description =
|
||||
"Publishing interval in milliseconds (default 1000).")]
|
||||
"Publishing interval in milliseconds (default 1000). PollGroupEngine floors " +
|
||||
"sub-250ms values.")]
|
||||
public int IntervalMs { get; init; } = 1000;
|
||||
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
@@ -38,8 +39,17 @@ public sealed class SubscribeCommand : AbLegacyCommandBase
|
||||
Writable: false);
|
||||
var options = BuildOptions([tag]);
|
||||
|
||||
await using var driver = new AbLegacyDriver(options, DriverInstanceId);
|
||||
// Plain `var driver` (no `await using`): driver.DisposeAsync internally calls
|
||||
// ShutdownAsync, so combining `await using` with an explicit finally-shutdown
|
||||
// would tear the driver down twice. The explicit teardown is preferred because
|
||||
// it deliberately passes CancellationToken.None — `await using` would otherwise
|
||||
// happen on a cancelled `ct` path which can cut teardown short.
|
||||
var driver = new AbLegacyDriver(options, DriverInstanceId);
|
||||
ISubscriptionHandle? handle = null;
|
||||
// Serialise console writes from the poll-thread OnDataChange callback against
|
||||
// the command-thread "Subscribed to ..." line and against each other; the
|
||||
// PollGroupEngine raises change events on a background timer/loop thread.
|
||||
var consoleGate = new object();
|
||||
try
|
||||
{
|
||||
await driver.InitializeAsync("{}", ct);
|
||||
@@ -49,13 +59,19 @@ public sealed class SubscribeCommand : AbLegacyCommandBase
|
||||
var line = $"[{DateTime.UtcNow:HH:mm:ss.fff}] " +
|
||||
$"{e.FullReference} = {SnapshotFormatter.FormatValue(e.Snapshot.Value)} " +
|
||||
$"({SnapshotFormatter.FormatStatus(e.Snapshot.StatusCode)})";
|
||||
console.Output.WriteLine(line);
|
||||
lock (consoleGate)
|
||||
{
|
||||
console.Output.WriteLine(line);
|
||||
}
|
||||
};
|
||||
|
||||
handle = await driver.SubscribeAsync([tagName], TimeSpan.FromMilliseconds(IntervalMs), ct);
|
||||
|
||||
await console.Output.WriteLineAsync(
|
||||
$"Subscribed to {Address} @ {IntervalMs}ms. Ctrl+C to stop.");
|
||||
lock (consoleGate)
|
||||
{
|
||||
console.Output.WriteLine(
|
||||
$"Subscribed to {Address} @ {IntervalMs}ms. Ctrl+C to stop.");
|
||||
}
|
||||
try
|
||||
{
|
||||
await Task.Delay(System.Threading.Timeout.InfiniteTimeSpan, ct);
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class WriteCommand : AbLegacyCommandBase
|
||||
public AbLegacyDataType DataType { get; init; } = AbLegacyDataType.Int;
|
||||
|
||||
[CommandOption("value", 'v', Description =
|
||||
"Value to write. Parsed per --type (booleans accept true/false/1/0).",
|
||||
"Value to write. Parsed per --type (booleans accept true/false, 1/0, on/off, yes/no).",
|
||||
IsRequired = true)]
|
||||
public string Value { get; init; } = default!;
|
||||
|
||||
@@ -45,7 +45,10 @@ public sealed class WriteCommand : AbLegacyCommandBase
|
||||
|
||||
var parsed = ParseValue(Value, DataType);
|
||||
|
||||
await using var driver = new AbLegacyDriver(options, DriverInstanceId);
|
||||
// Plain `var driver`: explicit ShutdownAsync(CancellationToken.None) in the
|
||||
// finally is the deliberate teardown path; combining it with `await using`
|
||||
// (which itself calls ShutdownAsync) would tear the driver down twice.
|
||||
var driver = new AbLegacyDriver(options, DriverInstanceId);
|
||||
try
|
||||
{
|
||||
await driver.InitializeAsync("{}", ct);
|
||||
|
||||
Reference in New Issue
Block a user