Skip to content

Commit 8589a61

Browse files
committed
Fix AsyncQueue handling of cancelled tasks
This change fixes an issue in AsyncQueue where cancelled tasks were not being skipped when new values are enqueued. This primarily affected the queueing of RunspaceHandle access in PowerShellContext.
1 parent 042d286 commit 8589a61

File tree

2 files changed

+36
-10
lines changed

2 files changed

+36
-10
lines changed

src/PowerShellEditorServices/Utility/AsyncQueue.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,24 @@ public async Task EnqueueAsync(T item)
7373
{
7474
using (await queueLock.LockAsync())
7575
{
76-
if (this.requestQueue.Count > 0)
77-
{
78-
// There are requests waiting, immediately dispatch the item
79-
TaskCompletionSource<T> requestTaskSource = this.requestQueue.Dequeue();
80-
requestTaskSource.SetResult(item);
81-
}
82-
else
76+
TaskCompletionSource<T> requestTaskSource = null;
77+
78+
// Are any requests waiting?
79+
while (this.requestQueue.Count > 0)
8380
{
84-
// No requests waiting, queue the item for a later request
85-
this.itemQueue.Enqueue(item);
86-
this.IsEmpty = false;
81+
// Is the next request cancelled already?
82+
requestTaskSource = this.requestQueue.Dequeue();
83+
if (!requestTaskSource.Task.IsCanceled)
84+
{
85+
// Dispatch the item
86+
requestTaskSource.SetResult(item);
87+
return;
88+
}
8789
}
90+
91+
// No more requests waiting, queue the item for a later request
92+
this.itemQueue.Enqueue(item);
93+
this.IsEmpty = false;
8894
}
8995
}
9096

test/PowerShellEditorServices.Test/Utility/AsyncQueueTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,26 @@ await Task.WhenAll(
5656
Assert.Equal(0, expectedItems.Except(outputItems).Count());
5757
}
5858

59+
[Fact]
60+
public async Task AsyncQueueSkipsCancelledTasks()
61+
{
62+
AsyncQueue<int> inputQueue = new AsyncQueue<int>();
63+
64+
// Queue up a couple of tasks to wait for input
65+
CancellationTokenSource cancellationSource = new CancellationTokenSource();
66+
Task<int> taskOne = inputQueue.DequeueAsync(cancellationSource.Token);
67+
Task<int> taskTwo = inputQueue.DequeueAsync();
68+
69+
// Cancel the first task and then enqueue a number
70+
cancellationSource.Cancel();
71+
await inputQueue.EnqueueAsync(1);
72+
73+
// Did the second task get the number?
74+
Assert.Equal(TaskStatus.Canceled, taskOne.Status);
75+
Assert.Equal(TaskStatus.RanToCompletion, taskTwo.Status);
76+
Assert.Equal(1, taskTwo.Result);
77+
}
78+
5979
private async Task ConsumeItems(
6080
AsyncQueue<int> inputQueue,
6181
ConcurrentBag<int> outputItems,

0 commit comments

Comments
 (0)