From dec60c8e37b9b712bd0f569dd387cbe370fbc5ac Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 7 Apr 2021 15:02:49 -0700 Subject: [PATCH 1/8] Fix null ref in SyntaxTokenCache (#30978) (#30988) Fixes https://github.com/dotnet/aspnetcore/issues/27154 --- .../Syntax/InternalSyntax/SyntaxFactory.cs | 8 +- .../Syntax/InternalSyntax/SyntaxTokenCache.cs | 20 +++-- .../test/Syntax/SyntaxTokenCacheTest.cs | 78 +++++++++++++++++++ 3 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 src/Razor/Microsoft.AspNetCore.Razor.Language/test/Syntax/SyntaxTokenCacheTest.cs diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/SyntaxFactory.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/SyntaxFactory.cs index 8352a2bb1a47..cb5efd862853 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/SyntaxFactory.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/SyntaxFactory.cs @@ -1,17 +1,15 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; - namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax { internal static partial class SyntaxFactory { internal static SyntaxToken Token(SyntaxKind kind, string content, params RazorDiagnostic[] diagnostics) { - if (SyntaxTokenCache.CanBeCached(kind, diagnostics)) + if (SyntaxTokenCache.Instance.CanBeCached(kind, diagnostics)) { - return SyntaxTokenCache.GetCachedToken(kind, content); + return SyntaxTokenCache.Instance.GetCachedToken(kind, content); } return new SyntaxToken(kind, content, diagnostics); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/SyntaxTokenCache.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/SyntaxTokenCache.cs index 65d13348113a..005e90076a68 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/SyntaxTokenCache.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Syntax/InternalSyntax/SyntaxTokenCache.cs @@ -1,23 +1,27 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +#nullable enable + using System; -using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax { // Simplified version of Roslyn's SyntaxNodeCache - internal static class SyntaxTokenCache + internal sealed class SyntaxTokenCache { private const int CacheSizeBits = 16; private const int CacheSize = 1 << CacheSizeBits; private const int CacheMask = CacheSize - 1; + public static readonly SyntaxTokenCache Instance = new(); private static readonly Entry[] s_cache = new Entry[CacheSize]; - private struct Entry + internal SyntaxTokenCache() { } + + private readonly struct Entry { public int Hash { get; } - public SyntaxToken Token { get; } + public SyntaxToken? Token { get; } internal Entry(int hash, SyntaxToken token) { @@ -26,7 +30,7 @@ internal Entry(int hash, SyntaxToken token) } } - public static bool CanBeCached(SyntaxKind kind, params RazorDiagnostic[] diagnostics) + public bool CanBeCached(SyntaxKind kind, params RazorDiagnostic[] diagnostics) { if (diagnostics.Length == 0) { @@ -50,7 +54,7 @@ public static bool CanBeCached(SyntaxKind kind, params RazorDiagnostic[] diagnos return false; } - public static SyntaxToken GetCachedToken(SyntaxKind kind, string content) + public SyntaxToken GetCachedToken(SyntaxKind kind, string content) { var hash = (kind, content).GetHashCode(); @@ -60,7 +64,7 @@ public static SyntaxToken GetCachedToken(SyntaxKind kind, string content) var idx = indexableHash & CacheMask; var e = s_cache[idx]; - if (e.Hash == hash && e.Token.Kind == kind && e.Token.Content == content) + if (e.Hash == hash && e.Token != null && e.Token.Kind == kind && e.Token.Content == content) { return e.Token; } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Syntax/SyntaxTokenCacheTest.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Syntax/SyntaxTokenCacheTest.cs new file mode 100644 index 000000000000..d5296db7c241 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/Syntax/SyntaxTokenCacheTest.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax +{ + public class SyntaxTokenCacheTest + { + // Regression test for https://github.com/dotnet/aspnetcore/issues/27154 + [Fact] + public void GetCachedToken_ReturnsNewEntry() + { + // Arrange + var cache = new SyntaxTokenCache(); + + // Act + var token = cache.GetCachedToken(SyntaxKind.Whitespace, "Hello world"); + + // Assert + Assert.Equal(SyntaxKind.Whitespace, token.Kind); + Assert.Equal("Hello world", token.Content); + Assert.Empty(token.GetDiagnostics()); + } + + [Fact] + public void GetCachedToken_ReturnsCachedToken() + { + // Arrange + var cache = new SyntaxTokenCache(); + + // Act + var token1 = cache.GetCachedToken(SyntaxKind.Whitespace, "Hello world"); + var token2 = cache.GetCachedToken(SyntaxKind.Whitespace, "Hello world"); + + // Assert + Assert.Same(token1, token2); + } + + [Fact] + public void GetCachedToken_ReturnsDifferentEntries_IfKindsAreDifferent() + { + // Arrange + var cache = new SyntaxTokenCache(); + + // Act + var token1 = cache.GetCachedToken(SyntaxKind.Whitespace, "Hello world"); + var token2 = cache.GetCachedToken(SyntaxKind.Keyword, "Hello world"); + + // Assert + Assert.NotSame(token1, token2); + Assert.Equal(SyntaxKind.Whitespace, token1.Kind); + Assert.Equal("Hello world", token1.Content); + + Assert.Equal(SyntaxKind.Keyword, token2.Kind); + Assert.Equal("Hello world", token2.Content); + } + + [Fact] + public void GetCachedToken_ReturnsDifferentEntries_IfContentsAreDifferent() + { + // Arrange + var cache = new SyntaxTokenCache(); + + // Act + var token1 = cache.GetCachedToken(SyntaxKind.Keyword, "Text1"); + var token2 = cache.GetCachedToken(SyntaxKind.Keyword, "Text2"); + + // Assert + Assert.NotSame(token1, token2); + Assert.Equal(SyntaxKind.Keyword, token1.Kind); + Assert.Equal("Text1", token1.Content); + + Assert.Equal(SyntaxKind.Keyword, token2.Kind); + Assert.Equal("Text2", token2.Content); + } + } +} From 9facd9b193be1e4f83a677bece2225e6602c2657 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Wed, 7 Apr 2021 16:06:11 -0700 Subject: [PATCH 2/8] Still send 100 Continue with null MinRequestBodyDataRate (#31568) --- .../Core/src/Internal/Http/MessageBody.cs | 10 +++--- .../InMemory.FunctionalTests/RequestTests.cs | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs index 08f13a59592b..62ebe1425b40 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/MessageBody.cs @@ -172,13 +172,15 @@ protected void AddAndCheckObservedBytes(long observedBytes) protected ValueTask StartTimingReadAsync(ValueTask readAwaitable, CancellationToken cancellationToken) { - - if (!readAwaitable.IsCompleted && _timingEnabled) + if (!readAwaitable.IsCompleted) { TryProduceContinue(); - _backpressure = true; - _context.TimeoutControl.StartTimingRead(); + if (_timingEnabled) + { + _backpressure = true; + _context.TimeoutControl.StartTimingRead(); + } } return readAwaitable; diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs index dd32baf2bbfc..d4cdab4bfef8 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/RequestTests.cs @@ -846,6 +846,42 @@ await connection.ReceiveEnd( } } + [Fact] + public async Task Expect100ContinueHonoredWhenMinRequestBodyDataRateIsDisabled() + { + var testContext = new TestServiceContext(LoggerFactory); + + // This may seem unrelated, but this is a regression test for + // https://github.com/dotnet/aspnetcore/issues/30449 + testContext.ServerOptions.Limits.MinRequestBodyDataRate = null; + + await using (var server = new TestServer(TestApp.EchoAppChunked, testContext)) + { + using (var connection = server.CreateConnection()) + { + await connection.Send( + "POST / HTTP/1.1", + "Host:", + "Expect: 100-continue", + "Connection: close", + "Content-Length: 11", + "\r\n"); + await connection.Receive( + "HTTP/1.1 100 Continue", + "", + ""); + await connection.Send("Hello World"); + await connection.ReceiveEnd( + "HTTP/1.1 200 OK", + "Connection: close", + $"Date: {testContext.DateHeaderValue}", + "Content-Length: 11", + "", + "Hello World"); + } + } + } + [Fact] public async Task ZeroContentLengthAssumedOnNonKeepAliveRequestsWithoutContentLengthOrTransferEncodingHeader() { From 00c3e7cd8593cd2a8dc281ae7fb3ef050c02e215 Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 7 Apr 2021 16:07:14 -0700 Subject: [PATCH 3/8] [SignalR TS] Fix permanent Disconnecting state (#30948) (#31253) --- .../clients/ts/signalr/src/HttpConnection.ts | 6 +- .../clients/ts/signalr/src/HubConnection.ts | 6 +- .../ts/signalr/tests/HttpConnection.test.ts | 19 ++-- .../tests/HubConnection.Reconnect.test.ts | 93 +++++++++++++++++++ .../clients/ts/signalr/tests/TestWebSocket.ts | 54 +++++++++++ 5 files changed, 169 insertions(+), 9 deletions(-) diff --git a/src/SignalR/clients/ts/signalr/src/HttpConnection.ts b/src/SignalR/clients/ts/signalr/src/HttpConnection.ts index a53fa15b69ff..a444db8d07d6 100644 --- a/src/SignalR/clients/ts/signalr/src/HttpConnection.ts +++ b/src/SignalR/clients/ts/signalr/src/HttpConnection.ts @@ -51,7 +51,7 @@ export class HttpConnection implements IConnection { private transport?: ITransport; private startInternalPromise?: Promise; private stopPromise?: Promise; - private stopPromiseResolver!: (value?: PromiseLike) => void; + private stopPromiseResolver: (value?: PromiseLike) => void = () => {}; private stopError?: Error; private accessTokenFactory?: () => string | Promise; private sendQueue?: TransportSendQueue; @@ -214,7 +214,6 @@ export class HttpConnection implements IConnection { this.transport = undefined; } else { this.logger.log(LogLevel.Debug, "HttpConnection.transport is undefined in HttpConnection.stop() because start() failed."); - this.stopConnection(); } } @@ -294,6 +293,9 @@ export class HttpConnection implements IConnection { this.logger.log(LogLevel.Error, "Failed to start the connection: " + e); this.connectionState = ConnectionState.Disconnected; this.transport = undefined; + + // if start fails, any active calls to stop assume that start will complete the stop promise + this.stopPromiseResolver(); return Promise.reject(e); } } diff --git a/src/SignalR/clients/ts/signalr/src/HubConnection.ts b/src/SignalR/clients/ts/signalr/src/HubConnection.ts index 505631fa8d63..ec52e89d3c1a 100644 --- a/src/SignalR/clients/ts/signalr/src/HubConnection.ts +++ b/src/SignalR/clients/ts/signalr/src/HubConnection.ts @@ -766,7 +766,11 @@ export class HubConnection { this.logger.log(LogLevel.Information, `Reconnect attempt failed because of error '${e}'.`); if (this.connectionState !== HubConnectionState.Reconnecting) { - this.logger.log(LogLevel.Debug, "Connection left the reconnecting state during reconnect attempt. Done reconnecting."); + this.logger.log(LogLevel.Debug, `Connection moved to the '${this.connectionState}' from the reconnecting state during reconnect attempt. Done reconnecting.`); + // The TypeScript compiler thinks that connectionState must be Connected here. The TypeScript compiler is wrong. + if (this.connectionState as any === HubConnectionState.Disconnecting) { + this.completeClose(); + } return; } diff --git a/src/SignalR/clients/ts/signalr/tests/HttpConnection.test.ts b/src/SignalR/clients/ts/signalr/tests/HttpConnection.test.ts index 94c0ba1818fd..7b055516115b 100644 --- a/src/SignalR/clients/ts/signalr/tests/HttpConnection.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/HttpConnection.test.ts @@ -134,23 +134,30 @@ describe("HttpConnection", () => { it("can stop a starting connection", async () => { await VerifyLogger.run(async (logger) => { + const stoppingPromise = new PromiseSource(); + const startingPromise = new PromiseSource(); const options: IHttpConnectionOptions = { ...commonOptions, httpClient: new TestHttpClient() .on("POST", async () => { - await connection.stop(); + startingPromise.resolve(); + await stoppingPromise; return "{}"; - }) - .on("GET", async () => { - await connection.stop(); - return ""; }), logger, } as IHttpConnectionOptions; const connection = new HttpConnection("http://tempuri.org", options); - await expect(connection.start(TransferFormat.Text)) + const startPromise = connection.start(TransferFormat.Text); + + await startingPromise; + const stopPromise = connection.stop(); + stoppingPromise.resolve(); + + await stopPromise; + + await expect(startPromise) .rejects .toThrow("The connection was stopped during negotiation."); }, diff --git a/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts b/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts index 15e5e953a0d7..825fff89ace8 100644 --- a/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts @@ -2,13 +2,17 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. import { DefaultReconnectPolicy } from "../src/DefaultReconnectPolicy"; +import { HttpConnection, INegotiateResponse } from "../src/HttpConnection"; import { HubConnection, HubConnectionState } from "../src/HubConnection"; +import { IHttpConnectionOptions } from "../src/IHttpConnectionOptions"; import { MessageType } from "../src/IHubProtocol"; import { RetryContext } from "../src/IRetryPolicy"; import { JsonHubProtocol } from "../src/JsonHubProtocol"; import { VerifyLogger } from "./Common"; import { TestConnection } from "./TestConnection"; +import { TestHttpClient } from "./TestHttpClient"; +import { TestEvent, TestMessageEvent, TestWebSocket } from "./TestWebSocket"; import { PromiseSource } from "./Utils"; describe("auto reconnect", () => { @@ -785,4 +789,93 @@ describe("auto reconnect", () => { } }); }); + + it("can be stopped while restarting the underlying connection and negotiate throws", async () => { + await VerifyLogger.run(async (logger) => { + let onreconnectingCount = 0; + let onreconnectedCount = 0; + let closeCount = 0; + + const nextRetryDelayCalledPromise = new PromiseSource(); + + const defaultConnectionId = "abc123"; + const defaultConnectionToken = "123abc"; + const defaultNegotiateResponse: INegotiateResponse = { + availableTransports: [ + { transport: "WebSockets", transferFormats: ["Text", "Binary"] }, + { transport: "ServerSentEvents", transferFormats: ["Text"] }, + { transport: "LongPolling", transferFormats: ["Text", "Binary"] }, + ], + connectionId: defaultConnectionId, + connectionToken: defaultConnectionToken, + negotiateVersion: 1, + }; + + const startStarted = new PromiseSource(); + let negotiateCount = 0; + + const options: IHttpConnectionOptions = { + WebSocket: TestWebSocket, + httpClient: new TestHttpClient() + .on("POST", async () => { + ++negotiateCount; + if (negotiateCount === 1) { + return defaultNegotiateResponse; + } + startStarted.resolve(); + return Promise.reject("Error with negotiate"); + }) + .on("GET", () => ""), + logger, + } as IHttpConnectionOptions; + + const connection = new HttpConnection("http://tempuri.org", options); + const hubConnection = HubConnection.create(connection, logger, new JsonHubProtocol(), { + nextRetryDelayInMilliseconds() { + nextRetryDelayCalledPromise.resolve(); + return 0; + }, + }); + + hubConnection.onreconnecting(() => { + onreconnectingCount++; + }); + + hubConnection.onreconnected(() => { + onreconnectedCount++; + }); + + hubConnection.onclose(() => { + closeCount++; + }); + + TestWebSocket.webSocketSet = new PromiseSource(); + const startPromise = hubConnection.start(); + await TestWebSocket.webSocketSet; + await TestWebSocket.webSocket.openSet; + TestWebSocket.webSocket.onopen(new TestEvent()); + TestWebSocket.webSocket.onmessage(new TestMessageEvent("{}\x1e")); + + await startPromise; + TestWebSocket.webSocket.close(); + TestWebSocket.webSocketSet = new PromiseSource(); + + await nextRetryDelayCalledPromise; + + expect(hubConnection.state).toBe(HubConnectionState.Reconnecting); + expect(onreconnectingCount).toBe(1); + expect(onreconnectedCount).toBe(0); + expect(closeCount).toBe(0); + + await startStarted; + await hubConnection.stop(); + + expect(hubConnection.state).toBe(HubConnectionState.Disconnected); + expect(onreconnectingCount).toBe(1); + expect(onreconnectedCount).toBe(0); + expect(closeCount).toBe(1); + }, + "Failed to complete negotiation with the server: Error with negotiate", + "Failed to start the connection: Error with negotiate"); + }); }); diff --git a/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts b/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts index ebdc6a07fb15..4cd71949f3e4 100644 --- a/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts +++ b/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts @@ -221,3 +221,57 @@ export class TestCloseEvent { public CAPTURING_PHASE: number = 0; public NONE: number = 0; } + +export class TestMessageEvent implements MessageEvent { + constructor(data: any) { + this.data = data; + } + public data: any; + public lastEventId: string = ""; + public origin: string = ""; + public ports: MessagePort[] = []; + public source: Window | null = null; + public composed: boolean = false; + public composedPath(): EventTarget[]; + public composedPath(): any[] { + throw new Error("Method not implemented."); + } + public code: number = 0; + public reason: string = ""; + public wasClean: boolean = false; + public initMessageEvent(typeArg: string, canBubbleArg: boolean, cancelableArg: boolean, data: any, origin: string, lastEventId: string): void { + throw new Error("Method not implemented."); + } + public bubbles: boolean = false; + public cancelBubble: boolean = false; + public cancelable: boolean = false; + public currentTarget!: EventTarget; + public defaultPrevented: boolean = false; + public eventPhase: number = 0; + public isTrusted: boolean = false; + public returnValue: boolean = false; + public scoped: boolean = false; + public srcElement!: Element | null; + public target!: EventTarget; + public timeStamp: number = 0; + public type: string = ""; + public deepPath(): EventTarget[] { + throw new Error("Method not implemented."); + } + public initEvent(type: string, bubbles?: boolean | undefined, cancelable?: boolean | undefined): void { + throw new Error("Method not implemented."); + } + public preventDefault(): void { + throw new Error("Method not implemented."); + } + public stopImmediatePropagation(): void { + throw new Error("Method not implemented."); + } + public stopPropagation(): void { + throw new Error("Method not implemented."); + } + public AT_TARGET: number = 0; + public BUBBLING_PHASE: number = 0; + public CAPTURING_PHASE: number = 0; + public NONE: number = 0; +} From dfbbc40f678ab5d2f3dd773b9cf2fef56fe96d01 Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 7 Apr 2021 16:07:47 -0700 Subject: [PATCH 4/8] Fix arguments order for call of HeartbeatSlow (#28698) (#31080) Co-authored-by: Roman Marusyk --- .../src/Internal/Infrastructure/KestrelTrace.cs | 6 +++--- src/Servers/Kestrel/Core/test/HeartbeatTests.cs | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs index 5a85007aab30..cf7ab76ac511 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs @@ -47,8 +47,8 @@ internal class KestrelTrace : IKestrelTrace private static readonly Action _notAllConnectionsAborted = LoggerMessage.Define(LogLevel.Debug, new EventId(21, nameof(NotAllConnectionsAborted)), "Some connections failed to abort during server shutdown."); - private static readonly Action _heartbeatSlow = - LoggerMessage.Define(LogLevel.Warning, new EventId(22, nameof(HeartbeatSlow)), @"As of ""{now}"", the heartbeat has been running for ""{heartbeatDuration}"" which is longer than ""{interval}"". This could be caused by thread pool starvation."); + private static readonly Action _heartbeatSlow = + LoggerMessage.Define(LogLevel.Warning, new EventId(22, "HeartbeatSlow"), @"As of ""{now}"", the heartbeat has been running for ""{heartbeatDuration}"" which is longer than ""{interval}"". This could be caused by thread pool starvation."); private static readonly Action _applicationNeverCompleted = LoggerMessage.Define(LogLevel.Critical, new EventId(23, nameof(ApplicationNeverCompleted)), @"Connection id ""{ConnectionId}"" application never completed"); @@ -196,7 +196,7 @@ public virtual void NotAllConnectionsAborted() public virtual void HeartbeatSlow(TimeSpan heartbeatDuration, TimeSpan interval, DateTimeOffset now) { - _heartbeatSlow(_logger, heartbeatDuration, interval, now, null); + _heartbeatSlow(_logger, now, heartbeatDuration, interval, null); } public virtual void ApplicationNeverCompleted(string connectionId) diff --git a/src/Servers/Kestrel/Core/test/HeartbeatTests.cs b/src/Servers/Kestrel/Core/test/HeartbeatTests.cs index 1af0a541a49d..49e7953715df 100644 --- a/src/Servers/Kestrel/Core/test/HeartbeatTests.cs +++ b/src/Servers/Kestrel/Core/test/HeartbeatTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -22,15 +23,16 @@ public void HeartbeatIntervalIsOneSecond() } [Fact] - public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsError() + public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsWarning() { var systemClock = new MockSystemClock(); var heartbeatHandler = new Mock(); var debugger = new Mock(); - var kestrelTrace = new Mock(); + var kestrelTrace = new TestKestrelTrace(); var handlerMre = new ManualResetEventSlim(); var handlerStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var now = systemClock.UtcNow; + var heartbeatDuration = TimeSpan.FromSeconds(2); heartbeatHandler.Setup(h => h.OnHeartbeat(now)).Callback(() => { @@ -41,7 +43,7 @@ public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsError() Task blockedHeartbeatTask; - using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, systemClock, debugger.Object, kestrelTrace.Object)) + using (var heartbeat = new Heartbeat(new[] { heartbeatHandler.Object }, systemClock, debugger.Object, kestrelTrace)) { blockedHeartbeatTask = Task.Run(() => heartbeat.OnHeartbeat()); @@ -56,11 +58,16 @@ public async Task HeartbeatTakingLongerThanIntervalIsLoggedAsError() await blockedHeartbeatTask.DefaultTimeout(); heartbeatHandler.Verify(h => h.OnHeartbeat(now), Times.Once()); - kestrelTrace.Verify(t => t.HeartbeatSlow(TimeSpan.FromSeconds(2), Heartbeat.Interval, now), Times.Once()); + + var warningMessage = kestrelTrace.Logger.Messages.Single(message => message.LogLevel == LogLevel.Warning).Message; + Assert.Equal($"As of \"{now.ToString(CultureInfo.InvariantCulture)}\", the heartbeat has been running for " + + $"\"{heartbeatDuration.ToString("c", CultureInfo.InvariantCulture)}\" which is longer than " + + $"\"{Heartbeat.Interval.ToString("c", CultureInfo.InvariantCulture)}\". " + + "This could be caused by thread pool starvation.", warningMessage); } [Fact] - public async Task HeartbeatTakingLongerThanIntervalIsNotLoggedAsErrorIfDebuggerAttached() + public async Task HeartbeatTakingLongerThanIntervalIsNotLoggedIfDebuggerAttached() { var systemClock = new MockSystemClock(); var heartbeatHandler = new Mock(); From 79dbbcad2dc8f65e8d75228162e71e75c3e3de9b Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Wed, 7 Apr 2021 18:07:30 -0700 Subject: [PATCH 5/8] Quarantine one failing test (#31045) (#31296) - #31044 - `RazorPagesTemplate_RazorRuntimeCompilation_BuildsAndPublishes()` Co-authored-by: Doug Bunting <6431421+dougbu@users.noreply.github.com> --- src/ProjectTemplates/test/RazorPagesTemplateTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs index 919f410f0f61..0bfea48705b3 100644 --- a/src/ProjectTemplates/test/RazorPagesTemplateTest.cs +++ b/src/ProjectTemplates/test/RazorPagesTemplateTest.cs @@ -223,6 +223,7 @@ public async Task RazorPagesTemplate_IndividualAuth(bool useLocalDB) } [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/31044")] public async Task RazorPagesTemplate_RazorRuntimeCompilation_BuildsAndPublishes() { var project = await BuildAndPublishRazorPagesTemplate(auth: null, new[] { "--razor-runtime-compilation" }); From 4efb96c2c21e03c7f5dab63a4267324c653f02f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 8 Apr 2021 08:12:05 -0700 Subject: [PATCH 6/8] Fix merge --- .../clients/ts/signalr/tests/HubConnection.Reconnect.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts b/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts index ae91a59656fe..756734db2fc5 100644 --- a/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts @@ -11,8 +11,6 @@ import { JsonHubProtocol } from "../src/JsonHubProtocol"; import { VerifyLogger } from "./Common"; import { TestConnection } from "./TestConnection"; -import { TestHttpClient } from "./TestHttpClient"; -import { TestEvent, TestMessageEvent, TestWebSocket } from "./TestWebSocket"; import { PromiseSource } from "./Utils"; import { TestHttpClient } from "./TestHttpClient"; import { TestWebSocket, TestEvent, TestMessageEvent } from "./TestWebSocket"; From 0da8e7503a7d18c350413553ef199ce6f3a2d78e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 8 Apr 2021 08:13:27 -0700 Subject: [PATCH 7/8] Fix merge --- .../clients/ts/signalr/tests/TestWebSocket.ts | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts b/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts index 529185f3d079..dc1e1a01aa33 100644 --- a/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts +++ b/src/SignalR/clients/ts/signalr/tests/TestWebSocket.ts @@ -285,57 +285,3 @@ export class TestMessageEvent implements MessageEvent { public CAPTURING_PHASE: number = 0; public NONE: number = 0; } - -export class TestMessageEvent implements MessageEvent { - constructor(data: any) { - this.data = data; - } - public data: any; - public lastEventId: string = ""; - public origin: string = ""; - public ports: MessagePort[] = []; - public source: Window | null = null; - public composed: boolean = false; - public composedPath(): EventTarget[]; - public composedPath(): any[] { - throw new Error("Method not implemented."); - } - public code: number = 0; - public reason: string = ""; - public wasClean: boolean = false; - public initMessageEvent(typeArg: string, canBubbleArg: boolean, cancelableArg: boolean, data: any, origin: string, lastEventId: string): void { - throw new Error("Method not implemented."); - } - public bubbles: boolean = false; - public cancelBubble: boolean = false; - public cancelable: boolean = false; - public currentTarget!: EventTarget; - public defaultPrevented: boolean = false; - public eventPhase: number = 0; - public isTrusted: boolean = false; - public returnValue: boolean = false; - public scoped: boolean = false; - public srcElement!: Element | null; - public target!: EventTarget; - public timeStamp: number = 0; - public type: string = ""; - public deepPath(): EventTarget[] { - throw new Error("Method not implemented."); - } - public initEvent(type: string, bubbles?: boolean | undefined, cancelable?: boolean | undefined): void { - throw new Error("Method not implemented."); - } - public preventDefault(): void { - throw new Error("Method not implemented."); - } - public stopImmediatePropagation(): void { - throw new Error("Method not implemented."); - } - public stopPropagation(): void { - throw new Error("Method not implemented."); - } - public AT_TARGET: number = 0; - public BUBBLING_PHASE: number = 0; - public CAPTURING_PHASE: number = 0; - public NONE: number = 0; -} From 9bf03a269d738ec84efbbcd947a5e1acd87ce9d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 8 Apr 2021 08:14:56 -0700 Subject: [PATCH 8/8] Update HubConnection.Reconnect.test.ts --- .../clients/ts/signalr/tests/HubConnection.Reconnect.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts b/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts index 756734db2fc5..50ad4a28d8ec 100644 --- a/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts +++ b/src/SignalR/clients/ts/signalr/tests/HubConnection.Reconnect.test.ts @@ -876,6 +876,6 @@ describe("auto reconnect", () => { expect(closeCount).toBe(1); }, "Failed to complete negotiation with the server: Error with negotiate", - "Failed to start the connection: Error with negotiate"); + "Failed to start the connection: Error: Failed to complete negotiation with the server: Error with negotiate"); }); });