@@ -12,25 +12,6 @@ internal static class TaskExtensions
1212{
1313 // suboptimal polyfill version of the .NET 6+ API; I'm not recommending this for production use,
1414 // but it's good enough for tests
15- public static Task < T > WaitAsync < T > ( this Task < T > task , CancellationToken cancellationToken )
16- {
17- if ( task . IsCompleted || ! cancellationToken . CanBeCanceled ) return task ;
18- return Wrap ( task , cancellationToken ) ;
19-
20- static async Task < T > Wrap ( Task < T > task , CancellationToken cancellationToken )
21- {
22- var tcs = new TaskCompletionSource < T > ( ) ;
23- using var reg = cancellationToken . Register ( ( ) => tcs . TrySetCanceled ( cancellationToken ) ) ;
24- _ = task . ContinueWith ( t =>
25- {
26- if ( t . IsCanceled ) tcs . TrySetCanceled ( ) ;
27- else if ( t . IsFaulted ) tcs . TrySetException ( t . Exception ! ) ;
28- else tcs . TrySetResult ( t . Result ) ;
29- } ) ;
30- return await tcs . Task ;
31- }
32- }
33-
3415 public static Task < T > WaitAsync < T > ( this Task < T > task , TimeSpan timeout )
3516 {
3617 if ( task . IsCompleted ) return task ;
@@ -92,6 +73,11 @@ private void Pause(IDatabase db)
9273 db . Execute ( "client" , new object [ ] { "pause" , ConnectionPauseMilliseconds } , CommandFlags . FireAndForget ) ;
9374 }
9475
76+ private void Pause ( IServer server )
77+ {
78+ server . Execute ( "client" , new object [ ] { "pause" , ConnectionPauseMilliseconds } , CommandFlags . FireAndForget ) ;
79+ }
80+
9581 [ Fact ]
9682 public async Task WithTimeout_ShortTimeout_Async_ThrowsOperationCanceledException ( )
9783 {
@@ -195,4 +181,38 @@ public async Task CancellationDuringOperation_Async_CancelsGracefully(CancelStra
195181 Assert . Equal ( cts . Token , oce . CancellationToken ) ;
196182 }
197183 }
184+
185+ [ Fact ]
186+ public async Task ScanCancellable ( )
187+ {
188+ using var conn = Create ( ) ;
189+ var db = conn . GetDatabase ( ) ;
190+ var server = conn . GetServer ( conn . GetEndPoints ( ) [ 0 ] ) ;
191+
192+ using var cts = new CancellationTokenSource ( ) ;
193+
194+ var watch = Stopwatch . StartNew ( ) ;
195+ Pause ( server ) ;
196+ try
197+ {
198+ db . StringSet ( Me ( ) , "value" , TimeSpan . FromMinutes ( 5 ) , flags : CommandFlags . FireAndForget ) ;
199+ await using var iter = server . KeysAsync ( pageSize : 1000 ) . WithCancellation ( cts . Token ) . GetAsyncEnumerator ( ) ;
200+ var pending = iter . MoveNextAsync ( ) ;
201+ Assert . False ( cts . Token . IsCancellationRequested ) ;
202+ cts . CancelAfter ( ShortDelayMilliseconds ) ; // start this *after* we've got past the initial check
203+ while ( await pending )
204+ {
205+ pending = iter . MoveNextAsync ( ) ;
206+ }
207+ Assert . Fail ( $ "{ ExpectedCancel } : { watch . ElapsedMilliseconds } ms") ;
208+ }
209+ catch ( OperationCanceledException oce )
210+ {
211+ var taken = watch . ElapsedMilliseconds ;
212+ // Expected if cancellation happens during operation
213+ Log ( $ "Cancelled after { taken } ms") ;
214+ Assert . True ( taken < ConnectionPauseMilliseconds / 2 , "Should have cancelled much sooner" ) ;
215+ Assert . Equal ( cts . Token , oce . CancellationToken ) ;
216+ }
217+ }
198218}
0 commit comments