From d2b00fcda54bae4df9952603f9d505773e4271f5 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 25 Jun 2026 03:05:10 -0400 Subject: [PATCH] fix: idempotent HistorianSession.DisposeAsync + upfront cancellation check --- src/AVEVA.Historian.Client/HistorianSession.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/AVEVA.Historian.Client/HistorianSession.cs b/src/AVEVA.Historian.Client/HistorianSession.cs index 9873fa2..120bbe6 100644 --- a/src/AVEVA.Historian.Client/HistorianSession.cs +++ b/src/AVEVA.Historian.Client/HistorianSession.cs @@ -13,6 +13,7 @@ public sealed class HistorianSession : IAsyncDisposable private readonly HistorianGrpcConnection _connection; private readonly HistorianGrpcHandshake.Session _session; private readonly HistorianClientOptions _options; + private int _disposed; /// Whether this session was opened read-only or write-enabled (the Open2 connection mode). public HistorianSessionKind Kind { get; } @@ -41,6 +42,7 @@ public sealed class HistorianSession : IAsyncDisposable int maxValues, [EnumeratorCancellation] CancellationToken ct = default) { + ct.ThrowIfCancellationRequested(); var orch = new HistorianGrpcReadOrchestrator(_options); IReadOnlyList rows = await Task.Run( () => orch.RunRawQueryOnSession(_connection, _session.ClientHandle, tag, startUtc, endUtc, maxValues, ct), ct) @@ -63,6 +65,7 @@ public sealed class HistorianSession : IAsyncDisposable TimeSpan interval, [EnumeratorCancellation] CancellationToken ct = default) { + ct.ThrowIfCancellationRequested(); var orch = new HistorianGrpcReadOrchestrator(_options); IReadOnlyList rows = await Task.Run( () => orch.RunAggregateQueryOnSession(_connection, _session.ClientHandle, tag, startUtc, endUtc, mode, interval, ct), ct) @@ -180,7 +183,10 @@ public sealed class HistorianSession : IAsyncDisposable /// also idle-expires on its own; this releases the local channel resources immediately. public ValueTask DisposeAsync() { - _connection.Dispose(); + if (Interlocked.Exchange(ref _disposed, 1) == 0) + { + _connection.Dispose(); + } return ValueTask.CompletedTask; } }