Skip to content

Commit fc21f2d

Browse files
authored
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
1 parent abc813f commit fc21f2d

2 files changed

Lines changed: 48 additions & 26 deletions

File tree

Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,6 @@ public async Task<T> EndAsync(
249249
try
250250
{
251251
Task<bool> awaitableTask = m_tcs.Task;
252-
#if NET6_0_OR_GREATER
253252
if (timeout != int.MaxValue)
254253
{
255254
awaitableTask = m_tcs.Task
@@ -259,31 +258,9 @@ public async Task<T> EndAsync(
259258
{
260259
awaitableTask = m_tcs.Task.WaitAsync(ct);
261260
}
262-
#else
263-
if (timeout != int.MaxValue || ct != default)
261+
if (!await awaitableTask.ConfigureAwait(false))
264262
{
265-
Task completedTask = await Task.WhenAny(m_tcs.Task, Task.Delay(timeout, ct))
266-
.ConfigureAwait(false);
267-
if (m_tcs.Task == completedTask)
268-
{
269-
if (!m_tcs.Task.Result)
270-
{
271-
badRequestInterrupted = true;
272-
}
273-
}
274-
else
275-
{
276-
m_tcs.TrySetCanceled(ct);
277-
badRequestInterrupted = true;
278-
}
279-
}
280-
else
281-
#endif
282-
{
283-
if (!await awaitableTask.ConfigureAwait(false))
284-
{
285-
badRequestInterrupted = true;
286-
}
263+
badRequestInterrupted = true;
287264
}
288265
}
289266
catch (TimeoutException)

Stack/Opc.Ua.Types/Polyfills/System.Threading.Tasks.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,51 @@ namespace System.Threading.Tasks
3434
/// </summary>
3535
public static class PolyFills
3636
{
37+
#if !NET6_0_OR_GREATER
38+
/// <summary>
39+
/// Gets a System.Threading.Tasks.Task that will complete when this System.Threading.Tasks.Task
40+
/// completes, when the specified timeout expires, or when the specified System.Threading.CancellationToken
41+
/// </summary>
42+
/// <typeparam name="T">Task return type</typeparam>
43+
/// <param name="task">The task to wait for. Can't be <see langword="null"></see></param>
44+
/// <param name="timeout">
45+
/// The timeout after which the System.Threading.Tasks.Task should be faulted with
46+
/// <br></br>a System.TimeoutException if it hasn't otherwise completed.
47+
/// </param>
48+
/// <param name="ct">The System.Threading.CancellationToken to monitor for a cancellation request.</param>
49+
/// <returns>The System.Threading.Tasks.Task representing the asynchronous wait. It may or
50+
/// may not be the same instance as the current instance.</returns>
51+
/// <exception cref="ArgumentNullException"></exception>
52+
/// <exception cref="TimeoutException"></exception>
53+
public static async Task<T> WaitAsync<T>(this Task<T> task, TimeSpan timeout, CancellationToken ct = default)
54+
{
55+
if (task is null)
56+
{
57+
throw new ArgumentNullException(nameof(task));
58+
}
59+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
60+
cts.CancelAfter(timeout);
61+
try
62+
{
63+
var tcs = new TaskCompletionSource<bool>();
64+
using (cts.Token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false))
65+
{
66+
Task completedTask = await Task.WhenAny(task, tcs.Task).ConfigureAwait(false);
67+
if (task != completedTask)
68+
{
69+
ct.ThrowIfCancellationRequested();
70+
throw new TimeoutException("The operation has timed out.");
71+
}
72+
}
73+
return await task.ConfigureAwait(false);
74+
}
75+
catch (OperationCanceledException) when (!ct.IsCancellationRequested)
76+
{
77+
throw new TimeoutException("The operation has timed out.");
78+
}
79+
}
80+
#endif
81+
3782
#if !NET8_0_OR_GREATER
3883
// Copyright Stephen Cleary Nito.AsyncEx
3984

@@ -72,7 +117,7 @@ private static async Task DoWaitAsync(
72117
Task task,
73118
CancellationToken cancellationToken)
74119
{
75-
var cancelTaskSource =
120+
using var cancelTaskSource =
76121
new CancellationTokenTaskSource<object>(cancellationToken);
77122
await (await Task.WhenAny(task, cancelTaskSource.Task).ConfigureAwait(false))
78123
.ConfigureAwait(false);

0 commit comments

Comments
 (0)