Skip to content

Commit 89c2ef3

Browse files
authored
Optimize CancellationToken handling (#51660)
1 parent 4c1262d commit 89c2ef3

File tree

15 files changed

+57
-123
lines changed

15 files changed

+57
-123
lines changed

src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ public IWebHostBuilder Configure(Action<IApplicationBuilder> configure)
324324
{
325325
services.Configure<GenericWebHostServiceOptions>(options =>
326326
{
327-
options.ConfigureApplication = app => configure(app);
327+
options.ConfigureApplication = configure;
328328
});
329329
}
330330
});

src/Hosting/Hosting/src/Internal/ApplicationLifetime.cs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public void StopApplication()
5858
{
5959
try
6060
{
61-
ExecuteHandlers(_stoppingSource);
61+
_stoppingSource.Cancel();
6262
}
6363
catch (Exception ex)
6464
{
@@ -76,7 +76,7 @@ public void NotifyStarted()
7676
{
7777
try
7878
{
79-
ExecuteHandlers(_startedSource);
79+
_startedSource.Cancel();
8080
}
8181
catch (Exception ex)
8282
{
@@ -93,7 +93,7 @@ public void NotifyStopped()
9393
{
9494
try
9595
{
96-
ExecuteHandlers(_stoppedSource);
96+
_stoppedSource.Cancel();
9797
}
9898
catch (Exception ex)
9999
{
@@ -102,16 +102,4 @@ public void NotifyStopped()
102102
ex);
103103
}
104104
}
105-
106-
private static void ExecuteHandlers(CancellationTokenSource cancel)
107-
{
108-
// Noop if this is already cancelled
109-
if (cancel.IsCancellationRequested)
110-
{
111-
return;
112-
}
113-
114-
// Run the cancellation token callbacks
115-
cancel.Cancel(throwOnFirstException: false);
116-
}
117105
}

src/Hosting/Hosting/src/Internal/HostedServiceExecutor.cs

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,39 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.Extensions.Hosting;
5-
using Microsoft.Extensions.Logging;
65

76
namespace Microsoft.AspNetCore.Hosting;
87

98
internal sealed class HostedServiceExecutor
109
{
1110
private readonly IEnumerable<IHostedService> _services;
12-
private readonly ILogger<HostedServiceExecutor> _logger;
1311

14-
public HostedServiceExecutor(ILogger<HostedServiceExecutor> logger, IEnumerable<IHostedService> services)
12+
public HostedServiceExecutor(IEnumerable<IHostedService> services)
1513
{
16-
_logger = logger;
1714
_services = services;
1815
}
1916

20-
public Task StartAsync(CancellationToken token)
17+
public async Task StartAsync(CancellationToken token)
2118
{
22-
return ExecuteAsync(service => service.StartAsync(token));
23-
}
24-
25-
public Task StopAsync(CancellationToken token)
26-
{
27-
return ExecuteAsync(service => service.StopAsync(token), throwOnFirstFailure: false);
19+
foreach (var service in _services)
20+
{
21+
await service.StartAsync(token);
22+
}
2823
}
2924

30-
private async Task ExecuteAsync(Func<IHostedService, Task> callback, bool throwOnFirstFailure = true)
25+
public async Task StopAsync(CancellationToken token)
3126
{
3227
List<Exception>? exceptions = null;
3328

3429
foreach (var service in _services)
3530
{
3631
try
3732
{
38-
await callback(service);
33+
await service.StopAsync(token);
3934
}
4035
catch (Exception ex)
4136
{
42-
if (throwOnFirstFailure)
43-
{
44-
throw;
45-
}
46-
47-
if (exceptions == null)
48-
{
49-
exceptions = new List<Exception>();
50-
}
51-
37+
exceptions ??= [];
5238
exceptions.Add(ex);
5339
}
5440
}

src/Hosting/Hosting/src/Internal/WebHost.cs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -286,16 +286,9 @@ public async Task StopAsync(CancellationToken cancellationToken = default)
286286

287287
Log.Shutdown(_logger);
288288

289-
using var timeoutCTS = new CancellationTokenSource(Options.ShutdownTimeout);
290-
var timeoutToken = timeoutCTS.Token;
291-
if (!cancellationToken.CanBeCanceled)
292-
{
293-
cancellationToken = timeoutToken;
294-
}
295-
else
296-
{
297-
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken).Token;
298-
}
289+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
290+
cts.CancelAfter(Options.ShutdownTimeout);
291+
cancellationToken = cts.Token;
299292

300293
// Fire IApplicationLifetime.Stopping
301294
_applicationLifetime?.StopApplication();
@@ -340,17 +333,17 @@ public async ValueTask DisposeAsync()
340333
await DisposeServiceProviderAsync(_hostingServiceProvider).ConfigureAwait(false);
341334
}
342335

343-
private static async ValueTask DisposeServiceProviderAsync(IServiceProvider? serviceProvider)
336+
private static ValueTask DisposeServiceProviderAsync(IServiceProvider? serviceProvider)
344337
{
345338
switch (serviceProvider)
346339
{
347340
case IAsyncDisposable asyncDisposable:
348-
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
349-
break;
341+
return asyncDisposable.DisposeAsync();
350342
case IDisposable disposable:
351343
disposable.Dispose();
352344
break;
353345
}
346+
return default;
354347
}
355348

356349
private static partial class Log

src/Hosting/Hosting/src/Startup/ConventionBasedStartup.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,9 @@ public void Configure(IApplicationBuilder app)
2323
{
2424
_methods.ConfigureDelegate(app);
2525
}
26-
catch (Exception ex)
26+
catch (TargetInvocationException ex)
2727
{
28-
if (ex is TargetInvocationException)
29-
{
30-
ExceptionDispatchInfo.Capture(ex.InnerException!).Throw();
31-
}
32-
28+
ExceptionDispatchInfo.Capture(ex.InnerException!).Throw();
3329
throw;
3430
}
3531
}
@@ -40,13 +36,9 @@ public IServiceProvider ConfigureServices(IServiceCollection services)
4036
{
4137
return _methods.ConfigureServicesDelegate(services);
4238
}
43-
catch (Exception ex)
39+
catch (TargetInvocationException ex)
4440
{
45-
if (ex is TargetInvocationException)
46-
{
47-
ExceptionDispatchInfo.Capture(ex.InnerException!).Throw();
48-
}
49-
41+
ExceptionDispatchInfo.Capture(ex.InnerException!).Throw();
5042
throw;
5143
}
5244
}

src/Hosting/Hosting/src/WebHostBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action
4646
{
4747
services.AddSingleton<IStartup>(sp =>
4848
{
49-
return new DelegateStartup(sp.GetRequiredService<IServiceProviderFactory<IServiceCollection>>(), (app => configureApp(app)));
49+
return new DelegateStartup(sp.GetRequiredService<IServiceProviderFactory<IServiceCollection>>(), configureApp);
5050
});
5151
});
5252
}

src/Hosting/Hosting/src/WebHostExtensions.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,8 @@ private static async Task WaitForTokenShutdownAsync(this IWebHost host, Cancella
159159
},
160160
applicationLifetime);
161161

162-
var waitForStop = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
163-
applicationLifetime.ApplicationStopping.Register(obj =>
164-
{
165-
var tcs = (TaskCompletionSource)obj!;
166-
tcs.TrySetResult();
167-
}, waitForStop);
168-
169-
await waitForStop.Task;
162+
await Task.Delay(Timeout.Infinite, applicationLifetime.ApplicationStopping)
163+
.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext);
170164

171165
// WebHost will use its default ShutdownTimeout if none is specified.
172166
#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods. StopAsync should not be canceled by the token to RunAsync.

src/Http/Http/src/Timeouts/RequestTimeoutsMiddleware.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,9 @@ async Task SetTimeoutAsync()
107107
await _next(context);
108108
}
109109
catch (OperationCanceledException operationCanceledException)
110+
when (linkedCts.IsCancellationRequested && !originalToken.IsCancellationRequested)
110111
{
111-
if (context.Response.HasStarted ||
112-
!linkedCts.Token.IsCancellationRequested ||
113-
originalToken.IsCancellationRequested)
112+
if (context.Response.HasStarted)
114113
{
115114
// We can't produce a response, or it wasn't our timeout that caused this.
116115
throw;

src/Middleware/Session/src/DistributedSession.cs

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,9 @@ public async Task LoadAsync(CancellationToken cancellationToken = default)
218218
// This will throw if called directly and a failure occurs. The user is expected to handle the failures.
219219
if (!_loaded)
220220
{
221-
using (var timeout = new CancellationTokenSource(_ioTimeout))
221+
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
222222
{
223-
var cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken);
223+
cts.CancelAfter(_ioTimeout);
224224
try
225225
{
226226
cts.Token.ThrowIfCancellationRequested();
@@ -234,14 +234,10 @@ public async Task LoadAsync(CancellationToken cancellationToken = default)
234234
_logger.AccessingExpiredSession(_sessionKey);
235235
}
236236
}
237-
catch (OperationCanceledException oex)
237+
catch (OperationCanceledException oex) when (!cancellationToken.IsCancellationRequested && cts.IsCancellationRequested)
238238
{
239-
if (timeout.Token.IsCancellationRequested)
240-
{
241-
_logger.SessionLoadingTimeout();
242-
throw new OperationCanceledException("Timed out loading the session.", oex, timeout.Token);
243-
}
244-
throw;
239+
_logger.SessionLoadingTimeout();
240+
throw new OperationCanceledException("Timed out loading the session.", oex, cts.Token);
245241
}
246242
}
247243
_isAvailable = true;
@@ -252,9 +248,9 @@ public async Task LoadAsync(CancellationToken cancellationToken = default)
252248
/// <inheritdoc />
253249
public async Task CommitAsync(CancellationToken cancellationToken = default)
254250
{
255-
using (var timeout = new CancellationTokenSource(_ioTimeout))
251+
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
256252
{
257-
var cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken);
253+
cts.CancelAfter(_ioTimeout);
258254
if (_isModified)
259255
{
260256
if (_logger.IsEnabled(LogLevel.Information))
@@ -293,14 +289,10 @@ await _cache.SetAsync(
293289
_isModified = false;
294290
_logger.SessionStored(_sessionKey, Id, _store.Count);
295291
}
296-
catch (OperationCanceledException oex)
292+
catch (OperationCanceledException oex) when (!cancellationToken.IsCancellationRequested && cts.IsCancellationRequested)
297293
{
298-
if (timeout.Token.IsCancellationRequested)
299-
{
300-
_logger.SessionCommitTimeout();
301-
throw new OperationCanceledException("Timed out committing the session.", oex, timeout.Token);
302-
}
303-
throw;
294+
_logger.SessionCommitTimeout();
295+
throw new OperationCanceledException("Timed out committing the session.", oex, cts.Token);
304296
}
305297
}
306298
else
@@ -309,14 +301,10 @@ await _cache.SetAsync(
309301
{
310302
await _cache.RefreshAsync(_sessionKey, cts.Token);
311303
}
312-
catch (OperationCanceledException oex)
304+
catch (OperationCanceledException oex) when (!cancellationToken.IsCancellationRequested && cts.IsCancellationRequested)
313305
{
314-
if (timeout.Token.IsCancellationRequested)
315-
{
316-
_logger.SessionRefreshTimeout();
317-
throw new OperationCanceledException("Timed out refreshing the session.", oex, timeout.Token);
318-
}
319-
throw;
306+
_logger.SessionRefreshTimeout();
307+
throw new OperationCanceledException("Timed out refreshing the session.", oex, cts.Token);
320308
}
321309
}
322310
}

src/Middleware/Spa/SpaProxy/src/SpaProxyLaunchManager.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ public async Task<bool> IsSpaProxyRunning(CancellationToken cancellationToken)
7474
{
7575
var httpClient = CreateHttpClient();
7676

77-
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10));
78-
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken);
77+
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
78+
cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
7979
try
8080
{
8181
var response = await httpClient.GetAsync(_options.ServerUrl, cancellationTokenSource.Token);
@@ -105,8 +105,8 @@ private static HttpClient CreateHttpClient()
105105

106106
private async Task<bool> ProbeSpaDevelopmentServerUrl(HttpClient httpClient, CancellationToken cancellationToken)
107107
{
108-
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10));
109-
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken);
108+
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
109+
cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
110110
try
111111
{
112112
var response = await httpClient.GetAsync(_options.ServerUrl, cancellationTokenSource.Token);

0 commit comments

Comments
 (0)