Skip to content

Commit 1be20f4

Browse files
authored
Merge pull request #49922 from vseanreesermsft/internal-merge-2.1-2023-08-08-1011
Merging internal commits for release/2.1
2 parents d2a12f5 + 3cc4892 commit 1be20f4

32 files changed

+1081
-169
lines changed

eng/PatchConfig.props

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,11 @@ Later on, this will be checked using this condition:
134134
Microsoft.AspNetCore.Identity;
135135
</PackagesInPatch>
136136
</PropertyGroup>
137+
<PropertyGroup Condition=" '$(VersionPrefix)' == '2.1.40' ">
138+
<PackagesInPatch>
139+
Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv;
140+
Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
141+
Microsoft.AspNetCore.SignalR.Redis;
142+
</PackagesInPatch>
143+
</PropertyGroup>
137144
</Project>

src/Middleware/WebSockets/test/UnitTests/Microsoft.AspNetCore.WebSockets.Tests.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
<ItemGroup>
88
<PackageReference Include="Microsoft.AspNetCore.Server.IntegrationTesting" Version="$(MicrosoftAspNetCoreServerIntegrationTestingPackageVersion)" />
9-
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
9+
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
10+
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" />
11+
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" />
1012
<Reference Include="Microsoft.AspNetCore.WebSockets" />
1113
<Reference Include="Microsoft.Extensions.Logging.Testing" />
1214
</ItemGroup>

src/Servers/Kestrel/Kestrel/src/Microsoft.AspNetCore.Server.Kestrel.csproj

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<Description>ASP.NET Core Kestrel cross-platform web server.</Description>
@@ -11,9 +11,12 @@
1111
<ItemGroup>
1212
<Reference Include="Microsoft.AspNetCore.Hosting" />
1313
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Https" />
14-
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" />
1514
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" Version="2.1.25" />
1615
<PackageReference Include="System.IO.Pipelines" Version="$(SystemIOPipelinesPackageVersion)" />
1716
</ItemGroup>
1817

18+
<ItemGroup>
19+
<ProjectReference Include="..\..\Transport.Sockets\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" />
20+
</ItemGroup>
21+
1922
</Project>

src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
@@ -10,8 +10,12 @@
1010
</ItemGroup>
1111

1212
<ItemGroup>
13-
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
14-
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" />
13+
<ProjectReference Include="..\..\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj" />
14+
<ProjectReference Include="..\..\Https\src\Microsoft.AspNetCore.Server.Kestrel.Https.csproj" />
15+
<ProjectReference Include="..\..\Transport.Abstractions\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj" />
16+
<ProjectReference Include="..\..\Transport.Libuv\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj" />
17+
<ProjectReference Include="..\..\Transport.Sockets\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" />
18+
<ProjectReference Include="..\src\Microsoft.AspNetCore.Server.Kestrel.csproj" />
1519
</ItemGroup>
1620

1721
</Project>

src/Servers/Kestrel/Transport.Libuv/src/Internal/ILibuvTrace.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -14,6 +14,8 @@ public interface ILibuvTrace : ILogger
1414

1515
void ConnectionWriteFin(string connectionId);
1616

17+
void ConnectionWriteRst(string connectionId);
18+
1719
void ConnectionWroteFin(string connectionId, int status);
1820

1921
void ConnectionWrite(string connectionId, int count);

src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvConnection.cs

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO;
77
using System.IO.Pipelines;
88
using System.Net;
9+
using System.Net.Sockets;
910
using System.Threading;
1011
using System.Threading.Tasks;
1112
using Microsoft.AspNetCore.Connections;
@@ -28,13 +29,21 @@ public partial class LibuvConnection : TransportConnection
2829
private readonly UvStreamHandle _socket;
2930
private readonly CancellationTokenSource _connectionClosedTokenSource = new CancellationTokenSource();
3031

32+
private readonly bool _finOnError;
33+
3134
private volatile ConnectionAbortedException _abortReason;
3235

3336
private MemoryHandle _bufferHandle;
3437

3538
public LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread thread, IPEndPoint remoteEndPoint, IPEndPoint localEndPoint)
39+
: this(socket, log, thread, remoteEndPoint, localEndPoint, finOnError: false)
40+
{
41+
}
42+
43+
internal LibuvConnection(UvStreamHandle socket, ILibuvTrace log, LibuvThread thread, IPEndPoint remoteEndPoint, IPEndPoint localEndPoint, bool finOnError)
3644
{
3745
_socket = socket;
46+
_finOnError = finOnError; ;
3847

3948
RemoteAddress = remoteEndPoint?.Address;
4049
RemotePort = remoteEndPoint?.Port ?? 0;
@@ -94,6 +103,13 @@ public async Task Start()
94103
}
95104
finally
96105
{
106+
if (!_finOnError && _abortReason != null)
107+
{
108+
// When shutdown isn't clean (note that we're using _abortReason, rather than inputError, to exclude that case),
109+
// we set the DontLinger socket option to cause libuv to send a RST and release any buffered response data.
110+
SetDontLingerOption(_socket);
111+
}
112+
97113
// Now, complete the input so that no more reads can happen
98114
Input.Complete(inputError ?? _abortReason ?? new ConnectionAbortedException());
99115
Output.Complete(outputError);
@@ -102,8 +118,16 @@ public async Task Start()
102118
// on the stream handle
103119
Input.CancelPendingFlush();
104120

105-
// Send a FIN
106-
Log.ConnectionWriteFin(ConnectionId);
121+
if (!_finOnError && _abortReason != null)
122+
{
123+
// Send a RST
124+
Log.ConnectionWriteRst(ConnectionId);
125+
}
126+
else
127+
{
128+
// Send a FIN
129+
Log.ConnectionWriteFin(ConnectionId);
130+
}
107131

108132
// We're done with the socket now
109133
_socket.Dispose();
@@ -116,13 +140,39 @@ public async Task Start()
116140
}
117141
}
118142

143+
/// <remarks>
144+
/// This should be called on <see cref="_socket"/> before it is disposed.
145+
/// Both <see cref="Abort"/> and <see cref="Start"/> call dispose but, rather than predict
146+
/// which will do so first (which varies), we make this method idempotent and call it in both.
147+
/// </remarks>
148+
private static void SetDontLingerOption(UvStreamHandle socket)
149+
{
150+
if (!socket.IsClosed && !socket.IsInvalid) {
151+
var libuv = socket.Libuv;
152+
if (libuv.IsWindows) // p/invoke of setsockopt is Windows-specific
153+
{
154+
var pSocket = IntPtr.Zero;
155+
libuv.uv_fileno(socket, ref pSocket);
156+
libuv.setsockopt(pSocket, SocketOptionLevel.Socket, SocketOptionName.DontLinger, 0);
157+
}
158+
}
159+
}
160+
119161
public override void Abort(ConnectionAbortedException abortReason)
120162
{
121163
_abortReason = abortReason;
122164
Output.CancelPendingRead();
123-
124-
// This cancels any pending I/O.
125-
Thread.Post(s => s.Dispose(), _socket);
165+
166+
Thread.Post(/*static*/ (self) =>
167+
{
168+
if (!self._finOnError)
169+
{
170+
SetDontLingerOption(self._socket);
171+
}
172+
173+
// This cancels any pending I/O.
174+
self._socket.Dispose();
175+
}, this);
126176
}
127177

128178
// Called on Libuv thread

src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTrace.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public class LibuvTrace : ILibuvTrace
2222
private static readonly Action<ILogger, string, Exception> _connectionWriteFin =
2323
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN.");
2424

25+
private static readonly Action<ILogger, string, Exception> _connectionWriteRst =
26+
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(8, nameof(ConnectionWriteRst)), @"Connection id ""{ConnectionId}"" sending RST.");
27+
2528
private static readonly Action<ILogger, string, int, Exception> _connectionWroteFin =
2629
LoggerMessage.Define<string, int>(LogLevel.Debug, new EventId(8, nameof(ConnectionWroteFin)), @"Connection id ""{ConnectionId}"" sent FIN with status ""{Status}"".");
2730

@@ -58,6 +61,10 @@ public void ConnectionWriteFin(string connectionId)
5861
_connectionWriteFin(_logger, connectionId, null);
5962
}
6063

64+
public void ConnectionWriteRst(string connectionId) {
65+
_connectionWriteRst(_logger, connectionId, null);
66+
}
67+
6168
public void ConnectionWroteFin(string connectionId, int status)
6269
{
6370
_connectionWroteFin(_logger, connectionId, status, null);

src/Servers/Kestrel/Transport.Libuv/src/Internal/LibuvTransport.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -10,6 +10,7 @@
1010
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
1111
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
1212
using Microsoft.Extensions.Logging;
13+
using System.Diagnostics;
1314

1415
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
1516
{
@@ -72,7 +73,22 @@ public async Task BindAsync()
7273
{
7374
// TODO: Move thread management to LibuvTransportFactory
7475
// TODO: Split endpoint management from thread management
75-
for (var index = 0; index < TransportOptions.ThreadCount; index++)
76+
77+
// When `FinOnError` is false (the default), we need to be able to forcibly abort connections.
78+
// On Windows, libuv 1.10.0 will call `shutdown`, preventing forcible abort, on any socket
79+
// not flagged as `UV_HANDLE_SHARED_TCP_SOCKET`. The only way we've found to cause socket
80+
// to be flagged as `UV_HANDLE_SHARED_TCP_SOCKET` is to share it across a named pipe (which
81+
// must, itself, be flagged `ipc`), which naturally happens when a `ListenerPrimary` dispatches
82+
// a connection to a `ListenerSecondary`. Therefore, in scenarios where this is required, we
83+
// tell the `ListenerPrimary` to dispatch *all* connections to secondary and create an
84+
// additional `ListenerSecondary` to replace the lost capacity.
85+
var dispatchAllToSecondary = Libuv.IsWindows && !TransportContext.Options.FinOnError;
86+
87+
var threadCount = dispatchAllToSecondary
88+
? TransportOptions.ThreadCount + 1
89+
: TransportOptions.ThreadCount;
90+
91+
for (var index = 0; index < threadCount; index++)
7692
{
7793
Threads.Add(new LibuvThread(this));
7894
}
@@ -84,8 +100,10 @@ public async Task BindAsync()
84100

85101
try
86102
{
87-
if (TransportOptions.ThreadCount == 1)
103+
if (threadCount == 1)
88104
{
105+
Debug.Assert(!dispatchAllToSecondary, "Should have taken the primary/secondary code path");
106+
89107
var listener = new Listener(TransportContext);
90108
_listeners.Add(listener);
91109
await listener.StartAsync(_endPointInformation, Threads[0]).ConfigureAwait(false);
@@ -95,7 +113,7 @@ public async Task BindAsync()
95113
var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
96114
var pipeMessage = Guid.NewGuid().ToByteArray();
97115

98-
var listenerPrimary = new ListenerPrimary(TransportContext);
116+
var listenerPrimary = new ListenerPrimary(TransportContext, dispatchAllToSecondary);
99117
_listeners.Add(listenerPrimary);
100118
await listenerPrimary.StartAsync(pipeName, pipeMessage, _endPointInformation, Threads[0]).ConfigureAwait(false);
101119

src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerContext.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ protected void HandleConnectionAsync(UvStreamHandle socket)
6363
}
6464
}
6565

66-
var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint);
66+
var finOnError = TransportContext.Options.FinOnError;
67+
var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint, finOnError);
6768
TransportContext.ConnectionDispatcher.OnConnection(connection);
6869
_ = connection.Start();
6970
}

src/Servers/Kestrel/Transport.Libuv/src/Internal/ListenerPrimary.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.IO;
78
using System.Runtime.InteropServices;
89
using System.Threading.Tasks;
@@ -22,6 +23,10 @@ public class ListenerPrimary : Listener
2223
private readonly List<UvPipeHandle> _dispatchPipes = new List<UvPipeHandle>();
2324
// The list of pipes we've created but may not be part of _dispatchPipes
2425
private readonly List<UvPipeHandle> _createdPipes = new List<UvPipeHandle>();
26+
27+
// If true, dispatch all connections to _dispatchPipes - don't process any in the primary
28+
private readonly bool _dispatchAll;
29+
2530
private int _dispatchIndex;
2631
private string _pipeName;
2732
private byte[] _pipeMessage;
@@ -32,8 +37,9 @@ public class ListenerPrimary : Listener
3237
// but it has no other functional significance
3338
private readonly ArraySegment<ArraySegment<byte>> _dummyMessage = new ArraySegment<ArraySegment<byte>>(new[] { new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 }) });
3439

35-
public ListenerPrimary(LibuvTransportContext transportContext) : base(transportContext)
40+
public ListenerPrimary(LibuvTransportContext transportContext, bool dispatchAll) : base(transportContext)
3641
{
42+
_dispatchAll = dispatchAll;
3743
}
3844

3945
/// <summary>
@@ -105,9 +111,22 @@ private void OnListenPipe(UvStreamHandle pipe, int status, UvException error)
105111

106112
protected override void DispatchConnection(UvStreamHandle socket)
107113
{
108-
var index = _dispatchIndex++ % (_dispatchPipes.Count + 1);
114+
var modulus = _dispatchAll ? _dispatchPipes.Count : (_dispatchPipes.Count + 1);
115+
if (modulus == 0) {
116+
if (_createdPipes.Count == 0) {
117+
Log.LogError(0, $"Connection received before listeners were initialized - see https://aka.ms/dotnet/aspnet/finonerror for possible mitigations");
118+
}
119+
else {
120+
Log.LogError(0, "Unable to process connection since listeners failed to initialize - see https://aka.ms/dotnet/aspnet/finonerror for possible mitigations");
121+
}
122+
123+
return;
124+
}
125+
126+
var index = _dispatchIndex++ % modulus;
109127
if (index == _dispatchPipes.Count)
110128
{
129+
Debug.Assert(!_dispatchAll, "Should have dispatched to a secondary listener");
111130
base.DispatchConnection(socket);
112131
}
113132
else

0 commit comments

Comments
 (0)