Skip to content

Commit 1810cfe

Browse files
authored
Flow Kestrel CancellationTokens to BindAsync (#31377)
1 parent 43ebb3b commit 1810cfe

File tree

9 files changed

+84
-43
lines changed

9 files changed

+84
-43
lines changed

src/Servers/Kestrel/Core/src/AnyIPListenOptions.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics;
66
using System.IO;
77
using System.Net;
8+
using System.Threading;
89
using System.Threading.Tasks;
910
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
1011
using Microsoft.Extensions.Logging;
@@ -18,22 +19,22 @@ internal AnyIPListenOptions(int port)
1819
{
1920
}
2021

21-
internal override async Task BindAsync(AddressBindContext context)
22+
internal override async Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
2223
{
2324
Debug.Assert(IPEndPoint != null);
2425

2526
// when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
2627
try
2728
{
28-
await base.BindAsync(context).ConfigureAwait(false);
29+
await base.BindAsync(context, cancellationToken).ConfigureAwait(false);
2930
}
3031
catch (Exception ex) when (!(ex is IOException))
3132
{
3233
context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(IPEndPoint.Port));
3334

3435
// for machines that do not support IPv6
3536
EndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.Port);
36-
await base.BindAsync(context).ConfigureAwait(false);
37+
await base.BindAsync(context, cancellationToken).ConfigureAwait(false);
3738
}
3839
}
3940
}

src/Servers/Kestrel/Core/src/Internal/AddressBindContext.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Threading;
67
using System.Threading.Tasks;
78
using Microsoft.Extensions.Logging;
89

@@ -14,7 +15,7 @@ public AddressBindContext(
1415
ServerAddressesFeature serverAddressesFeature,
1516
KestrelServerOptions serverOptions,
1617
ILogger logger,
17-
Func<ListenOptions, Task> createBinding)
18+
Func<ListenOptions, CancellationToken, Task> createBinding)
1819
{
1920
ServerAddressesFeature = serverAddressesFeature;
2021
ServerOptions = serverOptions;
@@ -28,6 +29,6 @@ public AddressBindContext(
2829
public KestrelServerOptions ServerOptions { get; }
2930
public ILogger Logger { get; }
3031

31-
public Func<ListenOptions, Task> CreateBinding { get; }
32+
public Func<ListenOptions, CancellationToken, Task> CreateBinding { get; }
3233
}
3334
}

src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs

+17-16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.IO;
88
using System.Linq;
99
using System.Net;
10+
using System.Threading;
1011
using System.Threading.Tasks;
1112
using Microsoft.AspNetCore.Builder;
1213
using Microsoft.AspNetCore.Connections;
@@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
2021
{
2122
internal class AddressBinder
2223
{
23-
public static async Task BindAsync(IEnumerable<ListenOptions> listenOptions, AddressBindContext context)
24+
public static async Task BindAsync(IEnumerable<ListenOptions> listenOptions, AddressBindContext context, CancellationToken cancellationToken)
2425
{
2526
var strategy = CreateStrategy(
2627
listenOptions.ToArray(),
@@ -32,7 +33,7 @@ public static async Task BindAsync(IEnumerable<ListenOptions> listenOptions, Add
3233
context.ServerOptions.OptionsInUse.Clear();
3334
context.Addresses.Clear();
3435

35-
await strategy.BindAsync(context).ConfigureAwait(false);
36+
await strategy.BindAsync(context, cancellationToken).ConfigureAwait(false);
3637
}
3738

3839
private static IStrategy CreateStrategy(ListenOptions[] listenOptions, string[] addresses, bool preferAddresses)
@@ -86,11 +87,11 @@ protected internal static bool TryCreateIPEndPoint(BindingAddress address, [NotN
8687
return true;
8788
}
8889

89-
internal static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
90+
internal static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
9091
{
9192
try
9293
{
93-
await context.CreateBinding(endpoint).ConfigureAwait(false);
94+
await context.CreateBinding(endpoint, cancellationToken).ConfigureAwait(false);
9495
}
9596
catch (AddressInUseException ex)
9697
{
@@ -144,24 +145,24 @@ internal static ListenOptions ParseAddress(string address, out bool https)
144145

145146
private interface IStrategy
146147
{
147-
Task BindAsync(AddressBindContext context);
148+
Task BindAsync(AddressBindContext context, CancellationToken cancellationToken);
148149
}
149150

150151
private class DefaultAddressStrategy : IStrategy
151152
{
152-
public async Task BindAsync(AddressBindContext context)
153+
public async Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
153154
{
154155
var httpDefault = ParseAddress(Constants.DefaultServerAddress, out _);
155156
context.ServerOptions.ApplyEndpointDefaults(httpDefault);
156-
await httpDefault.BindAsync(context).ConfigureAwait(false);
157+
await httpDefault.BindAsync(context, cancellationToken).ConfigureAwait(false);
157158

158159
// Conditional https default, only if a cert is available
159160
var httpsDefault = ParseAddress(Constants.DefaultServerHttpsAddress, out _);
160161
context.ServerOptions.ApplyEndpointDefaults(httpsDefault);
161162

162163
if (httpsDefault.IsTls || httpsDefault.TryUseHttps())
163164
{
164-
await httpsDefault.BindAsync(context).ConfigureAwait(false);
165+
await httpsDefault.BindAsync(context, cancellationToken).ConfigureAwait(false);
165166
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddresses,
166167
Constants.DefaultServerAddress, Constants.DefaultServerHttpsAddress);
167168
}
@@ -180,12 +181,12 @@ public OverrideWithAddressesStrategy(IReadOnlyCollection<string> addresses)
180181
{
181182
}
182183

183-
public override Task BindAsync(AddressBindContext context)
184+
public override Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
184185
{
185186
var joined = string.Join(", ", _addresses);
186187
context.Logger.LogInformation(CoreStrings.OverridingWithPreferHostingUrls, nameof(IServerAddressesFeature.PreferHostingUrls), joined);
187188

188-
return base.BindAsync(context);
189+
return base.BindAsync(context, cancellationToken);
189190
}
190191
}
191192

@@ -199,12 +200,12 @@ public OverrideWithEndpointsStrategy(IReadOnlyCollection<ListenOptions> endpoint
199200
_originalAddresses = originalAddresses;
200201
}
201202

202-
public override Task BindAsync(AddressBindContext context)
203+
public override Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
203204
{
204205
var joined = string.Join(", ", _originalAddresses);
205206
context.Logger.LogWarning(CoreStrings.OverridingWithKestrelOptions, joined);
206207

207-
return base.BindAsync(context);
208+
return base.BindAsync(context, cancellationToken);
208209
}
209210
}
210211

@@ -217,11 +218,11 @@ public EndpointsStrategy(IReadOnlyCollection<ListenOptions> endpoints)
217218
_endpoints = endpoints;
218219
}
219220

220-
public virtual async Task BindAsync(AddressBindContext context)
221+
public virtual async Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
221222
{
222223
foreach (var endpoint in _endpoints)
223224
{
224-
await endpoint.BindAsync(context).ConfigureAwait(false);
225+
await endpoint.BindAsync(context, cancellationToken).ConfigureAwait(false);
225226
}
226227
}
227228
}
@@ -235,7 +236,7 @@ public AddressesStrategy(IReadOnlyCollection<string> addresses)
235236
_addresses = addresses;
236237
}
237238

238-
public virtual async Task BindAsync(AddressBindContext context)
239+
public virtual async Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
239240
{
240241
foreach (var address in _addresses)
241242
{
@@ -247,7 +248,7 @@ public virtual async Task BindAsync(AddressBindContext context)
247248
options.UseHttps();
248249
}
249250

250-
await options.BindAsync(context).ConfigureAwait(false);
251+
await options.BindAsync(context, cancellationToken).ConfigureAwait(false);
251252
}
252253
}
253254
}

src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,19 @@ public TransportManager(
3737
private ConnectionManager ConnectionManager => _serviceContext.ConnectionManager;
3838
private IKestrelTrace Trace => _serviceContext.Log;
3939

40-
public async Task<EndPoint> BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig? endpointConfig)
40+
public async Task<EndPoint> BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig? endpointConfig, CancellationToken cancellationToken)
4141
{
4242
if (_transportFactory is null)
4343
{
4444
throw new InvalidOperationException($"Cannot bind with {nameof(ConnectionDelegate)} no {nameof(IConnectionListenerFactory)} is registered.");
4545
}
4646

47-
var transport = await _transportFactory.BindAsync(endPoint).ConfigureAwait(false);
47+
var transport = await _transportFactory.BindAsync(endPoint, cancellationToken).ConfigureAwait(false);
4848
StartAcceptLoop(new GenericConnectionListener(transport), c => connectionDelegate(c), endpointConfig);
4949
return transport.EndPoint;
5050
}
5151

52-
public async Task<EndPoint> BindAsync(EndPoint endPoint, MultiplexedConnectionDelegate multiplexedConnectionDelegate, ListenOptions listenOptions)
52+
public async Task<EndPoint> BindAsync(EndPoint endPoint, MultiplexedConnectionDelegate multiplexedConnectionDelegate, ListenOptions listenOptions, CancellationToken cancellationToken)
5353
{
5454
if (_multiplexedTransportFactory is null)
5555
{
@@ -69,7 +69,7 @@ public async Task<EndPoint> BindAsync(EndPoint endPoint, MultiplexedConnectionDe
6969
features.Set(sslServerAuthenticationOptions);
7070
}
7171

72-
var transport = await _multiplexedTransportFactory.BindAsync(endPoint, features).ConfigureAwait(false);
72+
var transport = await _multiplexedTransportFactory.BindAsync(endPoint, features, cancellationToken).ConfigureAwait(false);
7373
StartAcceptLoop(new GenericMultiplexedConnectionListener(transport), c => multiplexedConnectionDelegate(c), listenOptions.EndpointConfig);
7474
return transport.EndPoint;
7575
}

src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs

+5-6
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public async Task StartAsync<TContext>(IHttpApplication<TContext> application, C
160160

161161
ServiceContext.Heartbeat?.Start();
162162

163-
async Task OnBind(ListenOptions options)
163+
async Task OnBind(ListenOptions options, CancellationToken onBindCancellationToken)
164164
{
165165
// INVESTIGATE: For some reason, MsQuic needs to bind before
166166
// sockets for it to successfully listen. It also seems racy.
@@ -177,7 +177,7 @@ async Task OnBind(ListenOptions options)
177177
// Add the connection limit middleware
178178
multiplexedConnectionDelegate = EnforceConnectionLimit(multiplexedConnectionDelegate, Options.Limits.MaxConcurrentConnections, Trace);
179179

180-
options.EndPoint = await _transportManager.BindAsync(options.EndPoint, multiplexedConnectionDelegate, options).ConfigureAwait(false);
180+
options.EndPoint = await _transportManager.BindAsync(options.EndPoint, multiplexedConnectionDelegate, options, onBindCancellationToken).ConfigureAwait(false);
181181
}
182182

183183
// Add the HTTP middleware as the terminal connection middleware
@@ -197,7 +197,7 @@ async Task OnBind(ListenOptions options)
197197
// Add the connection limit middleware
198198
connectionDelegate = EnforceConnectionLimit(connectionDelegate, Options.Limits.MaxConcurrentConnections, Trace);
199199

200-
options.EndPoint = await _transportManager.BindAsync(options.EndPoint, connectionDelegate, options.EndpointConfig).ConfigureAwait(false);
200+
options.EndPoint = await _transportManager.BindAsync(options.EndPoint, connectionDelegate, options.EndpointConfig, onBindCancellationToken).ConfigureAwait(false);
201201
}
202202
}
203203

@@ -275,7 +275,7 @@ private async Task BindAsync(CancellationToken cancellationToken)
275275

276276
Options.ConfigurationLoader?.Load();
277277

278-
await AddressBinder.BindAsync(Options.ListenOptions, AddressBindContext!).ConfigureAwait(false);
278+
await AddressBinder.BindAsync(Options.ListenOptions, AddressBindContext!, cancellationToken).ConfigureAwait(false);
279279
_configChangedRegistration = reloadToken?.RegisterChangeCallback(TriggerRebind, this);
280280
}
281281
finally
@@ -342,8 +342,7 @@ private async Task RebindAsync()
342342
{
343343
try
344344
{
345-
// TODO: This should probably be canceled by the _stopCts too, but we don't currently support bind cancellation even in StartAsync().
346-
await listenOption.BindAsync(AddressBindContext!).ConfigureAwait(false);
345+
await listenOption.BindAsync(AddressBindContext!, _stopCts.Token).ConfigureAwait(false);
347346
}
348347
catch (Exception ex)
349348
{

src/Servers/Kestrel/Core/src/ListenOptions.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Net;
77
using System.Net.Sockets;
8+
using System.Threading;
89
using System.Threading.Tasks;
910
using Microsoft.AspNetCore.Connections;
1011
using Microsoft.AspNetCore.Connections.Experimental;
@@ -176,9 +177,9 @@ MultiplexedConnectionDelegate IMultiplexedConnectionBuilder.Build()
176177
return app;
177178
}
178179

179-
internal virtual async Task BindAsync(AddressBindContext context)
180+
internal virtual async Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
180181
{
181-
await AddressBinder.BindEndpointAsync(this, context).ConfigureAwait(false);
182+
await AddressBinder.BindEndpointAsync(this, context, cancellationToken).ConfigureAwait(false);
182183
context.Addresses.Add(GetDisplayName());
183184
}
184185
}

src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Net;
8+
using System.Threading;
89
using System.Threading.Tasks;
910
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
1011
using Microsoft.Extensions.Logging;
@@ -30,16 +31,16 @@ internal override string GetDisplayName()
3031
return $"{Scheme}://localhost:{IPEndPoint!.Port}";
3132
}
3233

33-
internal override async Task BindAsync(AddressBindContext context)
34+
internal override async Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
3435
{
3536
var exceptions = new List<Exception>();
3637

3738
try
3839
{
3940
var v4Options = Clone(IPAddress.Loopback);
40-
await AddressBinder.BindEndpointAsync(v4Options, context).ConfigureAwait(false);
41+
await AddressBinder.BindEndpointAsync(v4Options, context, cancellationToken).ConfigureAwait(false);
4142
}
42-
catch (Exception ex) when (!(ex is IOException))
43+
catch (Exception ex) when (!(ex is IOException or OperationCanceledException))
4344
{
4445
context.Logger.LogInformation(0, CoreStrings.NetworkInterfaceBindingFailed, GetDisplayName(), "IPv4 loopback", ex.Message);
4546
exceptions.Add(ex);
@@ -48,9 +49,9 @@ internal override async Task BindAsync(AddressBindContext context)
4849
try
4950
{
5051
var v6Options = Clone(IPAddress.IPv6Loopback);
51-
await AddressBinder.BindEndpointAsync(v6Options, context).ConfigureAwait(false);
52+
await AddressBinder.BindEndpointAsync(v6Options, context, cancellationToken).ConfigureAwait(false);
5253
}
53-
catch (Exception ex) when (!(ex is IOException))
54+
catch (Exception ex) when (!(ex is IOException or OperationCanceledException))
5455
{
5556
context.Logger.LogInformation(0, CoreStrings.NetworkInterfaceBindingFailed, GetDisplayName(), "IPv6 loopback", ex.Message);
5657
exceptions.Add(ex);

0 commit comments

Comments
 (0)