From 6bc5144129828c09718931332b6be3a145e5a322 Mon Sep 17 00:00:00 2001 From: ccic Date: Tue, 13 Aug 2019 13:23:32 +0800 Subject: [PATCH 1/8] add e2e test get remote ip (#625) * add test case to track "get client remote IP" * fix the wrong comparison * add another two checks * add test case to track "get client remote IP" --- .../SignalR/TestHub.cs | 25 +++++++++++++++++++ .../E2ETest/ServiceE2EFactsBase.cs | 5 +++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.Azure.SignalR.E2ETests/SignalR/TestHub.cs b/test/Microsoft.Azure.SignalR.E2ETests/SignalR/TestHub.cs index 2cede2f86..9a78caca7 100644 --- a/test/Microsoft.Azure.SignalR.E2ETests/SignalR/TestHub.cs +++ b/test/Microsoft.Azure.SignalR.E2ETests/SignalR/TestHub.cs @@ -31,6 +31,31 @@ public override Task OnDisconnectedAsync(Exception exception) return Task.CompletedTask; } + // Verify whether 'get client IP' is working or not + public void TestClientIPEcho(string message) + { + if (!string.IsNullOrEmpty(Context.GetHttpContext().Connection.RemoteIpAddress?.ToString())) + { + Clients.Caller.SendAsync(nameof(TestClientIPEcho), message); + } + } + + public void TestClientUser(string message) + { + if (Context.User != null) + { + Clients.Caller.SendAsync(nameof(TestClientUser), message); + } + } + + public void TestClientQueryString(string message) + { + if (Context.GetHttpContext().Request.QueryString != null) + { + Clients.Caller.SendAsync(nameof(TestClientQueryString), message); + } + } + public void Echo(string message) { Clients.Caller.SendAsync(nameof(Echo), message); diff --git a/test/Microsoft.Azure.SignalR.Tests.Common/E2ETest/ServiceE2EFactsBase.cs b/test/Microsoft.Azure.SignalR.Tests.Common/E2ETest/ServiceE2EFactsBase.cs index 97e29811b..6d84706a7 100644 --- a/test/Microsoft.Azure.SignalR.Tests.Common/E2ETest/ServiceE2EFactsBase.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/E2ETest/ServiceE2EFactsBase.cs @@ -19,7 +19,10 @@ public class ServiceE2EFactsBase : VerifiableLoggedTest new object[] { "Broadcast", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : 1, messages : _defaultMessage)) }, new object[] { "SendToClient", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, new object[] { "SendToUser", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, - new object[] { "SendToGroup", GetGroupSize(DefaultSendGroupIndex), new Func(GroupTask) } + new object[] { "SendToGroup", GetGroupSize(DefaultSendGroupIndex), new Func(GroupTask) }, + new object[] { "TestClientIPEcho", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, + new object[] { "TestClientUser", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, + new object[] { "TestClientQueryString", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, }; private const int DefaultClientCount = 3; From f3db84171a5b1649373d192d4928b6ec160aa1f6 Mon Sep 17 00:00:00 2001 From: "Liangying.Wei" Date: Thu, 15 Aug 2019 11:30:29 +0800 Subject: [PATCH 2/8] Update version.props to 1.0.13 (#629) --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index 9f2cffd82..d53fe165a 100644 --- a/version.props +++ b/version.props @@ -1,6 +1,6 @@ - 1.0.12 + 1.0.13 preview1 1.1.0 $(VersionPrefix) From 4fa7f8ad455596b000a6b912210d06b973df068c Mon Sep 17 00:00:00 2001 From: "Liangying.Wei" Date: Tue, 20 Aug 2019 17:33:49 +0800 Subject: [PATCH 3/8] Update the 404 case (#631) * Update the 404 case --- docs/tsg.md | 67 ++++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/docs/tsg.md b/docs/tsg.md index 0d9b1e6be..0c6bd6aeb 100644 --- a/docs/tsg.md +++ b/docs/tsg.md @@ -1,6 +1,6 @@ # Troubleshooting Guide -This guidence is to provide useful troubleshooting guide based on the common issues customers encountered and resolved in the past years. +This guidance is to provide useful troubleshooting guide based on the common issues customers encountered and resolved in the past years. - [Access token too long](#access_token_too_long) - [TLS 1.2 required](#tls_1.2_required) @@ -15,7 +15,7 @@ This guidence is to provide useful troubleshooting guide based on the common iss ### Possible errors: -1. Client side `ERR_CONNECTION_` +1. Client-side `ERR_CONNECTION_` 2. 414 URI Too Long 3. 413 Payload Too Large 4. Access Token must not be longer than 4K. 413 Request Entity Too Large @@ -23,7 +23,7 @@ This guidence is to provide useful troubleshooting guide based on the common iss ### Root cause: For HTTP/2, the max length for a single header is **4K**, so if you are using browser to access Azure service, you will encounter this limitation with `ERR_CONNECTION_` error. -For HTTP/1.1, or c# clients, the max URI length is **12K**, max header length is **16K**. +For HTTP/1.1, or C# clients, the max URI length is **12K**, the max header length is **16K**. With SDK version **1.0.6** or higher, `/negotiate` will throw `413 Payload Too Large` when the generated access token is larger than **4K**. @@ -32,7 +32,7 @@ By default, claims from `context.User.Claims` are included when generating JWT a In some cases, `context.User.Claims` are leveraged to store lots of information for app server, most of which are not used by `Hub`s but by other components. -The generated access token are passed through network, and for websocket/SSE connections, access tokens are passed through query strings. So as the best practice, we suggest only passing **necessary** claims from client through **ASRS** to your app server when the Hub needs. +The generated access token is passed through the network, and for WebSocket/SSE connections, access tokens are passed through query strings. So as the best practice, we suggest only passing **necessary** claims from the client through **ASRS** to your app server when the Hub needs. There is a `ClaimsProvider` for you to customize the claims passing to **ASRS** inside the access token. @@ -61,7 +61,7 @@ services.MapAzureSignalR(GetType().FullName, options => Take ASP.NET Core one for example (ASP.NET one is similar): 1. From browser: - Take chrome for example, **F12** to open the console window, and switch to **Network** tab. You might need to refresh the page using **F5** to capture the network from the very beginning. + Take Chrome as an example, you can use **F12** to open the console window, and switch to **Network** tab. You might need to refresh the page using **F5** to capture the network from the very beginning. ![Chrome View Network](./images/chrome_network.gif) @@ -81,7 +81,7 @@ Take ASP.NET Core one for example (ASP.NET one is similar): 3. "An error occurred while making the HTTP request to https://. This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case. This could also be caused by a mismatch of the security binding between the client and the server." ### Root cause: -Azure Service only support TLS1.2 for security concerns. With .NET framework, it is possible that TLS1.2 is not the default protocol. As a result, the server connections to ASRS can not be successfully established. +Azure Service only supports TLS1.2 for security concerns. With .NET framework, it is possible that TLS1.2 is not the default protocol. As a result, the server connections to ASRS can not be successfully established. ### Troubleshooting Guide 1. If this error can be repro-ed locally, uncheck *Just My Code* and throw all CLR exceptions and debug the app server locally to see what exception throws. @@ -112,14 +112,16 @@ ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; ## 404 returned for client requests -For a SignalR persistent connection, it first `/negotiate` to Azure SignalR service and then establish the real connection to Azure SignalR service. Our load balancer must ensure that the `/negotiate` request and the following connect request goes to the similar instance of the Service otherwise 404 occurs. Our load balancer now relies on the *signature* part of the generated `access_token` to keep the session sticky. +For a SignalR persistent connection, it first `/negotiate` to Azure SignalR service and then establishes the real connection to Azure SignalR service. Our load balancer must ensure that the `/negotiate` request and the following connect request goes to the same instance of the Service otherwise 404 occurs. Our load balancer relies on the *asrs_request_id* query string (added in SDK version 1.0.11) or the *signature* part of the generated `access_token`(if `asrs_request_id` query string does not exist) to keep the session sticky. ### Troubleshooting Guide -1. Following [How to view outgoing requests](#view_request) to get the request from client the the service. -1. Check the url of the request when 404 occurs. If the url is targeting to your web app, and similar to `{your_web_app}/hubs/{hubName}`, check if the client `SkipNegotiation` is `true`. When using Azure SignalR, client receives redirect url when it first negotiates with the app server. Client should **NOT** skip negotiation when using Azure SignalR. -1. Check if there are multiple `access_token` inside the outgoing request. Our load balancer is not able to handle duplicate `access_token` correctly, as described in [#346](https://github.com/Azure/azure-signalr/issues/346). -1. Another 404 can happen when the connect request is handled more than **5** seconds after `/negotiate` is called. Check the timestamp of client request, and open an issue to us if the request to the service has very slow response. -1. If you find the `/negotiate` request and the following connect request carry different access token through the above steps, the most possible reason is using HttpConnectionOptions.AccessTokenProvider in a **WRONG** way: +There are two kind of 404 errors with different symptoms. +1. The symptom for one kind of 404 is that the 404 errors happens **consistently**. For this kind of 404, please check: + 1. Following [How to view outgoing requests](#view_request) to get the request from the client to the service. + 1. Check the URL of the request when 404 occurs. If the URL is targeting to your web app, and similar to `{your_web_app}/hubs/{hubName}`, check if the client `SkipNegotiation` is `true`. When using Azure SignalR, the client receives redirect URL when it first negotiates with the app server. The client should **NOT** skip negotiation when using Azure SignalR. + 1. For SDK older than 1.0.11, check if there are multiple `access_token` inside the outgoing request. With old SDK which does not contain `asrs_request_id` in the query string, the load balancer of the service is not able to handle duplicate `access_token` correctly, as described in [#346](https://github.com/Azure/azure-signalr/issues/346). + 1. Another 404 can happen when the connect request is handled more than **5** seconds after `/negotiate` is called. Check the timestamp of the client request, and open an issue to us if the request to the service has a very slow response. + 1. If you find the `/negotiate` request and the following connect request carry different access token through the above steps, the most possible reason is using HttpConnectionOptions.AccessTokenProvider in a **WRONG** way: ```c# var url = ... @@ -142,6 +144,8 @@ For a SignalR persistent connection, it first `/negotiate` to Azure SignalR serv var hubConnectionBuilder = new HubConnectionBuilder().WithUrl(url); var hubConnection = hubConnectionBuilder.build(); ``` + +2. Another kind of 404 is always transient. It happens within a short period and disappears after that period. This kind of 404 can happen to clients using SSE or LongPolling for ASP.NET Core SignalR, and to clients with ASP.NET SignalR. This kind of 404 can happen during the period of Azure SignalR maintenance or upgrade when the connection is disconnected from the service. Having [restart the connection](#restart_connection) logic in the client-side can minimize the impact of the issue. ## 401 Unauthorized returned for client requests @@ -150,14 +154,15 @@ Currently the default value of JWT token's lifetime is 1 hour. For ASP.NET Core SignalR, when it is using WebSocket transport type, it is OK. -For ASP.NET Core SignalR's other transport type, SSE and longpolling, this means by default the connection can at most persist for 1 hour. +For ASP.NET Core SignalR's other transport type, SSE and long-polling, this means by default the connection can at most persist for 1 hour. For ASP.NET SignalR, the client sends a `/ping` KeepAlive request to the service from time to time, when the `/ping` fails, the client **aborts** the connection and never reconnect. This means, for ASP.NET SignalR, the default token lifetime makes the connection lasts for **at most** 1 hour for all the transport type. ### Solution -For security concerns, extend TTL is not encouraged. We suggest adding reconnect logic from client to restart the connection when such 401 occurs. When client restarts the connection, it will negotiate with app server to get the JWT token again and get a renewed token. +For security concerns, extend TTL is not encouraged. We suggest adding reconnect logic from the client to restart the connection when such 401 occurs. When the client restarts the connection, it will negotiate with app server to get the JWT token again and get a renewed token. + [Sample code](../samples/) contains restarting connection logic with *ALWAYS RETRY* strategy: * [ASP.NET Core C# Client](../samples/ChatSample/ChatSample.CSharpClient/Program.cs#L64) @@ -174,7 +179,7 @@ For security concerns, extend TTL is not encouraged. We suggest adding reconnect This error is reported when there is no server connection to Azure SignalR Service connected. ### Troubleshooting Guide -Please enable server side trace to find out the error details when server tries to connect to Azure SignalR Service. +Please enable server-side trace to find out the error details when the server tries to connect to Azure SignalR Service. #### Enable server side logging for ASP.NET Core SignalR Server side logging for ASP.NET Core SignalR integrates with the `ILogger` based [logging](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1&tabs=aspnetcore2x) provided in the ASP.NET Core framework. You can enable server side logging by using `ConfigureLogging`, a sample usage as follows: @@ -211,9 +216,9 @@ When using SDK version >= `1.0.0`, you can enable traces by adding the following ## Client connection drop -When client is connected to the Azure SignalR, the persistent connection between client and Azure SignalR can sometimes drop for different reasons. This section describes several possibilities causing such connection drop, and provides some guidance to how to identify the root cause. +When the client is connected to the Azure SignalR, the persistent connection between the client and Azure SignalR can sometimes drop for different reasons. This section describes several possibilities causing such connection drop and provides some guidance on how to identify the root cause. -### Possible errors seen from the client side +### Possible errors seen from the client-side 1. `The remote party closed the WebSocket connection without completing the close handshake` 2. `Service timeout. 30.00ms elapsed without receiving a message from service.` 3. `{"type":7,"error":"Connection closed with an error."}` @@ -222,27 +227,27 @@ When client is connected to the Azure SignalR, the persistent connection between ### Root cause: Client connections can drop under various circumstances: 1. When `Hub` throws exceptions with the incoming request. -2. When the server connection it routed to drops, see below section for details on [server connection drops](#server_connection_drop). -3. When network connectivity issue happens between client and SignalR Service. -4. When SignalR Service has some internal errors such as instance restart, failover, deployment, and so on. +2. When the server connection the client routed to drops, see below section for details on [server connection drops](#server_connection_drop). +3. When a network connectivity issue happens between client and SignalR Service. +4. When SignalR Service has some internal errors like instance restart, failover, deployment, and so on. ### Troubleshooting Guide -1. Open app server side log to see if anything abnormal took place -2. Check app server side event log to see if the app server restarted -3. Create an issue to us providing time frame, and email the resource name to us +1. Open app server-side log to see if anything abnormal took place +2. Check app server-side event log to see if the app server restarted +3. Create an issue to us providing the time frame, and email the resource name to us ## Server connection drop -When app server starts, in the background, the Azure SDK starts to initiate server connections to the remote Azure SignalR. As described in [Internals of Azure SignalR Service](internal.md), Azure SignalR routes incoming client traffics to these server connections. Once a server connection is dropped, all the client connections it serves will be closed too. +When the app server starts, in the background, the Azure SDK starts to initiate server connections to the remote Azure SignalR. As described in [Internals of Azure SignalR Service](internal.md), Azure SignalR routes incoming client traffics to these server connections. Once a server connection is dropped, all the client connections it serves will be closed too. -As the connections between app server and SignalR Service are persistent connections, they may experience network connectivity issues. In the Server SDK, we have **Always Reconnect** strategy to server connections. As the best practice, we also encourage users to add continuous reconnect logic to the clients with a random delay time to avoid massive simultaneous requests to the server. +As the connections between the app server and SignalR Service are persistent connections, they may experience network connectivity issues. In the Server SDK, we have **Always Reconnect** strategy to server connections. As the best practice, we also encourage users to add continuous reconnect logic to the clients with a random delay time to avoid massive simultaneous requests to the server. -On regular basis there are new version releases for the Azure SignalR Service, and sometimes the Azure wide OS patching or upgrades or occasionally interruption from our dependent services. These may bring in a very short period of service disruption, but as long as client side has the disconnect/reconnect mechanism, the impact is minimal like any client-side caused disconnect-reconnect. +On a regular basis there are new version releases for the Azure SignalR Service, and sometimes the Azure wide OS patching or upgrades or occasionally interruption from our dependent services. These may bring in a very short period of service disruption, but as long as client-side has the disconnect/reconnect mechanism, the impact is minimal like any client-side caused disconnect-reconnect. -This section describes several possibilities leading to server connection drop, and provides some guidance to how to identify the root cause. +This section describes several possibilities leading to server connection drop and provides some guidance on how to identify the root cause. -### Possible errors seen from server side: +### Possible errors seen from server-side: 1. `[Error]Connection "..." to the service was dropped` 2. `The remote party closed the WebSocket connection without completing the close handshake` 3. `Service timeout. 30.00ms elapsed without receiving a message from service.` @@ -251,6 +256,6 @@ This section describes several possibilities leading to server connection drop, Server-service connection is closed by **ASRS**(**A**zure **S**ignal**R** **S**ervice). ### Troubleshooting Guide -1. Open app server side log to see if anything abnormal took place -2. Check app server side event log to see if the app server restarted -3. Create an issue to us providing time frame, and email the resource name to us +1. Open app server-side log to see if anything abnormal took place +2. Check app server-side event log to see if the app server restarted +3. Create an issue to us providing the time frame, and email the resource name to us From cc3bee9d41bfd4c5445c2c970ecca9e57068ab5d Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Wed, 21 Aug 2019 14:39:43 +0800 Subject: [PATCH 4/8] Wanl/move test classes and move remote ip test in aspnet (#636) * move test classes * refactor test code, remove remote ip test in aspnet --- .../AspNetSignalRServiceE2EFacts.cs | 13 ++++++ .../ServiceMessageBusTests.cs | 1 + .../TestClasses/TestServiceConnectionProxy.cs | 1 + .../SignalR/SignalRServiceE2EFacts.cs | 19 ++++++++ .../E2ETest/ServiceE2EFactsBase.cs | 45 +++++++++---------- ...icrosoft.Azure.SignalR.Tests.Common.csproj | 9 ++-- .../Properties/AssemblyInfo.cs | 3 ++ .../TestBaseServiceConnectionContainer.cs | 2 +- .../TestClasses/TestConnectionContext.cs | 2 +- .../TestClasses/TestConnectionFactory.cs | 2 +- .../TestClasses/TestServiceConnection.cs | 2 +- .../TestServiceConnectionContainer.cs | 3 +- .../TestServiceConnectionContainerFactory.cs | 2 +- .../TestServiceConnectionFactory.cs | 2 +- .../TestClasses/TestServiceMessageHandler.cs | 2 +- 15 files changed, 73 insertions(+), 35 deletions(-) create mode 100644 test/Microsoft.Azure.SignalR.Tests.Common/Properties/AssemblyInfo.cs rename test/{Microsoft.Azure.SignalR.AspNet.Tests => Microsoft.Azure.SignalR.Tests.Common}/TestClasses/TestBaseServiceConnectionContainer.cs (94%) rename test/{Microsoft.Azure.SignalR.AspNet.Tests => Microsoft.Azure.SignalR.Tests.Common}/TestClasses/TestConnectionContext.cs (96%) rename test/{Microsoft.Azure.SignalR.AspNet.Tests => Microsoft.Azure.SignalR.Tests.Common}/TestClasses/TestConnectionFactory.cs (97%) rename test/{Microsoft.Azure.SignalR.AspNet.Tests => Microsoft.Azure.SignalR.Tests.Common}/TestClasses/TestServiceConnection.cs (98%) rename test/{Microsoft.Azure.SignalR.AspNet.Tests => Microsoft.Azure.SignalR.Tests.Common}/TestClasses/TestServiceConnectionContainer.cs (92%) rename test/{Microsoft.Azure.SignalR.AspNet.Tests => Microsoft.Azure.SignalR.Tests.Common}/TestClasses/TestServiceConnectionContainerFactory.cs (94%) rename test/{Microsoft.Azure.SignalR.AspNet.Tests => Microsoft.Azure.SignalR.Tests.Common}/TestClasses/TestServiceConnectionFactory.cs (94%) rename test/{Microsoft.Azure.SignalR.AspNet.Tests => Microsoft.Azure.SignalR.Tests.Common}/TestClasses/TestServiceMessageHandler.cs (93%) diff --git a/test/Microsoft.Azure.SignalR.AspNet.E2ETests/AspNetSignalRServiceE2EFacts.cs b/test/Microsoft.Azure.SignalR.AspNet.E2ETests/AspNetSignalRServiceE2EFacts.cs index 7e96d4713..c486ded93 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.E2ETests/AspNetSignalRServiceE2EFacts.cs +++ b/test/Microsoft.Azure.SignalR.AspNet.E2ETests/AspNetSignalRServiceE2EFacts.cs @@ -1,7 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Azure.SignalR.Tests.Common; +using Xunit; using Xunit.Abstractions; namespace Microsoft.Azure.SignalR.AspNet.Tests @@ -12,5 +17,13 @@ public AspNetSignalRServiceE2EFacts(ITestOutputHelper output) : base(new TestServerFactory(), new TestClientSetFactory(), output) { } + + [ConditionalTheory] + [SkipIfConnectionStringNotPresent] + [MemberData(nameof(TestDataBase))] + public Task RunE2ETests(string methodName, int expectedMessageCount, Func coreTask) + { + return RunE2ETestsBase(methodName, expectedMessageCount, coreTask); + } } } \ No newline at end of file diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/ServiceMessageBusTests.cs b/test/Microsoft.Azure.SignalR.AspNet.Tests/ServiceMessageBusTests.cs index 9369eb76d..8d38af8d6 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/ServiceMessageBusTests.cs +++ b/test/Microsoft.Azure.SignalR.AspNet.Tests/ServiceMessageBusTests.cs @@ -10,6 +10,7 @@ using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Messaging; using Microsoft.Azure.SignalR.Protocol; +using Microsoft.Azure.SignalR.Tests.Common; using Xunit; namespace Microsoft.Azure.SignalR.AspNet.Tests diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionProxy.cs b/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionProxy.cs index 111c5642c..a1433d125 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionProxy.cs +++ b/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionProxy.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.Azure.SignalR.Protocol; +using Microsoft.Azure.SignalR.Tests.Common; using Microsoft.Extensions.Logging; namespace Microsoft.Azure.SignalR.AspNet.Tests diff --git a/test/Microsoft.Azure.SignalR.E2ETests/SignalR/SignalRServiceE2EFacts.cs b/test/Microsoft.Azure.SignalR.E2ETests/SignalR/SignalRServiceE2EFacts.cs index 3938b49e1..4c8ed9235 100644 --- a/test/Microsoft.Azure.SignalR.E2ETests/SignalR/SignalRServiceE2EFacts.cs +++ b/test/Microsoft.Azure.SignalR.E2ETests/SignalR/SignalRServiceE2EFacts.cs @@ -1,15 +1,34 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Azure.SignalR.Tests.Common; +using Xunit; using Xunit.Abstractions; namespace Microsoft.Azure.SignalR.Tests { public class SignalRServiceE2EFacts : ServiceE2EFactsBase { + public static object[][] TestData = TestDataBase.Concat(new object[][] { + new object[] { "TestClientIPEcho", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : DefaultMessage)) }, + new object[] { "TestClientUser", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : DefaultMessage)) }, + new object[] { "TestClientQueryString", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : DefaultMessage)) }, + }).ToArray(); + public SignalRServiceE2EFacts(ITestOutputHelper output) : base(new TestServerFactory(), new TestClientSetFactory(), output) { } + + [ConditionalTheory] + [SkipIfConnectionStringNotPresent] + [MemberData(nameof(TestData))] + public Task RunE2ETests(string methodName, int expectedMessageCount, Func coreTask) + { + return RunE2ETestsBase(methodName, expectedMessageCount, coreTask); + } } } \ No newline at end of file diff --git a/test/Microsoft.Azure.SignalR.Tests.Common/E2ETest/ServiceE2EFactsBase.cs b/test/Microsoft.Azure.SignalR.Tests.Common/E2ETest/ServiceE2EFactsBase.cs index 6d84706a7..0540781b8 100644 --- a/test/Microsoft.Azure.SignalR.Tests.Common/E2ETest/ServiceE2EFactsBase.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/E2ETest/ServiceE2EFactsBase.cs @@ -14,22 +14,26 @@ namespace Microsoft.Azure.SignalR.Tests.Common { public class ServiceE2EFactsBase : VerifiableLoggedTest { - public static object[][] TestData = { - new object[] { "Echo", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, - new object[] { "Broadcast", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : 1, messages : _defaultMessage)) }, - new object[] { "SendToClient", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, - new object[] { "SendToUser", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, + private readonly ITestServerFactory _testServerFactory; + private readonly ITestClientSetFactory _testClientSetFactory; + private readonly ITestOutputHelper _output; + + public static string DefaultMessage { get; } = $"Message from {nameof(ServiceE2EFactsBase)}"; + + public static int DefaultClientCount { get; } = 3; + + public static int DefaultDelayMilliseconds { get; } = 3000; + + public static int DefaultSendGroupIndex { get; } = 0; + + public static object[][] TestDataBase { get; } = { + new object[] { "Echo", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : DefaultMessage)) }, + new object[] { "Broadcast", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : 1, messages : DefaultMessage)) }, + new object[] { "SendToClient", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : DefaultMessage)) }, + new object[] { "SendToUser", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : DefaultMessage)) }, new object[] { "SendToGroup", GetGroupSize(DefaultSendGroupIndex), new Func(GroupTask) }, - new object[] { "TestClientIPEcho", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, - new object[] { "TestClientUser", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, - new object[] { "TestClientQueryString", DefaultClientCount, new Func((methodName, clients) => clients.SendAsync(methodName, sendCount : DefaultClientCount, messages : _defaultMessage)) }, }; - private const int DefaultClientCount = 3; - private const int DefaultDelayMilliseconds = 3000; - private const int DefaultSendGroupIndex = 0; - - private static readonly string _defaultMessage = $"Message from {nameof(ServiceE2EFactsBase)}"; private static IDictionary ConnectionGroupMap { get @@ -38,9 +42,7 @@ private static IDictionary ConnectionGroupMap } } - private readonly ITestServerFactory _testServerFactory; - private readonly ITestClientSetFactory _testClientSetFactory; - private readonly ITestOutputHelper _output; + public ServiceE2EFactsBase(ITestServerFactory testServerFactory, ITestClientSetFactory testClientSetFactory, ITestOutputHelper output) : base(output) { @@ -49,10 +51,7 @@ public ServiceE2EFactsBase(ITestServerFactory testServerFactory, ITestClientSetF _output = output; } - [ConditionalTheory] - [SkipIfConnectionStringNotPresent] - [MemberData(nameof(TestData))] - public async Task RunE2ETests(string methodName, int expectedMessageCount, Func coreTask) + protected async Task RunE2ETestsBase(string methodName, int expectedMessageCount, Func coreTask) { ITestServer server = null; try @@ -79,16 +78,16 @@ public async Task RunE2ETests(string methodName, int expectedMessageCount, Func< private static async Task GroupTask(string methodName, ITestClientSet clients) { await clients.ManageGroupAsync("JoinGroup", ConnectionGroupMap); - await clients.SendAsync(methodName, sendCount: 1, messages: new[] { GetGroupName(DefaultSendGroupIndex), _defaultMessage }); + await clients.SendAsync(methodName, sendCount: 1, messages: new[] { GetGroupName(DefaultSendGroupIndex), DefaultMessage }); await Task.Delay(DefaultDelayMilliseconds); await clients.ManageGroupAsync("LeaveGroup", ConnectionGroupMap); - await clients.SendAsync(methodName, messages: new[] { GetGroupName(DefaultSendGroupIndex), _defaultMessage }); + await clients.SendAsync(methodName, messages: new[] { GetGroupName(DefaultSendGroupIndex), DefaultMessage }); await Task.Delay(DefaultDelayMilliseconds); } private static string GetGroupName(int ind) => $"group_{ind % 2}"; - private static int GetGroupSize(int ind) => (from entry in ConnectionGroupMap where GetGroupName(ind) == entry.Value select entry).Count(); + protected static int GetGroupSize(int ind) => (from entry in ConnectionGroupMap where GetGroupName(ind) == entry.Value select entry).Count(); private void Shuffle(T[] array) { diff --git a/test/Microsoft.Azure.SignalR.Tests.Common/Microsoft.Azure.SignalR.Tests.Common.csproj b/test/Microsoft.Azure.SignalR.Tests.Common/Microsoft.Azure.SignalR.Tests.Common.csproj index a89949f42..fa40b56b8 100644 --- a/test/Microsoft.Azure.SignalR.Tests.Common/Microsoft.Azure.SignalR.Tests.Common.csproj +++ b/test/Microsoft.Azure.SignalR.Tests.Common/Microsoft.Azure.SignalR.Tests.Common.csproj @@ -7,14 +7,15 @@ true - - - - + + + + + diff --git a/test/Microsoft.Azure.SignalR.Tests.Common/Properties/AssemblyInfo.cs b/test/Microsoft.Azure.SignalR.Tests.Common/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..e18a2bd0c --- /dev/null +++ b/test/Microsoft.Azure.SignalR.Tests.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Azure.SignalR.AspNet.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] \ No newline at end of file diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestBaseServiceConnectionContainer.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestBaseServiceConnectionContainer.cs similarity index 94% rename from test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestBaseServiceConnectionContainer.cs rename to test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestBaseServiceConnectionContainer.cs index bdd55f224..b737b7169 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestBaseServiceConnectionContainer.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestBaseServiceConnectionContainer.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.Azure.SignalR.Protocol; -namespace Microsoft.Azure.SignalR.AspNet.Tests +namespace Microsoft.Azure.SignalR.Tests.Common { internal sealed class TestBaseServiceConnectionContainer : ServiceConnectionContainerBase { diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestConnectionContext.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestConnectionContext.cs similarity index 96% rename from test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestConnectionContext.cs rename to test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestConnectionContext.cs index e4dc34516..79a910cb6 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestConnectionContext.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestConnectionContext.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; -namespace Microsoft.Azure.SignalR.AspNet.Tests +namespace Microsoft.Azure.SignalR.Tests.Common { internal sealed class TestConnectionContext : ConnectionContext { diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestConnectionFactory.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestConnectionFactory.cs similarity index 97% rename from test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestConnectionFactory.cs rename to test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestConnectionFactory.cs index c1c7970cb..f315e3531 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestConnectionFactory.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestConnectionFactory.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; -namespace Microsoft.Azure.SignalR.AspNet.Tests +namespace Microsoft.Azure.SignalR.Tests.Common { internal sealed class TestConnectionFactory : IConnectionFactory { diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnection.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnection.cs similarity index 98% rename from test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnection.cs rename to test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnection.cs index 9219bfa57..3e01f6814 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnection.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnection.cs @@ -7,7 +7,7 @@ using Microsoft.Azure.SignalR.Common; using Microsoft.Azure.SignalR.Protocol; -namespace Microsoft.Azure.SignalR.AspNet.Tests +namespace Microsoft.Azure.SignalR.Tests.Common { internal sealed class TestServiceConnection : ServiceConnectionBase { diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionContainer.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnectionContainer.cs similarity index 92% rename from test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionContainer.cs rename to test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnectionContainer.cs index 445ef4961..fc2afec17 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionContainer.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnectionContainer.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.Azure.SignalR.Protocol; -namespace Microsoft.Azure.SignalR.AspNet.Tests +namespace Microsoft.Azure.SignalR.Tests.Common { internal sealed class TestServiceConnectionContainer : IServiceConnectionContainer, IServiceConnection { @@ -33,6 +33,7 @@ public TestServiceConnectionContainer(string name, Action<(ServiceMessage, IServ public Task StartAsync() { + ConnectionStatusChanged?.Invoke(new StatusChange(ServiceConnectionStatus.Connecting, Status)); return Task.CompletedTask; } diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionContainerFactory.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnectionContainerFactory.cs similarity index 94% rename from test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionContainerFactory.cs rename to test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnectionContainerFactory.cs index ac2f25324..ff2e780e5 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionContainerFactory.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnectionContainerFactory.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using Microsoft.Azure.SignalR.Protocol; -namespace Microsoft.Azure.SignalR.AspNet.Tests +namespace Microsoft.Azure.SignalR.Tests.Common { internal class TestServiceConnectionContainerFactory : IServiceConnectionContainerFactory { diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionFactory.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnectionFactory.cs similarity index 94% rename from test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionFactory.cs rename to test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnectionFactory.cs index ed0a668b7..18bab17ed 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceConnectionFactory.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnectionFactory.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Azure.SignalR.AspNet.Tests +namespace Microsoft.Azure.SignalR.Tests.Common { internal sealed class TestServiceConnectionFactory : IServiceConnectionFactory { diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceMessageHandler.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceMessageHandler.cs similarity index 93% rename from test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceMessageHandler.cs rename to test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceMessageHandler.cs index bef408f2c..d853afc3a 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/TestClasses/TestServiceMessageHandler.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceMessageHandler.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.Azure.SignalR.Protocol; -namespace Microsoft.Azure.SignalR.AspNet.Tests +namespace Microsoft.Azure.SignalR.Tests.Common { internal sealed class TestServiceMessageHandler : IServiceMessageHandler { From ab291ca18894e8d7260bcf1e8c05a275b824d7f4 Mon Sep 17 00:00:00 2001 From: "Liangying.Wei" Date: Thu, 22 Aug 2019 14:11:35 +0800 Subject: [PATCH 5/8] Update ServiceConnection.cs (#633) --- .../ServerConnections/ServiceConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.SignalR/ServerConnections/ServiceConnection.cs b/src/Microsoft.Azure.SignalR/ServerConnections/ServiceConnection.cs index 8abffe139..32e5f95ef 100644 --- a/src/Microsoft.Azure.SignalR/ServerConnections/ServiceConnection.cs +++ b/src/Microsoft.Azure.SignalR/ServerConnections/ServiceConnection.cs @@ -199,7 +199,7 @@ private async Task WaitOnApplicationTask(ServiceConnectionContext connection) if (connection.AbortOnClose) { // Inform the Service that we will remove the client because SignalR told us it is disconnected. - var serviceMessage = new CloseConnectionMessage(connection.ConnectionId, errorMessage: "Web application error."); + var serviceMessage = new CloseConnectionMessage(connection.ConnectionId, errorMessage: "Web application task completed, close the client."); await WriteAsync(serviceMessage); Log.CloseConnection(Logger, connection.ConnectionId); } From 389f3a8f0a269bce587a740c2b56c6f5b511ee78 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 23 Aug 2019 13:09:26 +0800 Subject: [PATCH 6/8] expose connection count in management sdk (#637) * expose connection count in management sdk * use connection count --- src/Microsoft.Azure.SignalR.Management/ServiceManager.cs | 3 +-- .../ServiceManagerOptions.cs | 5 +++++ .../ServiceManagerFacts.cs | 9 ++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Azure.SignalR.Management/ServiceManager.cs b/src/Microsoft.Azure.SignalR.Management/ServiceManager.cs index fe2dbda83..7d144f5bb 100644 --- a/src/Microsoft.Azure.SignalR.Management/ServiceManager.cs +++ b/src/Microsoft.Azure.SignalR.Management/ServiceManager.cs @@ -23,7 +23,6 @@ internal class ServiceManager : IServiceManager private readonly ServiceEndpointProvider _endpointProvider; private readonly IServerNameProvider _serverNameProvider; private readonly ServiceEndpoint _endpoint; - private const int ServerConnectionCount = 1; private readonly string _productInfo; internal ServiceManager(ServiceManagerOptions serviceManagerOptions, string productInfo) @@ -50,7 +49,7 @@ public async Task CreateHubContextAsync(string hubName, ILog var clientConnectionFactory = new ClientConnectionFactory(); ConnectionDelegate connectionDelegate = connectionContext => Task.CompletedTask; var serviceConnectionFactory = new ServiceConnectionFactory(serviceProtocol, clientConnectionManager, connectionFactory, loggerFactory, connectionDelegate, clientConnectionFactory); - var weakConnectionContainer = new WeakServiceConnectionContainer(serviceConnectionFactory, ServerConnectionCount, new HubServiceEndpoint(hubName, _endpointProvider, _endpoint)); + var weakConnectionContainer = new WeakServiceConnectionContainer(serviceConnectionFactory, _serviceManagerOptions.ConnectionCount, new HubServiceEndpoint(hubName, _endpointProvider, _endpoint)); var serviceCollection = new ServiceCollection(); serviceCollection.AddSignalRCore(); diff --git a/src/Microsoft.Azure.SignalR.Management/ServiceManagerOptions.cs b/src/Microsoft.Azure.SignalR.Management/ServiceManagerOptions.cs index 26a5aefde..aeff81388 100644 --- a/src/Microsoft.Azure.SignalR.Management/ServiceManagerOptions.cs +++ b/src/Microsoft.Azure.SignalR.Management/ServiceManagerOptions.cs @@ -26,6 +26,11 @@ public class ServiceManagerOptions /// public string ApplicationName { get; set; } + /// + /// Gets or sets the total number of connections from SDK to Azure SignalR Service. Default value is 1. + /// + public int ConnectionCount { get; set; } = 1; + internal void ValidateOptions() { ValidateConnectionString(); diff --git a/test/Microsoft.Azure.SignalR.Management.Tests/ServiceManagerFacts.cs b/test/Microsoft.Azure.SignalR.Management.Tests/ServiceManagerFacts.cs index 9ac34a313..15668452d 100644 --- a/test/Microsoft.Azure.SignalR.Management.Tests/ServiceManagerFacts.cs +++ b/test/Microsoft.Azure.SignalR.Management.Tests/ServiceManagerFacts.cs @@ -26,11 +26,13 @@ public class ServiceManagerFacts private static readonly string[] _appNames = new string[] { "appName", "", null }; private static readonly string[] _userIds = new string[] { UserId, null }; private static readonly IEnumerable _claimLists = new Claim[][] { _defaultClaims, null }; + private static readonly int[] _connectionCounts = new int[] {1, 2}; public static IEnumerable TestServiceManagerOptionData => from transport in _serviceTransportTypes from useLoggerFactory in _useLoggerFatories from appName in _appNames - select new object[] { transport, useLoggerFactory, appName }; + from connectionCount in _connectionCounts + select new object[] { transport, useLoggerFactory, appName, connectionCount }; public static IEnumerable TestGenerateClientEndpointData => from appName in _appNames select new object[] { appName, GetExpectedClientEndpoint(appName) }; @@ -65,13 +67,14 @@ internal void GenerateClientEndpointTest(string appName, string expectedClientEn [Theory] [MemberData(nameof(TestServiceManagerOptionData))] - internal async Task CreateServiceHubContextTest(ServiceTransportType serviceTransportType, bool useLoggerFacory, string appName) + internal async Task CreateServiceHubContextTest(ServiceTransportType serviceTransportType, bool useLoggerFacory, string appName, int connectionCount) { var serviceManager = new ServiceManager(new ServiceManagerOptions { ConnectionString = _testConnectionString, ServiceTransportType = serviceTransportType, - ApplicationName = appName + ApplicationName = appName, + ConnectionCount = connectionCount }, null); LoggerFactory loggerFactory; From 8fb4529f19655d7cb060367fb4035beb8358db18 Mon Sep 17 00:00:00 2001 From: Chenyang Liu Date: Fri, 23 Aug 2019 14:23:39 +0800 Subject: [PATCH 7/8] Improve ack-able message response logic and add tests (#638) * Improve ack-able message response logic and add tests * Update ack-able operation return value --- .../ServiceConnections/AckStatus.cs | 8 +- .../ServiceConnectionContainerBase.cs | 29 +- .../Utilities/AckHandler.cs | 29 +- ...EndpointServiceConnectionContainerTests.cs | 253 +++++++++++++++++- .../ServiceLifetimeManagerFacts.cs | 2 +- .../TestBaseServiceConnectionContainer.cs | 4 +- 6 files changed, 288 insertions(+), 37 deletions(-) diff --git a/src/Microsoft.Azure.SignalR.Common/ServiceConnections/AckStatus.cs b/src/Microsoft.Azure.SignalR.Common/ServiceConnections/AckStatus.cs index 4d6aed349..fa9eadc33 100644 --- a/src/Microsoft.Azure.SignalR.Common/ServiceConnections/AckStatus.cs +++ b/src/Microsoft.Azure.SignalR.Common/ServiceConnections/AckStatus.cs @@ -4,10 +4,10 @@ namespace Microsoft.Azure.SignalR { - internal static class AckStatus + internal enum AckStatus { - public const int Ok = 1; - public const int NotFound = 2; - public const int Timeout = 3; + Ok = 1, + NotFound = 2, + Timeout = 3 } } diff --git a/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServiceConnectionContainerBase.cs b/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServiceConnectionContainerBase.cs index ecb2359c8..e1b1362ba 100644 --- a/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServiceConnectionContainerBase.cs +++ b/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServiceConnectionContainerBase.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -71,11 +71,11 @@ private set protected ServiceConnectionContainerBase(IServiceConnectionFactory serviceConnectionFactory, int minConnectionCount, HubServiceEndpoint endpoint, - IReadOnlyList initialConnections = null, ILogger logger = null) + IReadOnlyList initialConnections = null, ILogger logger = null, AckHandler ackHandler = null) { ServiceConnectionFactory = serviceConnectionFactory; Endpoint = endpoint; - _ackHandler = new AckHandler(); + _ackHandler = ackHandler ?? new AckHandler(); // make sure it is after _endpoint is set // init initial connections @@ -134,14 +134,7 @@ protected async Task StartCoreAsync(IServiceConnection connection, string target public void HandleAck(AckMessage ackMessage) { - if (ackMessage.Status == AckStatus.Ok) - { - _ackHandler.TriggerAck(ackMessage.AckId, true); - } - else - { - _ackHandler.TriggerAck(ackMessage.AckId, false); - } + _ackHandler.TriggerAck(ackMessage.AckId, (AckStatus)ackMessage.Status); } /// @@ -258,7 +251,19 @@ public async Task WriteAckableMessageAsync(ServiceMessage serviceMessage, await WriteToRandomAvailableConnection(serviceMessage); - return await task; + var status = await task; + switch (status) + { + case AckStatus.Ok: + return true; + case AckStatus.NotFound: + return false; + case AckStatus.Timeout: + throw new TimeoutException($"Ack-able message {serviceMessage.GetType()} waiting for ack timed out."); + default: + // should not be hit. + return false; + } } // Ready for scalable containers diff --git a/src/Microsoft.Azure.SignalR.Common/Utilities/AckHandler.cs b/src/Microsoft.Azure.SignalR.Common/Utilities/AckHandler.cs index 1dd8e4450..d0a7b4902 100644 --- a/src/Microsoft.Azure.SignalR.Common/Utilities/AckHandler.cs +++ b/src/Microsoft.Azure.SignalR.Common/Utilities/AckHandler.cs @@ -11,11 +11,15 @@ internal sealed class AckHandler : IDisposable { private readonly ConcurrentDictionary _acks = new ConcurrentDictionary(); private readonly Timer _timer; - private readonly TimeSpan _ackInterval = TimeSpan.FromSeconds(5); + private readonly TimeSpan _ackInterval; + private readonly TimeSpan _ackTtl; private int _currentId = 0; - public AckHandler() + public AckHandler(int ackIntervalInMilliseconds = 3000, int ackTtlInMilliseconds = 10000) { + _ackInterval = TimeSpan.FromMilliseconds(ackIntervalInMilliseconds); + _ackTtl = TimeSpan.FromMilliseconds(ackTtlInMilliseconds); + bool restoreFlow = false; try { @@ -37,19 +41,19 @@ public AckHandler() } } - public Task CreateAck(out int id, CancellationToken cancellationToken = default) + public Task CreateAck(out int id, CancellationToken cancellationToken = default) { id = Interlocked.Increment(ref _currentId); - var tcs = _acks.GetOrAdd(id, _ => new AckInfo()).Tcs; + var tcs = _acks.GetOrAdd(id, _ => new AckInfo(_ackTtl)).Tcs; cancellationToken.Register(() => tcs.TrySetCanceled()); return tcs.Task; } - public void TriggerAck(int id, bool isSuccess) + public void TriggerAck(int id, AckStatus ackStatus) { if (_acks.TryRemove(id, out var ack)) { - ack.Tcs.TrySetResult(isSuccess); + ack.Tcs.TrySetResult(ackStatus); } } @@ -63,8 +67,7 @@ private void CheckAcks() { if (_acks.TryRemove(pair.Key, out var ack)) { - // If acks not coming back in time, do not throw an exception - ack.Tcs.TrySetResult(false); + ack.Tcs.TrySetResult(AckStatus.Timeout); } } } @@ -85,16 +88,14 @@ public void Dispose() private class AckInfo { - private readonly TimeSpan _ttl = TimeSpan.FromSeconds(10); - - public TaskCompletionSource Tcs { get; private set; } + public TaskCompletionSource Tcs { get; private set; } public DateTime Expired { get; private set; } - public AckInfo() + public AckInfo(TimeSpan ttl) { - Expired = DateTime.UtcNow.Add(_ttl); - Tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + Expired = DateTime.UtcNow.Add(ttl); + Tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } } } diff --git a/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs b/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs index e2ecd19cf..bcf2ff94f 100644 --- a/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs +++ b/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Connections; @@ -162,7 +163,7 @@ public async Task TestContainerWithOneEndpointWithAllConnectedSucceeeds() var task = container.WriteAckableMessageAsync(DefaultGroupMessage); await writeTcs.Task.OrTimeout(); - innerContainer.HandleAck(new AckMessage(1, AckStatus.Ok)); + innerContainer.HandleAck(new AckMessage(1, (int)AckStatus.Ok)); await task.OrTimeout(); } @@ -248,7 +249,7 @@ public async Task TestContainerWithTwoEndpointWithAllConnectedSucceedsWithGoodRo var task = container.WriteAckableMessageAsync(DefaultGroupMessage); await writeTcs.Task.OrTimeout(); - containers.First().Value.HandleAck(new AckMessage(1, AckStatus.Ok)); + containers.First().Value.HandleAck(new AckMessage(1, (int)AckStatus.Ok)); await task.OrTimeout(); } @@ -362,7 +363,7 @@ public async Task TestContainerWithTwoEndpointWithOneOfflineSucceeds() _ = container.StartAsync(); var task = container.WriteAckableMessageAsync(DefaultGroupMessage); await writeTcs.Task.OrTimeout(); - containers.First(p => !string.IsNullOrEmpty(p.Key.Name)).Value.HandleAck(new AckMessage(1, AckStatus.Ok)); + containers.First(p => !string.IsNullOrEmpty(p.Key.Name)).Value.HandleAck(new AckMessage(1, (int)AckStatus.Ok)); await task.OrTimeout(); } } @@ -406,7 +407,7 @@ public async Task TestContainerWithTwoEndpointWithPrimaryOfflineAndConnectionSta var task = container.WriteAckableMessageAsync(DefaultGroupMessage); await writeTcs.Task.OrTimeout(); - containers.First(p => !string.IsNullOrEmpty(p.Key.Name)).Value.HandleAck(new AckMessage(1, AckStatus.Ok)); + containers.First(p => !string.IsNullOrEmpty(p.Key.Name)).Value.HandleAck(new AckMessage(1, (int)AckStatus.Ok)); await task.OrTimeout(); var endpoints = container.GetOnlineEndpoints().ToArray(); @@ -456,6 +457,250 @@ public async Task TestMultiEndpointConnectionWithNotExistEndpointRouter() } } + [Fact] + public async Task TestTwoEndpointsWithAllNotFoundAck() + { + var sem = new TestServiceEndpointManager( + new ServiceEndpoint(ConnectionString1), + new ServiceEndpoint(ConnectionString2, name: "online")); + + var router = new TestEndpointRouter(); + var writeTcs = new TaskCompletionSource(); + TestBaseServiceConnectionContainer innerContainer = null; + var containers = new Dictionary(); + var container = new MultiEndpointServiceConnectionContainer("hub", e => + { + if (string.IsNullOrEmpty(e.Name)) + { + return containers[e] = new TestBaseServiceConnectionContainer(new List { + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + }, e); + } + return containers[e] = new TestBaseServiceConnectionContainer(new List { + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + }, e); + }, sem, router, null); + + // All the connections started + _ = container.StartAsync(); + await container.ConnectionInitializedTask; + + var task = container.WriteAckableMessageAsync(DefaultGroupMessage); + await writeTcs.Task.OrTimeout(); + foreach (var c in containers) + { + c.Value.HandleAck(new AckMessage(1, (int) AckStatus.NotFound)); + } + var result = await task.OrTimeout(); + Assert.False(result); + } + + [Fact] + public async Task TestTwoEndpointsWithAllTimeoutAck() + { + var sem = new TestServiceEndpointManager( + new ServiceEndpoint(ConnectionString1), + new ServiceEndpoint(ConnectionString2, name: "online")); + + var router = new TestEndpointRouter(); + var writeTcs = new TaskCompletionSource(); + TestBaseServiceConnectionContainer innerContainer = null; + var containers = new Dictionary(); + var container = new MultiEndpointServiceConnectionContainer("hub", e => + { + if (string.IsNullOrEmpty(e.Name)) + { + return containers[e] = new TestBaseServiceConnectionContainer(new List { + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + }, e); + } + return containers[e] = new TestBaseServiceConnectionContainer(new List { + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + }, e); + }, sem, router, null); + + // All the connections started + _ = container.StartAsync(); + await container.ConnectionInitializedTask; + + var task = container.WriteAckableMessageAsync(DefaultGroupMessage); + await writeTcs.Task.OrTimeout(); + foreach (var c in containers) + { + c.Value.HandleAck(new AckMessage(1, (int)AckStatus.Timeout)); + } + await Assert.ThrowsAnyAsync(async() => await task.OrTimeout()); + } + + [Fact] + public async Task TestTwoEndpointsWithoutAck() + { + var sem = new TestServiceEndpointManager( + new ServiceEndpoint(ConnectionString1), + new ServiceEndpoint(ConnectionString2, name: "online")); + + var router = new TestEndpointRouter(); + var writeTcs = new TaskCompletionSource(); + TestBaseServiceConnectionContainer innerContainer = null; + var containers = new Dictionary(); + var container = new MultiEndpointServiceConnectionContainer("hub", e => + { + if (string.IsNullOrEmpty(e.Name)) + { + return containers[e] = new TestBaseServiceConnectionContainer(new List { + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + }, e, new AckHandler(100, 200)); + } + return containers[e] = new TestBaseServiceConnectionContainer(new List { + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + }, e, new AckHandler(100, 200)); + }, sem, router, null); + + // All the connections started + _ = container.StartAsync(); + await container.ConnectionInitializedTask; + + var task = container.WriteAckableMessageAsync(DefaultGroupMessage); + await writeTcs.Task.OrTimeout(); + foreach (var c in containers) + { + c.Value.HandleAck(new AckMessage(1, (int)AckStatus.Timeout)); + } + await Assert.ThrowsAnyAsync(async () => await task).OrTimeout(); + } + + [Fact] + public async Task TestTwoEndpointsWithOneSucceededAndOtherNotAcked() + { + var sem = new TestServiceEndpointManager( + new ServiceEndpoint(ConnectionString1), + new ServiceEndpoint(ConnectionString2, name: "online")); + + var router = new TestEndpointRouter(); + var writeTcs = new TaskCompletionSource(); + TestBaseServiceConnectionContainer innerContainer = null; + var containers = new Dictionary(); + var container = new MultiEndpointServiceConnectionContainer("hub", e => + { + if (string.IsNullOrEmpty(e.Name)) + { + return containers[e] = new TestBaseServiceConnectionContainer(new List { + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + }, e, new AckHandler(100, 200)); + } + return containers[e] = new TestBaseServiceConnectionContainer(new List { + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + }, e, new AckHandler(100, 200)); + }, sem, router, null); + + // All the connections started + _ = container.StartAsync(); + await container.ConnectionInitializedTask; + + var task = container.WriteAckableMessageAsync(DefaultGroupMessage); + await writeTcs.Task.OrTimeout(); + containers.First().Value.HandleAck(new AckMessage(1, (int)AckStatus.Ok)); + var result = await task.OrTimeout(); + Assert.True(result); + } + + [Fact] + public async Task TestTwoEndpointsWithCancellationToken() + { + var sem = new TestServiceEndpointManager( + new ServiceEndpoint(ConnectionString1), + new ServiceEndpoint(ConnectionString2, name: "online")); + + var router = new TestEndpointRouter(); + var writeTcs = new TaskCompletionSource(); + TestBaseServiceConnectionContainer innerContainer = null; + var containers = new Dictionary(); + var container = new MultiEndpointServiceConnectionContainer("hub", e => + { + if (string.IsNullOrEmpty(e.Name)) + { + return containers[e] = new TestBaseServiceConnectionContainer(new List { + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + }, e, new AckHandler(100, 200)); + } + return containers[e] = new TestBaseServiceConnectionContainer(new List { + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestServiceConnection(writeAsyncTcs: writeTcs), + }, e, new AckHandler(100, 200)); + }, sem, router, null); + + // All the connections started + _ = container.StartAsync(); + await container.ConnectionInitializedTask; + + var task = container.WriteAckableMessageAsync(DefaultGroupMessage, new CancellationToken(true)); + await writeTcs.Task.OrTimeout(); + foreach (var c in containers) + { + c.Value.HandleAck(new AckMessage(1, (int)AckStatus.Timeout)); + } + await Assert.ThrowsAnyAsync(async () => await task).OrTimeout(); + } + private class NotExistEndpointRouter : EndpointRouterDecorator { public override IEnumerable GetEndpointsForConnection(string connectionId, IEnumerable endpoints) diff --git a/test/Microsoft.Azure.SignalR.Tests/ServiceLifetimeManagerFacts.cs b/test/Microsoft.Azure.SignalR.Tests/ServiceLifetimeManagerFacts.cs index 6b9a8279c..d88e6d111 100644 --- a/test/Microsoft.Azure.SignalR.Tests/ServiceLifetimeManagerFacts.cs +++ b/test/Microsoft.Azure.SignalR.Tests/ServiceLifetimeManagerFacts.cs @@ -113,7 +113,7 @@ public async void ServiceLifetimeManagerIntegrationTest(string methodName, Type if (typeof(IAckableMessage).IsAssignableFrom(messageType)) { - await proxy.WriteMessageAsync(new AckMessage(1, AckStatus.Ok)); + await proxy.WriteMessageAsync(new AckMessage(1, (int)AckStatus.Ok)); } // Need to return in time, or it indicate a timeout when sending ack-able messages. diff --git a/test/Microsoft.Azure.SignalR.Tests/TestBaseServiceConnectionContainer.cs b/test/Microsoft.Azure.SignalR.Tests/TestBaseServiceConnectionContainer.cs index e9c17570c..f1bcfaa4b 100644 --- a/test/Microsoft.Azure.SignalR.Tests/TestBaseServiceConnectionContainer.cs +++ b/test/Microsoft.Azure.SignalR.Tests/TestBaseServiceConnectionContainer.cs @@ -9,8 +9,8 @@ namespace Microsoft.Azure.SignalR.Tests { internal sealed class TestBaseServiceConnectionContainer : ServiceConnectionContainerBase { - public TestBaseServiceConnectionContainer(List serviceConnections, HubServiceEndpoint endpoint = null) - : base(null, 0, endpoint, serviceConnections) + public TestBaseServiceConnectionContainer(List serviceConnections, HubServiceEndpoint endpoint = null, AckHandler ackHandler = null) + : base(null, 0, endpoint, serviceConnections, ackHandler: ackHandler) { } From 91c4b9d4f79837f49243ba3bbbadfcfc286a02a6 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 23 Aug 2019 17:08:17 +0800 Subject: [PATCH 8/8] fix for tcs's RunContinuationsAsynchronously (#630) * fix for tcs's RunContinuationsAsynchronously * add unit test * remove used package * restore synchronizationContext * remove ut * fix other UT * rename * add ut --- .../ServiceConnectionBase.cs | 2 +- ...EndpointServiceConnectionContainerTests.cs | 3 +- .../Properties/AssemblyInfo.cs | 3 +- .../TestClasses/TestServiceConnection.cs | 10 +- ...EndpointServiceConnectionContainerTests.cs | 322 +++++++++--------- .../ServiceConnectionFacts.cs | 26 ++ .../ServiceConnectionTests.cs | 1 - ...tion.cs => TestSimpleServiceConnection.cs} | 4 +- 8 files changed, 202 insertions(+), 169 deletions(-) rename test/Microsoft.Azure.SignalR.Tests/{TestServiceConnection.cs => TestSimpleServiceConnection.cs} (85%) diff --git a/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServiceConnectionBase.cs b/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServiceConnectionBase.cs index bacb9140f..c5cbc8b24 100644 --- a/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServiceConnectionBase.cs +++ b/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServiceConnectionBase.cs @@ -32,7 +32,7 @@ internal abstract class ServiceConnectionBase : IServiceConnection private readonly SemaphoreSlim _serviceConnectionLock = new SemaphoreSlim(1, 1); - private readonly TaskCompletionSource _serviceConnectionStartTcs = new TaskCompletionSource(TaskContinuationOptions.RunContinuationsAsynchronously); + private readonly TaskCompletionSource _serviceConnectionStartTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); private readonly ServerConnectionType _connectionType; diff --git a/test/Microsoft.Azure.SignalR.AspNet.Tests/MultiEndpointServiceConnectionContainerTests.cs b/test/Microsoft.Azure.SignalR.AspNet.Tests/MultiEndpointServiceConnectionContainerTests.cs index a80a1d4e9..65e6eaab6 100644 --- a/test/Microsoft.Azure.SignalR.AspNet.Tests/MultiEndpointServiceConnectionContainerTests.cs +++ b/test/Microsoft.Azure.SignalR.AspNet.Tests/MultiEndpointServiceConnectionContainerTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -423,6 +422,8 @@ public async Task TestContainerWithTwoEndpointWithPrimaryOfflineAndConnectionSta _ = container.StartAsync(); + await container.ConnectionInitializedTask; + await container.WriteAsync(DefaultGroupMessage); var endpoints = container.GetOnlineEndpoints().ToArray(); diff --git a/test/Microsoft.Azure.SignalR.Tests.Common/Properties/AssemblyInfo.cs b/test/Microsoft.Azure.SignalR.Tests.Common/Properties/AssemblyInfo.cs index e18a2bd0c..2eae86749 100644 --- a/test/Microsoft.Azure.SignalR.Tests.Common/Properties/AssemblyInfo.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/Properties/AssemblyInfo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.Azure.SignalR.AspNet.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] \ No newline at end of file +[assembly: InternalsVisibleTo("Microsoft.Azure.SignalR.AspNet.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.Azure.SignalR.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] \ No newline at end of file diff --git a/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnection.cs b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnection.cs index 3e01f6814..03232cbfc 100644 --- a/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnection.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/TestClasses/TestServiceConnection.cs @@ -36,9 +36,10 @@ protected override Task CreateConnection(string target = null }); } - protected override Task HandshakeAsync() + protected override async Task HandshakeAsync() { - return Task.FromResult(_status == ServiceConnectionStatus.Connected); + await Task.Yield(); + return _status == ServiceConnectionStatus.Connected; } protected override Task DisposeConnection() @@ -70,5 +71,10 @@ public override Task WriteAsync(ServiceMessage serviceMessage) return Task.CompletedTask; } + + public void Stop() + { + ConnectionContext?.Transport.Input.CancelPendingRead(); + } } } diff --git a/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs b/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs index bcf2ff94f..535eb4076 100644 --- a/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs +++ b/test/Microsoft.Azure.SignalR.Tests/MultiEndpointServiceConnectionContainerTests.cs @@ -51,8 +51,8 @@ public void TestGetRoutedEndpointsReturnDistinctResultForMultiMessages() var router = new TestEndpointRouter(); var container = new MultiEndpointServiceConnectionContainer("hub", e => new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(), - new TestServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), }, e), sem, router, null); var result = container.GetRoutedEndpoints(new MultiGroupBroadcastDataMessage(new[] { "group1", "group2" }, null)).ToList(); @@ -81,8 +81,8 @@ public void TestEndpointManagerWithDuplicateEndpoints() var router = new TestEndpointRouter(); var container = new MultiEndpointServiceConnectionContainer("hub", e => new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(), - new TestServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), }, e), sem, router, null); Assert.Equal(2, container.Connections.Count); @@ -105,8 +105,8 @@ public async Task TestEndpointManagerWithDuplicateEndpointsAndConnectionStarted( var router = new TestEndpointRouter(); var container = new MultiEndpointServiceConnectionContainer("hub", e => new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(), - new TestServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), }, e), sem, router, null); // All the connections started @@ -148,13 +148,13 @@ public async Task TestContainerWithOneEndpointWithAllConnectedSucceeeds() TestBaseServiceConnectionContainer innerContainer = null; var container = new MultiEndpointServiceConnectionContainer("hub", e => innerContainer = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e), sem, router, null); // All the connections started @@ -176,13 +176,13 @@ public async Task TestContainerWithBadRouterThrows() var router = new TestEndpointRouter(throws); var container = new MultiEndpointServiceConnectionContainer("hub", e => new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), }, e), sem, router, null); // All the connections started @@ -204,13 +204,13 @@ public async Task TestContainerWithTwoConnectedEndpointAndBadRouterThrows() var router = new TestEndpointRouter(new ServiceConnectionNotActiveException()); var container = new MultiEndpointServiceConnectionContainer("hub", e => new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(), - new TestServiceConnection(), - new TestServiceConnection(), - new TestServiceConnection(), - new TestServiceConnection(), - new TestServiceConnection(), - new TestServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), }, e), sem, router, null); // All the connections started @@ -234,13 +234,13 @@ public async Task TestContainerWithTwoEndpointWithAllConnectedSucceedsWithGoodRo var containers = new Dictionary(); var container = new MultiEndpointServiceConnectionContainer("hub", e => containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e), sem, router, null); // All the connections started @@ -271,13 +271,13 @@ public async Task TestContainerWithTwoEndpointWithAllOfflineSucceedsWithWarning( var router = new TestEndpointRouter(); var container = new MultiEndpointServiceConnectionContainer("hub", e => new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), }, e), sem, router, loggerFactory); // All the connections started @@ -306,13 +306,13 @@ public async Task TestContainerWithTwoOfflineEndpointWriteAckableMessageSucceeds var container = new MultiEndpointServiceConnectionContainer("hub", e => { return new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), }, e); }, sem, router, loggerFactory); @@ -340,23 +340,23 @@ public async Task TestContainerWithTwoEndpointWithOneOfflineSucceeds() if (string.IsNullOrEmpty(e.Name)) { return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), }, e); } return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e); }, sem, router, loggerFactory); @@ -383,23 +383,23 @@ public async Task TestContainerWithTwoEndpointWithPrimaryOfflineAndConnectionSta if (string.IsNullOrEmpty(e.Name)) { return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), - new TestServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected, writeAsyncTcs: writeTcs), }, e); } return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e); }, sem, router, null); @@ -431,23 +431,23 @@ public async Task TestMultiEndpointConnectionWithNotExistEndpointRouter() if (string.IsNullOrEmpty(e.Name)) { return new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), - new TestServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), + new TestSimpleServiceConnection(ServiceConnectionStatus.Disconnected), }, e); } return new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(), - new TestServiceConnection(), - new TestServiceConnection(), - new TestServiceConnection(), - new TestServiceConnection(), - new TestServiceConnection(), - new TestServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), + new TestSimpleServiceConnection(), }, e); }, sem, router, loggerFactory); @@ -473,23 +473,23 @@ public async Task TestTwoEndpointsWithAllNotFoundAck() if (string.IsNullOrEmpty(e.Name)) { return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e); } return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e); }, sem, router, null); @@ -523,23 +523,23 @@ public async Task TestTwoEndpointsWithAllTimeoutAck() if (string.IsNullOrEmpty(e.Name)) { return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e); } return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e); }, sem, router, null); @@ -572,23 +572,23 @@ public async Task TestTwoEndpointsWithoutAck() if (string.IsNullOrEmpty(e.Name)) { return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e, new AckHandler(100, 200)); } return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e, new AckHandler(100, 200)); }, sem, router, null); @@ -621,23 +621,23 @@ public async Task TestTwoEndpointsWithOneSucceededAndOtherNotAcked() if (string.IsNullOrEmpty(e.Name)) { return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e, new AckHandler(100, 200)); } return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e, new AckHandler(100, 200)); }, sem, router, null); @@ -668,23 +668,23 @@ public async Task TestTwoEndpointsWithCancellationToken() if (string.IsNullOrEmpty(e.Name)) { return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e, new AckHandler(100, 200)); } return containers[e] = new TestBaseServiceConnectionContainer(new List { - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), - new TestServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), + new TestSimpleServiceConnection(writeAsyncTcs: writeTcs), }, e, new AckHandler(100, 200)); }, sem, router, null); @@ -716,7 +716,7 @@ public override IEnumerable GetEndpointsForGroup(string groupNa private IServiceConnection CreateServiceConnection(ServerConnectionType type, IConnectionFactory factory) { - return new TestServiceConnection(); + return new TestSimpleServiceConnection(); } private class TestServiceEndpointManager : ServiceEndpointManagerBase diff --git a/test/Microsoft.Azure.SignalR.Tests/ServiceConnectionFacts.cs b/test/Microsoft.Azure.SignalR.Tests/ServiceConnectionFacts.cs index db10418d8..bc432a1c6 100644 --- a/test/Microsoft.Azure.SignalR.Tests/ServiceConnectionFacts.cs +++ b/test/Microsoft.Azure.SignalR.Tests/ServiceConnectionFacts.cs @@ -8,11 +8,13 @@ using System.Linq; using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Connections; using Microsoft.Azure.SignalR.Common; using Microsoft.Azure.SignalR.Protocol; +using Microsoft.Azure.SignalR.Tests.Common; using Microsoft.Extensions.Primitives; using Xunit; @@ -497,6 +499,30 @@ await proxy.WriteMessageAsync(new PingMessage() Assert.Equal(connectionId2, proxy.ClientConnections.FirstOrDefault().Key); } + /// + /// Test if there's a deadlock in server connection initialization. _serviceConnectionStartTcs in ServiceConnectionBase should be inited with option TaskCreationOptions.RunContinuationsAsynchronously + /// + /// + [Fact] + public async Task ServiceConnectionInitializationDeadlockTest() + { + var context = SynchronizationContext.Current; + try + { + SynchronizationContext.SetSynchronizationContext(null); + var conn = new TestServiceConnection(); + var initTask = conn.StartAsync(); + await conn.ConnectionInitializedTask; + conn.Stop(); + var completedTask = Task.WhenAny(initTask, Task.Delay(TimeSpan.FromSeconds(1))).Result; + Assert.Equal(initTask, completedTask); + } + finally + { + SynchronizationContext.SetSynchronizationContext(context); + } + } + private static void AssertTimeout(params Task[] task) { Assert.False(Task.WaitAll(task, DefaultTimeoutInMilliSeconds)); diff --git a/test/Microsoft.Azure.SignalR.Tests/ServiceConnectionTests.cs b/test/Microsoft.Azure.SignalR.Tests/ServiceConnectionTests.cs index 9e8313e7c..f33797a8e 100644 --- a/test/Microsoft.Azure.SignalR.Tests/ServiceConnectionTests.cs +++ b/test/Microsoft.Azure.SignalR.Tests/ServiceConnectionTests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; diff --git a/test/Microsoft.Azure.SignalR.Tests/TestServiceConnection.cs b/test/Microsoft.Azure.SignalR.Tests/TestSimpleServiceConnection.cs similarity index 85% rename from test/Microsoft.Azure.SignalR.Tests/TestServiceConnection.cs rename to test/Microsoft.Azure.SignalR.Tests/TestSimpleServiceConnection.cs index 508fd2c6d..f501ff0a0 100644 --- a/test/Microsoft.Azure.SignalR.Tests/TestServiceConnection.cs +++ b/test/Microsoft.Azure.SignalR.Tests/TestSimpleServiceConnection.cs @@ -8,7 +8,7 @@ namespace Microsoft.Azure.SignalR.Tests { - internal sealed class TestServiceConnection : IServiceConnection + internal sealed class TestSimpleServiceConnection : IServiceConnection { public ServiceConnectionStatus Status { get; } @@ -19,7 +19,7 @@ internal sealed class TestServiceConnection : IServiceConnection public event Action ConnectionStatusChanged; - public TestServiceConnection(ServiceConnectionStatus status = ServiceConnectionStatus.Connected, bool throws = false, TaskCompletionSource writeAsyncTcs = null) + public TestSimpleServiceConnection(ServiceConnectionStatus status = ServiceConnectionStatus.Connected, bool throws = false, TaskCompletionSource writeAsyncTcs = null) { Status = status; _throws = throws;