Skip to content

Commit 4dd0a6c

Browse files
committed
Merge branch 'release/2.1' into release/3.1
2 parents cf3bf40 + 8211a1c commit 4dd0a6c

File tree

16 files changed

+450
-22
lines changed

16 files changed

+450
-22
lines changed

eng/Baseline.Designer.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
<Project>
33
<PropertyGroup>
44
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
5+
<<<<<<< HEAD
56
<AspNetCoreBaselineVersion>3.1.1</AspNetCoreBaselineVersion>
7+
=======
8+
<AspNetCoreBaselineVersion>2.1.15</AspNetCoreBaselineVersion>
9+
>>>>>>> release/2.1
610
</PropertyGroup>
711
<!-- Package: AspNetCoreRuntime.3.0.x64-->
812
<PropertyGroup Condition=" '$(PackageId)' == 'AspNetCoreRuntime.3.0.x64' ">
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public Startup(IConfiguration config, IWebHostEnvironment env)
3535

3636
private void CheckSameSite(HttpContext httpContext, CookieOptions options)
3737
{
38-
if (options.SameSite > SameSiteMode.Unspecified)
38+
if (options.SameSite == SameSiteMode.None)
3939
{
4040
var userAgent = httpContext.Request.Headers["User-Agent"];
4141
// TODO: Use your User Agent library of choice here.

src/Security/Authentication/test/WsFederation/WsFederationTest.cs

Lines changed: 2 additions & 2 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;
@@ -440,4 +440,4 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
440440
}
441441
}
442442
}
443-
}
443+
}

src/Security/CookiePolicy/test/CookieChunkingTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,29 @@ public void AppendLargeCookie_WithOptions_Appended()
4444
Assert.Equal($"TestCookie={testString}; expires={now.AddMinutes(5).ToString("R")}; max-age=300; domain=foo.com; path=/bar; secure; samesite=strict; httponly", values[0]);
4545
}
4646

47+
[Fact]
48+
public void AppendLargeCookie_WithOptions_Appended()
49+
{
50+
HttpContext context = new DefaultHttpContext();
51+
var now = DateTimeOffset.UtcNow;
52+
var options = new CookieOptions
53+
{
54+
Domain = "foo.com",
55+
HttpOnly = true,
56+
SameSite = SameSiteMode.Strict,
57+
Path = "/bar",
58+
Secure = true,
59+
Expires = now.AddMinutes(5),
60+
MaxAge = TimeSpan.FromMinutes(5)
61+
};
62+
var testString = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
63+
new ChunkingCookieManager() { ChunkSize = null }.AppendResponseCookie(context, "TestCookie", testString, options);
64+
65+
var values = context.Response.Headers["Set-Cookie"];
66+
Assert.Single(values);
67+
Assert.Equal($"TestCookie={testString}; expires={now.AddMinutes(5).ToString("R")}; max-age=300; domain=foo.com; path=/bar; secure; samesite=strict; httponly", values[0]);
68+
}
69+
4770
[Fact]
4871
public void AppendLargeCookieWithLimit_Chunked()
4972
{

src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ internal class HttpConnectionContext : ConnectionContext,
3333
{
3434
private static long _tenSeconds = TimeSpan.FromSeconds(10).Ticks;
3535

36+
<<<<<<< HEAD
3637
private readonly object _stateLock = new object();
38+
=======
39+
>>>>>>> release/2.1
3740
private readonly object _itemsLock = new object();
3841
private readonly object _heartbeatLock = new object();
3942
private List<(Action<object> handler, object state)> _heartbeatHandlers;
@@ -46,6 +49,10 @@ internal class HttpConnectionContext : ConnectionContext,
4649
private bool _activeSend;
4750
private long _startedSendTime;
4851
private readonly object _sendingLock = new object();
52+
<<<<<<< HEAD
53+
=======
54+
55+
>>>>>>> release/2.1
4956
internal CancellationToken SendingToken { get; private set; }
5057

5158
// This tcs exists so that multiple calls to DisposeAsync all wait asynchronously
@@ -292,6 +299,7 @@ private async Task WaitOnTasks(Task applicationTask, Task transportTask, bool cl
292299
// Wait for either to finish
293300
var result = await Task.WhenAny(applicationTask, transportTask);
294301

302+
<<<<<<< HEAD
295303
// If the application is complete, complete the transport pipe (it's the pipe to the transport)
296304
if (result == applicationTask)
297305
{
@@ -333,7 +341,47 @@ private async Task WaitOnTasks(Task applicationTask, Task transportTask, bool cl
333341

334342
Transport?.Output.Complete();
335343
Transport?.Input.Complete();
344+
=======
345+
// Normally it isn't safe to try and acquire this lock because the Send can hold onto it for a long time if there is backpressure
346+
// It is safe to wait for this lock now because the Send will be in one of 4 states
347+
// 1. In the middle of a write which is in the middle of being canceled by the CancelPendingFlush above, when it throws
348+
// an OperationCanceledException it will complete the PipeWriter which will make any other Send waiting on the lock
349+
// throw an InvalidOperationException if they call Write
350+
// 2. About to write and see that there is a pending cancel from the CancelPendingFlush, go to 1 to see what happens
351+
// 3. Enters the Send and sees the Dispose state from DisposeAndRemoveAsync and releases the lock
352+
// 4. No Send in progress
353+
await WriteLock.WaitAsync();
354+
try
355+
{
356+
// Complete the applications read loop
357+
Application?.Output.Complete(transportTask.Exception?.InnerException);
358+
}
359+
finally
360+
{
361+
WriteLock.Release();
362+
>>>>>>> release/2.1
336363
}
364+
365+
Application?.Input.CancelPendingRead();
366+
367+
await transportTask.NoThrow();
368+
Application?.Input.Complete();
369+
370+
Log.WaitingForTransportAndApplication(_logger, TransportType);
371+
372+
// A poorly written application *could* in theory get stuck forever and it'll show up as a memory leak
373+
// Wait for application so we can complete the writer safely
374+
await applicationTask.NoThrow();
375+
Log.TransportAndApplicationComplete(_logger, TransportType);
376+
377+
// Shutdown application side now that it's finished
378+
Transport?.Output.Complete(applicationTask.Exception?.InnerException);
379+
380+
// Close the reading side after both sides run
381+
Transport?.Input.Complete();
382+
383+
// Observe exceptions
384+
await Task.WhenAll(transportTask, applicationTask);
337385
}
338386

339387
// Notify all waiters that we're done disposing
@@ -353,6 +401,7 @@ private async Task WaitOnTasks(Task applicationTask, Task transportTask, bool cl
353401
}
354402
}
355403

404+
<<<<<<< HEAD
356405
internal bool TryActivatePersistentConnection(
357406
ConnectionDelegate connectionDelegate,
358407
IHttpTransport transport,
@@ -533,6 +582,8 @@ private async Task ExecuteApplication(ConnectionDelegate connectionDelegate)
533582
await connectionDelegate(this);
534583
}
535584

585+
=======
586+
>>>>>>> release/2.1
536587
internal void StartSendCancellation()
537588
{
538589
lock (_sendingLock)
@@ -542,10 +593,18 @@ internal void StartSendCancellation()
542593
_sendCts = new CancellationTokenSource();
543594
SendingToken = _sendCts.Token;
544595
}
596+
<<<<<<< HEAD
597+
=======
598+
599+
>>>>>>> release/2.1
545600
_startedSendTime = DateTime.UtcNow.Ticks;
546601
_activeSend = true;
547602
}
548603
}
604+
<<<<<<< HEAD
605+
=======
606+
607+
>>>>>>> release/2.1
549608
internal void TryCancelSend(long currentTicks)
550609
{
551610
lock (_sendingLock)
@@ -559,6 +618,10 @@ internal void TryCancelSend(long currentTicks)
559618
}
560619
}
561620
}
621+
<<<<<<< HEAD
622+
=======
623+
624+
>>>>>>> release/2.1
562625
internal void StopSendCancellation()
563626
{
564627
lock (_sendingLock)

src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,11 @@ private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connecti
142142
connection.SupportedFormats = TransferFormat.Text;
143143

144144
// We only need to provide the Input channel since writing to the application is handled through /send.
145+
<<<<<<< HEAD
145146
var sse = new ServerSentEventsServerTransport(connection.Application.Input, connection.ConnectionId, connection, _loggerFactory);
147+
=======
148+
var sse = new ServerSentEventsTransport(connection.Application.Input, connection.ConnectionId, connection, _loggerFactory);
149+
>>>>>>> release/2.1
146150

147151
await DoPersistentConnection(connectionDelegate, sse, context, connection);
148152
}
@@ -191,9 +195,83 @@ private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connecti
191195

192196
if (!await connection.CancelPreviousPoll(context))
193197
{
198+
<<<<<<< HEAD
194199
// Connection closed. It's already set the response status code.
195200
return;
196201
}
202+
=======
203+
if (connection.Status == HttpConnectionStatus.Disposed)
204+
{
205+
Log.ConnectionDisposed(_logger, connection.ConnectionId);
206+
207+
// The connection was disposed
208+
context.Response.StatusCode = StatusCodes.Status404NotFound;
209+
context.Response.ContentType = "text/plain";
210+
return;
211+
}
212+
213+
if (connection.Status == HttpConnectionStatus.Active)
214+
{
215+
var existing = connection.GetHttpContext();
216+
Log.ConnectionAlreadyActive(_logger, connection.ConnectionId, existing.TraceIdentifier);
217+
}
218+
219+
using (connection.Cancellation)
220+
{
221+
// Cancel the previous request
222+
connection.Cancellation?.Cancel();
223+
224+
try
225+
{
226+
// Wait for the previous request to drain
227+
await connection.PreviousPollTask;
228+
}
229+
catch (OperationCanceledException)
230+
{
231+
// Previous poll canceled due to connection closing, close this poll too
232+
context.Response.ContentType = "text/plain";
233+
context.Response.StatusCode = StatusCodes.Status204NoContent;
234+
return;
235+
}
236+
237+
connection.PreviousPollTask = currentRequestTcs.Task;
238+
}
239+
240+
// Mark the connection as active
241+
connection.Status = HttpConnectionStatus.Active;
242+
243+
// Raise OnConnected for new connections only since polls happen all the time
244+
if (connection.ApplicationTask == null)
245+
{
246+
Log.EstablishedConnection(_logger);
247+
248+
connection.ApplicationTask = ExecuteApplication(connectionDelegate, connection);
249+
250+
context.Response.ContentType = "application/octet-stream";
251+
252+
// This request has no content
253+
context.Response.ContentLength = 0;
254+
255+
// On the first poll, we flush the response immediately to mark the poll as "initialized" so future
256+
// requests can be made safely
257+
connection.TransportTask = context.Response.Body.FlushAsync();
258+
}
259+
else
260+
{
261+
Log.ResumingConnection(_logger);
262+
263+
// REVIEW: Performance of this isn't great as this does a bunch of per request allocations
264+
connection.Cancellation = new CancellationTokenSource();
265+
266+
var timeoutSource = new CancellationTokenSource();
267+
var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(connection.Cancellation.Token, context.RequestAborted, timeoutSource.Token);
268+
269+
// Dispose these tokens when the request is over
270+
context.Response.RegisterForDispose(timeoutSource);
271+
context.Response.RegisterForDispose(tokenSource);
272+
273+
var longPolling = new LongPollingTransport(timeoutSource.Token, connection.Application.Input, _loggerFactory, connection);
274+
>>>>>>> release/2.1
197275

198276
// Create a new Tcs every poll to keep track of the poll finishing, so we can properly wait on previous polls
199277
var currentRequestTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@@ -236,7 +314,23 @@ private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connecti
236314
connection.MarkInactive();
237315
}
238316
}
317+
<<<<<<< HEAD
239318
else if (resultTask.IsFaulted || resultTask.IsCanceled)
319+
=======
320+
else if (connection.TransportTask.IsFaulted || connection.TransportTask.IsCanceled)
321+
{
322+
// Cancel current request to release any waiting poll and let dispose aquire the lock
323+
currentRequestTcs.TrySetCanceled();
324+
325+
// We should be able to safely dispose because there's no more data being written
326+
// We don't need to wait for close here since we've already waited for both sides
327+
await _manager.DisposeAndRemoveAsync(connection, closeGracefully: false);
328+
329+
// Don't poll again if we've removed the connection completely
330+
pollAgain = false;
331+
}
332+
else if (context.Response.StatusCode == StatusCodes.Status204NoContent)
333+
>>>>>>> release/2.1
240334
{
241335
// Cancel current request to release any waiting poll and let dispose acquire the lock
242336
currentRequestTcs.TrySetCanceled();
@@ -444,6 +538,7 @@ private async Task ProcessSend(HttpContext context, HttpConnectionDispatcherOpti
444538
// Other code isn't guaranteed to be able to acquire the lock before another write
445539
// even if CancelPendingFlush is called, and the other write could hang if there is backpressure
446540
connection.Application.Output.Complete();
541+
<<<<<<< HEAD
447542
return;
448543
}
449544
catch (IOException ex)
@@ -453,6 +548,8 @@ private async Task ProcessSend(HttpContext context, HttpConnectionDispatcherOpti
453548

454549
context.Response.StatusCode = StatusCodes.Status400BadRequest;
455550
context.Response.ContentType = "text/plain";
551+
=======
552+
>>>>>>> release/2.1
456553
return;
457554
}
458555

src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ internal partial class HttpConnectionManager
3232
private readonly ILogger<HttpConnectionManager> _logger;
3333
private readonly ILogger<HttpConnectionContext> _connectionLogger;
3434
private readonly bool _useSendTimeout = true;
35+
<<<<<<< HEAD
3536
private readonly TimeSpan _disconnectTimeout;
37+
=======
38+
>>>>>>> release/2.1
3639

3740
public HttpConnectionManager(ILoggerFactory loggerFactory, IHostApplicationLifetime appLifetime)
3841
: this(loggerFactory, appLifetime, Options.Create(new ConnectionOptions() { DisconnectTimeout = ConnectionOptionsSetup.DefaultDisconectTimeout }))
@@ -53,6 +56,15 @@ public HttpConnectionManager(ILoggerFactory loggerFactory, IHostApplicationLifet
5356
// Register these last as the callbacks could run immediately
5457
appLifetime.ApplicationStarted.Register(() => Start());
5558
appLifetime.ApplicationStopping.Register(() => CloseConnections());
59+
<<<<<<< HEAD
60+
=======
61+
_nextHeartbeat = new TimerAwaitable(_heartbeatTickRate, _heartbeatTickRate);
62+
63+
if (AppContext.TryGetSwitch("Microsoft.AspNetCore.Http.Connections.DoNotUseSendTimeout", out var timeoutDisabled))
64+
{
65+
_useSendTimeout = !timeoutDisabled;
66+
}
67+
>>>>>>> release/2.1
5668
}
5769

5870
public void Start()
@@ -161,10 +173,31 @@ public void Scan()
161173
// Capture the connection state
162174
var lastSeenUtc = connection.LastSeenUtcIfInactive;
163175

176+
<<<<<<< HEAD
164177
var utcNow = DateTimeOffset.UtcNow;
165178
// Once the decision has been made to dispose we don't check the status again
166179
// But don't clean up connections while the debugger is attached.
167180
if (!Debugger.IsAttached && lastSeenUtc.HasValue && (utcNow - lastSeenUtc.Value).TotalSeconds > _disconnectTimeout.TotalSeconds)
181+
=======
182+
await connection.StateLock.WaitAsync();
183+
184+
try
185+
{
186+
// Capture the connection state
187+
status = connection.Status;
188+
189+
lastSeenUtc = connection.LastSeenUtc;
190+
}
191+
finally
192+
{
193+
connection.StateLock.Release();
194+
}
195+
196+
var utcNow = DateTimeOffset.UtcNow;
197+
// Once the decision has been made to dispose we don't check the status again
198+
// But don't clean up connections while the debugger is attached.
199+
if (!Debugger.IsAttached && status == HttpConnectionStatus.Inactive && (utcNow - lastSeenUtc).TotalSeconds > 5)
200+
>>>>>>> release/2.1
168201
{
169202
Log.ConnectionTimedOut(_logger, connection.ConnectionId);
170203
HttpConnectionsEventSource.Log.ConnectionTimedOut(connection.ConnectionId);

0 commit comments

Comments
 (0)