Skip to content

Commit 51c5309

Browse files
authored
Merge pull request #464 from fsprojects/repo-assist/improve-cancellation-test-coverage-95cc9a62f209c4b1
[repo-assist] tests: add coverage for side-effect re-execution with independent cancellation tokens
2 parents 7d5ca2d + 1afd1ae commit 51c5309

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

release-notes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Release notes:
33

44
Unreleased
5+
- tests: add coverage for side-effect re-execution semantics when re-enumerating a `taskSeq` with independent `CancellationToken`s
56
- test: add TaskSeq.Issue452.Tests.fs, regression tests wrapping an externally-produced IAsyncEnumerable<'T> (TaskSeq.map and `taskSeq { for .. in .. do yield .. }`) while running on a custom, single-threaded TaskScheduler; investigates #452's reported duplicated-final-item bug, which could not be reproduced outside of Orleans, see #452
67
- adds TaskSeq.tryMax and TaskSeq.tryMin: safe variants of TaskSeq.max and TaskSeq.min that return None instead of raising ArgumentException when the input sequence is empty
78
- test: rename `SideEffect` module to `SideEffects` in TaskSeq.Concat.Tests.fs, TaskSeq.Delay.Tests.fs, and TaskSeq.Item.Tests.fs for consistency with the rest of the test suite (50+ files already use the plural form)

src/FSharp.Control.TaskSeq.Test/TaskSeq.CancellationToken.Tests.fs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module TaskSeq.Tests.CancellationToken
22

33
open System
4+
open System.Collections.Generic
45
open System.Threading
56
open System.Threading.Tasks
67

@@ -152,3 +153,82 @@ module Cancellation =
152153
hasNext |> should be True
153154
enum2.Current |> should equal 1
154155
}
156+
157+
module SideEffects =
158+
159+
[<Fact>]
160+
let ``Cancelling one enumerator does not affect side effects of a fresh enumerator over the same taskSeq`` () = task {
161+
let mutable itemsProduced = 0
162+
163+
let source = taskSeq {
164+
for i in 1..5 do
165+
itemsProduced <- itemsProduced + 1
166+
yield i
167+
}
168+
169+
// fully consume with a first, never-cancelled enumerator
170+
use cts1 = new CancellationTokenSource()
171+
use enum1 = source.GetAsyncEnumerator(cts1.Token)
172+
let mutable canContinue = true
173+
174+
while canContinue do
175+
let! hasNext = enum1.MoveNextAsync()
176+
177+
if not hasNext then
178+
canContinue <- false
179+
180+
itemsProduced |> should equal 5
181+
182+
// cancel and dispose that first enumerator explicitly, then re-enumerate the same
183+
// taskSeq from scratch with a fresh, non-cancelled token
184+
cts1.Cancel()
185+
do! enum1.DisposeAsync()
186+
187+
use cts2 = new CancellationTokenSource()
188+
use enum2 = source.GetAsyncEnumerator(cts2.Token)
189+
let! hasNext = enum2.MoveNextAsync()
190+
191+
// re-enumeration re-runs the body from scratch: side effects accumulate further,
192+
// and the previous enumerator's cancellation has no bearing on this fresh one
193+
hasNext |> should be True
194+
enum2.Current |> should equal 1
195+
itemsProduced |> should equal 6
196+
}
197+
198+
[<Fact>]
199+
let ``A CancellationToken passed to GetAsyncEnumerator does not prevent re-iteration with a different token`` () = task {
200+
let mutable totalCalls = 0
201+
202+
let source = taskSeq {
203+
for i in 1..3 do
204+
totalCalls <- totalCalls + 1
205+
yield i
206+
}
207+
208+
let drain (enum: IAsyncEnumerator<int>) = task {
209+
let items = ResizeArray()
210+
let mutable canContinue = true
211+
212+
while canContinue do
213+
let! hasNext = enum.MoveNextAsync()
214+
215+
if hasNext then
216+
items.Add enum.Current
217+
else
218+
canContinue <- false
219+
220+
return List.ofSeq items
221+
}
222+
223+
use cts = new CancellationTokenSource()
224+
use enum1 = source.GetAsyncEnumerator(cts.Token)
225+
let! first = drain enum1
226+
first |> should equal [ 1; 2; 3 ]
227+
totalCalls |> should equal 3
228+
229+
// re-iterate using CancellationToken.None: side effects re-run independently
230+
use enum2 = source.GetAsyncEnumerator(CancellationToken.None)
231+
let! second = drain enum2
232+
second |> should equal [ 1; 2; 3 ]
233+
totalCalls |> should equal 6
234+
}

0 commit comments

Comments
 (0)