-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: None
-
Component/s: None
-
None
-
None
-
Dotnet Drivers
-
Not Needed
-
None
-
None
-
None
-
None
-
None
-
None
Summary
Fixes resource retention in the pre-.NET 6 implementation of TaskExtensions.WaitAsync.
The existing implementation passes the caller-provided CancellationToken directly to Task.Delay. When the operation task completes before the timeout task, Task.WhenAny returns but the delay remains registered with the caller's cancellation token until the timeout expires or cancellation is requested.
For long-lived cancellation tokens and repeatedly executed operations—such as polling a tailable cursor with MoveNextAsync—these registrations can accumulate and retain the associated timeout tasks and state. In our application, this appeared as steadily increasing memory usage while continuously reading from a tailable cursor.
How to Reproduce
The issue can be observed in a long-running tailable-cursor loop:
while (!cancellationToken.IsCancellationRequested)
{
using (var cursor = await collection.FindAsync(
filter,
new FindOptions<T>
{ CursorType = CursorType.TailableAwait }
,
cancellationToken))
{
while (await cursor.MoveNextAsync(cancellationToken))
{ // Process the current batch. }
}
}
When the same long-lived cancellation token is reused across many asynchronous operations, the previous WaitAsync implementation can leave pending timeout registrations associated with that token after the operation task has already completed.