fix(dcl): remove unused poll-based WriteBatchAndWaitAsync from IDataConnection and both adapters (review 2026-10-21 02/U3)

This commit is contained in:
Joseph Doherty
2026-10-22 06:14:09 -04:00
parent 3169429c3f
commit b811a2451b
8 changed files with 5 additions and 138 deletions
@@ -56,16 +56,6 @@ public interface IDataConnection : IAsyncDisposable
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A dictionary of tag paths to their write results.</returns>
Task<IReadOnlyDictionary<string, WriteResult>> WriteBatchAsync(IDictionary<string, object?> values, CancellationToken cancellationToken = default);
/// <summary>Writes a batch of values, then writes a flag and waits for a specific response value within the timeout.</summary>
/// <param name="values">Tag values to write before the flag.</param>
/// <param name="flagPath">Tag path of the trigger flag.</param>
/// <param name="flagValue">Value to write to the flag tag.</param>
/// <param name="responsePath">Tag path to monitor for the expected response value.</param>
/// <param name="responseValue">The response value that indicates completion.</param>
/// <param name="timeout">Maximum time to wait for the response.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns><c>true</c> if the response value was observed within the timeout; otherwise <c>false</c>.</returns>
Task<bool> WriteBatchAndWaitAsync(IDictionary<string, object?> values, string flagPath, object? flagValue, string responsePath, object? responseValue, TimeSpan timeout, CancellationToken cancellationToken = default);
/// <summary>Current connection health status.</summary>
ConnectionHealth Status { get; }
@@ -1972,8 +1972,9 @@ public class DataConnectionActor : UntypedActor, IWithTimers
/// the timeout, or an adapter exception is translated into a failed
/// <see cref="WriteTagBatchResponse"/> returned synchronously to the caller — never a
/// dropped reply. NOTE: this deliberately composes the batch + trigger primitives and
/// uses the EXISTING event-driven WaitForAttribute waiter for the wait half; it does
/// NOT call the adapter's poll-based <c>WriteBatchAndWaitAsync</c>.
/// uses the EXISTING event-driven WaitForAttribute waiter for the wait half (the
/// adapters' former poll-based <c>WriteBatchAndWaitAsync</c> was removed — review
/// 2026-10-21 02/U3 — so no polling handshake can bypass that waiter).
/// </summary>
private void HandleWriteBatch(WriteTagBatchRequest request)
{
@@ -420,35 +420,6 @@ public class MxGatewayDataConnection
return results.ToDictionary(w => w.TagPath, w => new WriteResult(w.Success, w.Error));
}
/// <inheritdoc />
public async Task<bool> WriteBatchAndWaitAsync(
IDictionary<string, object?> values, string flagPath, object? flagValue,
string responsePath, object? responseValue, TimeSpan timeout, CancellationToken cancellationToken = default)
{
await WriteBatchAsync(values, cancellationToken);
await WriteAsync(flagPath, flagValue, cancellationToken);
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(timeout);
try
{
while (!timeoutCts.IsCancellationRequested)
{
var r = await ReadAsync(responsePath, timeoutCts.Token);
// r.Value is a TagValue wrapper; compare its underlying scalar. String
// projection tolerates numeric type differences across the gRPC boundary.
if (r.Success && string.Equals(r.Value?.Value?.ToString(), responseValue?.ToString(), StringComparison.Ordinal))
return true;
await Task.Delay(TimeSpan.FromMilliseconds(200), timeoutCts.Token);
}
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
// Timeout elapsed (the linked CTS, not the caller's token) — fall through to false.
}
return false;
}
/// <inheritdoc />
public async Task<BrowseChildrenResult> BrowseChildrenAsync(
string? parentNodeId,
@@ -416,34 +416,6 @@ public class OpcUaDataConnection
return _client.SearchAddressSpaceAsync(query, maxDepth, maxResults, cancellationToken);
}
/// <inheritdoc />
public async Task<bool> WriteBatchAndWaitAsync(
IDictionary<string, object?> values, string flagPath, object? flagValue,
string responsePath, object? responseValue, TimeSpan timeout,
CancellationToken cancellationToken = default)
{
// Write all values including the flag
var allValues = new Dictionary<string, object?>(values) { [flagPath] = flagValue };
var writeResults = await WriteBatchAsync(allValues, cancellationToken);
if (writeResults.Values.Any(r => !r.Success))
return false;
// Poll for response value within timeout
var deadline = DateTimeOffset.UtcNow + timeout;
while (DateTimeOffset.UtcNow < deadline)
{
cancellationToken.ThrowIfCancellationRequested();
var readResult = await ReadAsync(responsePath, cancellationToken);
if (readResult.Success && readResult.Value != null && Equals(readResult.Value.Value, responseValue))
return true;
await Task.Delay(100, cancellationToken);
}
return false;
}
private void StopHeartbeatMonitor()
{
_staleMonitor?.Dispose();