@@ -961,7 +961,7 @@ public async Task CompletedEndPointEndsConnection()
961
961
}
962
962
963
963
[ Fact ]
964
- public async Task SynchronusExceptionEndsConnection ( )
964
+ public async Task SynchronousExceptionEndsConnection ( )
965
965
{
966
966
bool ExpectedErrors ( WriteContext writeContext )
967
967
{
@@ -2269,6 +2269,173 @@ bool ExpectedErrors(WriteContext writeContext)
2269
2269
}
2270
2270
}
2271
2271
2272
+ [ Fact ]
2273
+ public async Task LongPollingConnectionClosingTriggersConnectionClosedToken ( )
2274
+ {
2275
+ using ( StartVerifiableLog ( ) )
2276
+ {
2277
+ var manager = CreateConnectionManager ( LoggerFactory ) ;
2278
+ var pipeOptions = new PipeOptions ( pauseWriterThreshold : 2 , resumeWriterThreshold : 1 ) ;
2279
+ var connection = manager . CreateConnection ( pipeOptions , pipeOptions ) ;
2280
+ connection . TransportType = HttpTransportType . LongPolling ;
2281
+
2282
+ var dispatcher = new HttpConnectionDispatcher ( manager , LoggerFactory ) ;
2283
+
2284
+ var context = MakeRequest ( "/foo" , connection ) ;
2285
+
2286
+ var services = new ServiceCollection ( ) ;
2287
+ services . AddSingleton < NeverEndingConnectionHandler > ( ) ;
2288
+ var builder = new ConnectionBuilder ( services . BuildServiceProvider ( ) ) ;
2289
+ builder . UseConnectionHandler < NeverEndingConnectionHandler > ( ) ;
2290
+ var app = builder . Build ( ) ;
2291
+ var options = new HttpConnectionDispatcherOptions ( ) ;
2292
+
2293
+ var pollTask = dispatcher . ExecuteAsync ( context , options , app ) ;
2294
+ Assert . True ( pollTask . IsCompleted ) ;
2295
+
2296
+ // Now send the second poll
2297
+ pollTask = dispatcher . ExecuteAsync ( context , options , app ) ;
2298
+
2299
+ // Issue the delete request and make sure the poll completes
2300
+ var deleteContext = new DefaultHttpContext ( ) ;
2301
+ deleteContext . Request . Path = "/foo" ;
2302
+ deleteContext . Request . QueryString = new QueryString ( $ "?id={ connection . ConnectionId } ") ;
2303
+ deleteContext . Request . Method = "DELETE" ;
2304
+
2305
+ Assert . False ( pollTask . IsCompleted ) ;
2306
+
2307
+ await dispatcher . ExecuteAsync ( deleteContext , options , app ) . OrTimeout ( ) ;
2308
+
2309
+ await pollTask . OrTimeout ( ) ;
2310
+
2311
+ // Verify that transport shuts down
2312
+ await connection . TransportTask . OrTimeout ( ) ;
2313
+
2314
+ // Verify the response from the DELETE request
2315
+ Assert . Equal ( StatusCodes . Status202Accepted , deleteContext . Response . StatusCode ) ;
2316
+ Assert . Equal ( "text/plain" , deleteContext . Response . ContentType ) ;
2317
+ Assert . Equal ( HttpConnectionStatus . Disposed , connection . Status ) ;
2318
+
2319
+ // Verify the connection not removed because application is hanging
2320
+ Assert . True ( manager . TryGetConnection ( connection . ConnectionId , out _ ) ) ;
2321
+
2322
+ Assert . True ( connection . ConnectionClosed . IsCancellationRequested ) ;
2323
+ }
2324
+ }
2325
+
2326
+ [ Fact ]
2327
+ public async Task SSEConnectionClosingTriggersConnectionClosedToken ( )
2328
+ {
2329
+ using ( StartVerifiableLog ( ) )
2330
+ {
2331
+ var manager = CreateConnectionManager ( LoggerFactory ) ;
2332
+ var connection = manager . CreateConnection ( ) ;
2333
+ connection . TransportType = HttpTransportType . ServerSentEvents ;
2334
+ var dispatcher = new HttpConnectionDispatcher ( manager , LoggerFactory ) ;
2335
+ var context = MakeRequest ( "/foo" , connection ) ;
2336
+ SetTransport ( context , connection . TransportType ) ;
2337
+ var services = new ServiceCollection ( ) ;
2338
+ services . AddSingleton < NeverEndingConnectionHandler > ( ) ;
2339
+ var builder = new ConnectionBuilder ( services . BuildServiceProvider ( ) ) ;
2340
+ builder . UseConnectionHandler < NeverEndingConnectionHandler > ( ) ;
2341
+ var app = builder . Build ( ) ;
2342
+ var options = new HttpConnectionDispatcherOptions ( ) ;
2343
+ _ = dispatcher . ExecuteAsync ( context , options , app ) ;
2344
+
2345
+ // Close the SSE connection
2346
+ connection . Transport . Output . Complete ( ) ;
2347
+
2348
+ var tcs = new TaskCompletionSource < object > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
2349
+ connection . ConnectionClosed . Register ( ( ) => tcs . SetResult ( null ) ) ;
2350
+ await tcs . Task . OrTimeout ( ) ;
2351
+ }
2352
+ }
2353
+
2354
+ [ Fact ]
2355
+ public async Task WebSocketConnectionClosingTriggersConnectionClosedToken ( )
2356
+ {
2357
+ using ( StartVerifiableLog ( ) )
2358
+ {
2359
+ var manager = CreateConnectionManager ( LoggerFactory ) ;
2360
+ var connection = manager . CreateConnection ( ) ;
2361
+ connection . TransportType = HttpTransportType . WebSockets ;
2362
+
2363
+ var dispatcher = new HttpConnectionDispatcher ( manager , LoggerFactory ) ;
2364
+
2365
+ var context = MakeRequest ( "/foo" , connection ) ;
2366
+ SetTransport ( context , HttpTransportType . WebSockets ) ;
2367
+
2368
+ var services = new ServiceCollection ( ) ;
2369
+ services . AddSingleton < NeverEndingConnectionHandler > ( ) ;
2370
+ var builder = new ConnectionBuilder ( services . BuildServiceProvider ( ) ) ;
2371
+ builder . UseConnectionHandler < NeverEndingConnectionHandler > ( ) ;
2372
+ var app = builder . Build ( ) ;
2373
+ var options = new HttpConnectionDispatcherOptions ( ) ;
2374
+ options . WebSockets . CloseTimeout = TimeSpan . FromSeconds ( 1 ) ;
2375
+
2376
+ _ = dispatcher . ExecuteAsync ( context , options , app ) ;
2377
+
2378
+ var websocket = ( TestWebSocketConnectionFeature ) context . Features . Get < IHttpWebSocketFeature > ( ) ;
2379
+ await websocket . Accepted . OrTimeout ( ) ;
2380
+ await websocket . Client . CloseOutputAsync ( WebSocketCloseStatus . NormalClosure , "" , cancellationToken : default ) . OrTimeout ( ) ;
2381
+
2382
+ var tcs = new TaskCompletionSource < object > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
2383
+ connection . ConnectionClosed . Register ( ( ) => tcs . SetResult ( null ) ) ;
2384
+ await tcs . Task . OrTimeout ( ) ;
2385
+ }
2386
+ }
2387
+
2388
+ public class CustomHttpRequestLifetimeFeature : IHttpRequestLifetimeFeature
2389
+ {
2390
+ public CancellationToken RequestAborted { get ; set ; }
2391
+
2392
+ private CancellationTokenSource _cts ;
2393
+ public CustomHttpRequestLifetimeFeature ( )
2394
+ {
2395
+ _cts = new CancellationTokenSource ( ) ;
2396
+ RequestAborted = _cts . Token ;
2397
+ }
2398
+
2399
+ public void Abort ( )
2400
+ {
2401
+ _cts . Cancel ( ) ;
2402
+ }
2403
+ }
2404
+
2405
+ [ Fact ]
2406
+ public async Task AbortingConnectionAbortsHttpContextAndTriggersConnectionClosedToken ( )
2407
+ {
2408
+ using ( StartVerifiableLog ( ) )
2409
+ {
2410
+ var manager = CreateConnectionManager ( LoggerFactory ) ;
2411
+ var connection = manager . CreateConnection ( ) ;
2412
+ connection . TransportType = HttpTransportType . ServerSentEvents ;
2413
+ var dispatcher = new HttpConnectionDispatcher ( manager , LoggerFactory ) ;
2414
+ var context = MakeRequest ( "/foo" , connection ) ;
2415
+ var lifetimeFeature = new CustomHttpRequestLifetimeFeature ( ) ;
2416
+ context . Features . Set < IHttpRequestLifetimeFeature > ( lifetimeFeature ) ;
2417
+ SetTransport ( context , connection . TransportType ) ;
2418
+
2419
+ var services = new ServiceCollection ( ) ;
2420
+ services . AddSingleton < NeverEndingConnectionHandler > ( ) ;
2421
+ var builder = new ConnectionBuilder ( services . BuildServiceProvider ( ) ) ;
2422
+ builder . UseConnectionHandler < NeverEndingConnectionHandler > ( ) ;
2423
+ var app = builder . Build ( ) ;
2424
+ var options = new HttpConnectionDispatcherOptions ( ) ;
2425
+ _ = dispatcher . ExecuteAsync ( context , options , app ) ;
2426
+
2427
+ connection . Abort ( ) ;
2428
+
2429
+ var tcs = new TaskCompletionSource < object > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
2430
+ connection . ConnectionClosed . Register ( ( ) => tcs . SetResult ( null ) ) ;
2431
+ await tcs . Task . OrTimeout ( ) ;
2432
+
2433
+ tcs = new TaskCompletionSource < object > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
2434
+ lifetimeFeature . RequestAborted . Register ( ( ) => tcs . SetResult ( null ) ) ;
2435
+ await tcs . Task . OrTimeout ( ) ;
2436
+ }
2437
+ }
2438
+
2272
2439
private static async Task CheckTransportSupported ( HttpTransportType supportedTransports , HttpTransportType transportType , int status , ILoggerFactory loggerFactory )
2273
2440
{
2274
2441
var manager = CreateConnectionManager ( loggerFactory ) ;
0 commit comments