From e46ff9abd03cb5b1000e788b3559fc67161ef94e Mon Sep 17 00:00:00 2001 From: Suciu Mircea Adrian Date: Thu, 18 Dec 2025 09:36:37 +0200 Subject: [PATCH 1/2] Update version from 1.5.378-preview to 1.5.378 --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 2384e1c39b..2dc9e72bb2 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.5.378-preview", + "version": "1.5.378", "versionHeightOffset": 0, "nugetPackageVersion": { "semVer": 2 From c9af408cc240d62c8f42af8a7eaf75b54c056642 Mon Sep 17 00:00:00 2001 From: KarenKrill <74286712+KarenKrill@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:58:24 +0500 Subject: [PATCH 2/2] Fixed timer leaks in ChannelAsyncOperation.EndAsync (#3669) * Fixed timer leaks in ChannelAsyncOperation.EndAsync (added delay task cancellation) * Added WaitAsync with timeout PolyFill extension for versions <= .NET 6.0 * The ChannelAsyncOperation.EndAsync has been unified across all .NET versions using polyfills for older versions (< .NET 6.0) * Fixed missing "using" operator --- .../Stack/Tcp/ChannelAsyncOperation.cs | 27 +---------- .../Polyfills/System.Threading.Tasks.cs | 47 ++++++++++++++++++- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs index b7f9dc9aeb..1d97376444 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs @@ -249,7 +249,6 @@ public async Task EndAsync( try { Task awaitableTask = m_tcs.Task; -#if NET6_0_OR_GREATER if (timeout != int.MaxValue) { awaitableTask = m_tcs.Task @@ -259,31 +258,9 @@ public async Task EndAsync( { awaitableTask = m_tcs.Task.WaitAsync(ct); } -#else - if (timeout != int.MaxValue || ct != default) + if (!await awaitableTask.ConfigureAwait(false)) { - Task completedTask = await Task.WhenAny(m_tcs.Task, Task.Delay(timeout, ct)) - .ConfigureAwait(false); - if (m_tcs.Task == completedTask) - { - if (!m_tcs.Task.Result) - { - badRequestInterrupted = true; - } - } - else - { - m_tcs.TrySetCanceled(ct); - badRequestInterrupted = true; - } - } - else -#endif - { - if (!await awaitableTask.ConfigureAwait(false)) - { - badRequestInterrupted = true; - } + badRequestInterrupted = true; } } catch (TimeoutException) diff --git a/Stack/Opc.Ua.Types/Polyfills/System.Threading.Tasks.cs b/Stack/Opc.Ua.Types/Polyfills/System.Threading.Tasks.cs index fba2c7c5d4..7444fbc9df 100644 --- a/Stack/Opc.Ua.Types/Polyfills/System.Threading.Tasks.cs +++ b/Stack/Opc.Ua.Types/Polyfills/System.Threading.Tasks.cs @@ -34,6 +34,51 @@ namespace System.Threading.Tasks /// public static class PolyFills { +#if !NET6_0_OR_GREATER + /// + /// Gets a System.Threading.Tasks.Task that will complete when this System.Threading.Tasks.Task + /// completes, when the specified timeout expires, or when the specified System.Threading.CancellationToken + /// + /// Task return type + /// The task to wait for. Can't be + /// + /// The timeout after which the System.Threading.Tasks.Task should be faulted with + ///

a System.TimeoutException if it hasn't otherwise completed. + /// + /// The System.Threading.CancellationToken to monitor for a cancellation request. + /// The System.Threading.Tasks.Task representing the asynchronous wait. It may or + /// may not be the same instance as the current instance. + /// + /// + public static async Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken ct = default) + { + if (task is null) + { + throw new ArgumentNullException(nameof(task)); + } + using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + cts.CancelAfter(timeout); + try + { + var tcs = new TaskCompletionSource(); + using (cts.Token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false)) + { + Task completedTask = await Task.WhenAny(task, tcs.Task).ConfigureAwait(false); + if (task != completedTask) + { + ct.ThrowIfCancellationRequested(); + throw new TimeoutException("The operation has timed out."); + } + } + return await task.ConfigureAwait(false); + } + catch (OperationCanceledException) when (!ct.IsCancellationRequested) + { + throw new TimeoutException("The operation has timed out."); + } + } +#endif + #if !NET8_0_OR_GREATER // Copyright Stephen Cleary Nito.AsyncEx @@ -72,7 +117,7 @@ private static async Task DoWaitAsync( Task task, CancellationToken cancellationToken) { - var cancelTaskSource = + using var cancelTaskSource = new CancellationTokenTaskSource(cancellationToken); await (await Task.WhenAny(task, cancelTaskSource.Task).ConfigureAwait(false)) .ConfigureAwait(false);