From d8abe00cbd23007182d2830c71aef26b62975167 Mon Sep 17 00:00:00 2001 From: yallie Date: Sat, 11 Jan 2025 00:18:47 +0300 Subject: [PATCH] Added validation synchronization context for the unit tests to detect missing ConfigureAwait(false) calls. --- CoreRemoting.Tests/AsyncTests.cs | 7 +- CoreRemoting.Tests/RpcTests.cs | 81 +++++++++++++++++-- CoreRemoting.Tests/SessionTests.cs | 15 +++- CoreRemoting.Tests/Tools/TestService.cs | 2 +- .../Tools/ValidationSyncContext.cs | 76 +++++++++++++++++ CoreRemoting/RemotingClient.cs | 22 +++-- CoreRemoting/RemotingSession.cs | 6 +- CoreRemoting/ServiceProxy.cs | 32 ++++---- CoreRemoting/Toolbox/TaskExtensions.cs | 4 +- 9 files changed, 205 insertions(+), 40 deletions(-) create mode 100644 CoreRemoting.Tests/Tools/ValidationSyncContext.cs diff --git a/CoreRemoting.Tests/AsyncTests.cs b/CoreRemoting.Tests/AsyncTests.cs index fc41299..e1221ce 100644 --- a/CoreRemoting.Tests/AsyncTests.cs +++ b/CoreRemoting.Tests/AsyncTests.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using CoreRemoting.Tests.Tools; using Xunit; @@ -13,8 +14,9 @@ public AsyncTests(ServerFixture serverFixture) _serverFixture = serverFixture; _serverFixture.Start(); } - + [Fact] + [SuppressMessage("Usage", "xUnit1030:Do not call ConfigureAwait in test method", Justification = "")] public async void AsyncMethods_should_work() { using var client = new RemotingClient(new ClientConfig() @@ -34,9 +36,10 @@ public async void AsyncMethods_should_work() } /// - /// Awaiting for ordinary non-generic task method should not hangs. + /// Awaiting for ordinary non-generic task method should not hangs. /// [Fact(Timeout = 15000)] + [SuppressMessage("Usage", "xUnit1030:Do not call ConfigureAwait in test method", Justification = "")] public async void AwaitingNonGenericTask_should_not_hang_forever() { using var client = new RemotingClient(new ClientConfig() diff --git a/CoreRemoting.Tests/RpcTests.cs b/CoreRemoting.Tests/RpcTests.cs index 96ecd1f..4dd7b24 100644 --- a/CoreRemoting.Tests/RpcTests.cs +++ b/CoreRemoting.Tests/RpcTests.cs @@ -1,6 +1,7 @@ using System; using System.Data; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Security; using System.Threading; using System.Threading.Tasks; @@ -39,11 +40,21 @@ public RpcTests(ServerFixture serverFixture, ITestOutputHelper testOutputHelper) _serverFixture.Start(ServerChannel); } + [Fact] + public void ValidationSyncContext_is_installed() + { + using var ctx = ValidationSyncContext.Install(); + + Assert.IsType(SynchronizationContext.Current); + } + [Fact] public void Call_on_Proxy_should_be_invoked_on_remote_service() { void ClientAction() { + using var ctx = ValidationSyncContext.Install(); + try { var stopWatch = new Stopwatch(); @@ -117,6 +128,8 @@ public void Call_on_Proxy_should_be_invoked_on_remote_service_with_MessageEncryp void ClientAction() { + using var ctx = ValidationSyncContext.Install(); + try { var stopWatch = new Stopwatch(); @@ -188,6 +201,8 @@ public void Delegate_invoked_on_server_should_callback_client() void ClientAction() { + using var ctx = ValidationSyncContext.Install(); + try { using var client = new RemotingClient( @@ -222,8 +237,10 @@ void ClientAction() [Fact] public void Events_should_work_remotely() { - bool serviceEventCalled = false; - bool customDelegateEventCalled = false; + using var ctx = ValidationSyncContext.Install(); + + var serviceEventCalled = false; + var customDelegateEventCalled = false; using var client = new RemotingClient( new ClientConfig() @@ -278,6 +295,8 @@ public void External_types_should_work_as_remote_service_parameters() void ClientAction() { + using var ctx = ValidationSyncContext.Install(); + try { using var client = new RemotingClient(new ClientConfig() @@ -313,6 +332,8 @@ void ClientAction() [Fact] public void Generic_methods_should_be_called_correctly() { + using var ctx = ValidationSyncContext.Install(); + using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 0, @@ -333,6 +354,8 @@ public void Generic_methods_should_be_called_correctly() [Fact] public void Inherited_methods_should_be_called_correctly() { + using var ctx = ValidationSyncContext.Install(); + using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 0, @@ -353,6 +376,8 @@ public void Inherited_methods_should_be_called_correctly() [Fact] public void Enum_arguments_should_be_passed_correctly() { + using var ctx = ValidationSyncContext.Install(); + using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 0, @@ -375,6 +400,8 @@ public void Enum_arguments_should_be_passed_correctly() [Fact] public void Missing_method_throws_RemoteInvocationException() { + using var ctx = ValidationSyncContext.Install(); + using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 0, @@ -412,6 +439,8 @@ public void Missing_method_throws_RemoteInvocationException() [Fact] public void Missing_service_throws_RemoteInvocationException() { + using var ctx = ValidationSyncContext.Install(); + using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 0, @@ -436,6 +465,8 @@ public void Missing_service_throws_RemoteInvocationException() [Fact] public void Error_method_throws_Exception() { + using var ctx = ValidationSyncContext.Install(); + try { using var client = new RemotingClient(new ClientConfig() @@ -466,8 +497,11 @@ public void Error_method_throws_Exception() } [Fact] + [SuppressMessage("Usage", "xUnit1030:Do not call ConfigureAwait in test method", Justification = "")] public async Task ErrorAsync_method_throws_Exception() { + // using var ctx = ValidationSyncContext.Install(); // fails? + try { using var client = new RemotingClient(new ClientConfig() @@ -484,8 +518,10 @@ public async Task ErrorAsync_method_throws_Exception() var proxy = client.CreateProxy(); var ex = (await Assert.ThrowsAsync(async () => - await proxy.ErrorAsync(nameof(ErrorAsync_method_throws_Exception)))) - .GetInnermostException(); + { + await proxy.ErrorAsync(nameof(ErrorAsync_method_throws_Exception)).ConfigureAwait(false); + }) + .ConfigureAwait(false)).GetInnermostException(); Assert.NotNull(ex); Assert.Equal(nameof(ErrorAsync_method_throws_Exception), ex.Message); @@ -500,6 +536,8 @@ await proxy.ErrorAsync(nameof(ErrorAsync_method_throws_Exception)))) [Fact] public void NonSerializableError_method_throws_Exception() { + using var ctx = ValidationSyncContext.Install(); + try { using var client = new RemotingClient(new ClientConfig() @@ -550,7 +588,9 @@ void AfterCall(object sender, ServerRpcContext ctx) ctx.Exception); } + using var ctx = ValidationSyncContext.Install(); _serverFixture.Server.AfterCall += AfterCall; + try { using var client = new RemotingClient(new ClientConfig() @@ -588,6 +628,8 @@ void AfterCall(object sender, ServerRpcContext ctx) [Fact] public void Failing_component_constructor_throws_RemoteInvocationException() { + using var ctx = ValidationSyncContext.Install(); + using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 3, @@ -608,8 +650,11 @@ public void Failing_component_constructor_throws_RemoteInvocationException() } [Fact] + [SuppressMessage("Usage", "xUnit1030:Do not call ConfigureAwait in test method", Justification = "")] public async Task Disposed_client_subscription_doesnt_break_other_clients() { + using var ctx = ValidationSyncContext.Install(); + async Task Roundtrip(bool encryption) { var oldEncryption = _serverFixture.Server.Config.MessageEncryption; @@ -642,7 +687,7 @@ async Task Roundtrip(bool encryption) client1.Disconnect(); proxy2.FireServiceEvent(); - Assert.True(await fired2.Task); + Assert.True(await fired2.Task.ConfigureAwait(false)); Assert.True(fired2.Task.IsCompleted); Assert.False(fired1.Task.IsCompleted); } @@ -656,15 +701,17 @@ async Task Roundtrip(bool encryption) } // works! - await Roundtrip(encryption: false); + await Roundtrip(encryption: false).ConfigureAwait(false); // fails! - await Roundtrip(encryption: true); + await Roundtrip(encryption: true).ConfigureAwait(false); } [Fact] public void DataTable_roundtrip_works_issue60() { + using var ctx = ValidationSyncContext.Install(); + using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 0, @@ -694,6 +741,8 @@ public void Large_messages_are_sent_and_received() // max payload size, in bytes var maxSize = 2 * 1024 * 1024 + 1; + using var ctx = ValidationSyncContext.Install(); + using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 0, @@ -748,6 +797,7 @@ void BeforeCall(object sender, ServerRpcContext e) => void AfterCall(object sender, ServerRpcContext e) => Interlocked.Increment(ref afterCallFired); + using var ctx = ValidationSyncContext.Install(); _serverFixture.Server.BeforeCall += BeforeCall; _serverFixture.Server.AfterCall += AfterCall; @@ -793,6 +843,7 @@ void BeforeCall(object sender, ServerRpcContext e) => void AfterCall(object sender, ServerRpcContext e) => Interlocked.Increment(ref afterCallFired); + using var ctx = ValidationSyncContext.Install(); _serverFixture.Server.BeforeCall += BeforeCall; _serverFixture.Server.AfterCall += AfterCall; @@ -848,7 +899,9 @@ void InterceptMethodCalls(object sender, ServerRpcContext e) } } + using var ctx = ValidationSyncContext.Install(); _serverFixture.Server.BeginCall += InterceptMethodCalls; + try { using var client = new RemotingClient(new ClientConfig() @@ -893,6 +946,8 @@ void RejectCall(object sender, ServerRpcContext e) => server.RejectCall += RejectCall; server.Config.AuthenticationRequired = true; + using var ctx = ValidationSyncContext.Install(); + try { using var client = new RemotingClient(new ClientConfig() @@ -934,6 +989,8 @@ public void Authentication_handler_has_access_to_the_current_session() AuthenticateFake = c => RemotingSession.Current != null }; + using var ctx = ValidationSyncContext.Install(); + try { using var client = new RemotingClient(new ClientConfig() @@ -970,6 +1027,8 @@ public void Broken_auhentication_handler_doesnt_break_the_server() AuthenticateFake = c => throw new Exception("Broken") }; + using var ctx = ValidationSyncContext.Install(); + try { using var client = new RemotingClient(new ClientConfig() @@ -1015,6 +1074,8 @@ public void Authentication_handler_can_check_client_address() } }; + using var ctx = ValidationSyncContext.Install(); + try { using var client = new RemotingClient(new ClientConfig() @@ -1043,6 +1104,8 @@ public void Authentication_handler_can_check_client_address() [Fact] public void ServerComponent_can_track_client_network_address() { + using var ctx = ValidationSyncContext.Install(); + using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 0, @@ -1064,6 +1127,8 @@ public void ServerComponent_can_track_client_network_address() [Fact] public void Logon_and_logoff_events_are_triggered() { + using var ctx = ValidationSyncContext.Install(); + void CheckSession(string operation) { var rs = RemotingSession.Current; @@ -1135,8 +1200,10 @@ void BypassAuthorizationForEcho(object sender, ServerRpcContext e) => e.AuthenticationRequired = e.MethodCallMessage.MethodName != "Echo"; + using var ctx = ValidationSyncContext.Install(); _serverFixture.Server.Config.AuthenticationRequired = true; _serverFixture.Server.BeginCall += BypassAuthorizationForEcho; + try { using var client = new RemotingClient(new ClientConfig() diff --git a/CoreRemoting.Tests/SessionTests.cs b/CoreRemoting.Tests/SessionTests.cs index 4b1c496..a5d2b16 100644 --- a/CoreRemoting.Tests/SessionTests.cs +++ b/CoreRemoting.Tests/SessionTests.cs @@ -25,8 +25,11 @@ public SessionTests(ServerFixture serverFixture) } [Fact] + [SuppressMessage("Usage", "xUnit1030:Do not call ConfigureAwait in test method", Justification = "")] public async Task Client_Connect_should_create_new_session_AND_Disconnect_should_close_session() { + using var ctx = ValidationSyncContext.Install(); + var clientStarted1 = new TaskCompletionSource(); var clientStarted2 = new TaskCompletionSource(); var clientStopSignal = new TaskCompletionSource(); @@ -46,7 +49,7 @@ async Task ClientTask(TaskCompletionSource connected) connected.TrySetResult(); Assert.True(client.HasSession); - await clientStopSignal.Task; + await clientStopSignal.Task.ConfigureAwait(false); client.Dispose(); } @@ -58,13 +61,13 @@ async Task ClientTask(TaskCompletionSource connected) var client2 = ClientTask(clientStarted2); // Wait for connection of both clients - await Task.WhenAll(clientStarted1.Task, clientStarted2.Task).Timeout(1); + await Task.WhenAll(clientStarted1.Task, clientStarted2.Task).Timeout(1).ConfigureAwait(false); Assert.Equal(2, _serverFixture.Server.SessionRepository.Sessions.Count()); clientStopSignal.TrySetResult(); - await Task.WhenAll(client1, client2, Task.Delay(100)).Timeout(1); + await Task.WhenAll(client1, client2, Task.Delay(100)).Timeout(1).ConfigureAwait(false); // There should be no sessions left, after both clients disconnected Assert.Empty(_serverFixture.Server.SessionRepository.Sessions); @@ -73,6 +76,8 @@ async Task ClientTask(TaskCompletionSource connected) [Fact] public void Client_Connect_should_throw_exception_on_invalid_auth_credentials() { + using var ctx = ValidationSyncContext.Install(); + var serverConfig = new ServerConfig() { @@ -138,6 +143,8 @@ public void RemotingSession_Dispose_should_disconnect_client() return null; }; + using var ctx = ValidationSyncContext.Install(); + var client = new RemotingClient(new ClientConfig() { @@ -168,6 +175,8 @@ public void RemotingSession_Dispose_should_disconnect_client() [Fact] public void RemotingSession_should_be_accessible_to_the_component_constructor() { + using var ctx = ValidationSyncContext.Install(); + using var client = new RemotingClient(new ClientConfig() { ConnectionTimeout = 0, diff --git a/CoreRemoting.Tests/Tools/TestService.cs b/CoreRemoting.Tests/Tools/TestService.cs index b5e9a5d..69918e1 100644 --- a/CoreRemoting.Tests/Tools/TestService.cs +++ b/CoreRemoting.Tests/Tools/TestService.cs @@ -78,7 +78,7 @@ public void Error(string text) public async Task ErrorAsync(string text) { - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); Error(text); } diff --git a/CoreRemoting.Tests/Tools/ValidationSyncContext.cs b/CoreRemoting.Tests/Tools/ValidationSyncContext.cs new file mode 100644 index 0000000..a69b74e --- /dev/null +++ b/CoreRemoting.Tests/Tools/ValidationSyncContext.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using CoreRemoting.Toolbox; +using Xunit; + +namespace CoreRemoting.Tests.Tools +{ + /// + /// Synchronization context for validating the ConfigureAwait usage across the library. + /// The idea is that if ConfigureAwait(false) is missing somewhere, then the continuation + /// is posted to the current synchronization context and can be detected automatically. + /// + /// Post or Send methods are called on the worker threads and the exceptions will be lost. + /// But Dispose is called on the main thread, so it can throw, and the exception will be + /// detected and reported by the unit test runner. + /// + /// References: + /// 1. https://btburnett.com/2016/04/testing-an-sdk-for-asyncawait-synchronizationcontext-deadlocks.html + /// 2. https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html + /// + public class ValidationSyncContext : SynchronizationContext, IDisposable + { + private ConcurrentDictionary errors = new(); + + public void Dispose() + { + if (errors.Any()) + { + var message = "Post or Send methods were called " + errors.Count + " times."; + Console.WriteLine(message); + foreach (var pair in errors) + { + Console.WriteLine("===================="); + Console.WriteLine($"{pair.Value.method}"); + Console.WriteLine("===================="); + Console.WriteLine($"{pair.Value.trace}"); + } + + Assert.Fail(message); + } + } + + public override void Post(SendOrPostCallback d, object state) + { + errors.GetOrAdd(errors.Count, c => (nameof(Post), new StackTrace())); + + base.Post(d, state); + } + + public override void Send(SendOrPostCallback d, object state) + { + errors.GetOrAdd(errors.Count, c => (nameof(Send), new StackTrace())); + + base.Send(d, state); + } + + public static IDisposable UseSyncContext(SynchronizationContext ctx) + { + var oldSyncContext = Current; + SetSynchronizationContext(ctx); + + return Disposable.Create(() => + { + SetSynchronizationContext(oldSyncContext); + if (ctx is IDisposable disposable) + disposable.Dispose(); + }); + } + + public static IDisposable Install() => + UseSyncContext(new ValidationSyncContext()); + } +} diff --git a/CoreRemoting/RemotingClient.cs b/CoreRemoting/RemotingClient.cs index 804e916..dfb7409 100644 --- a/CoreRemoting/RemotingClient.cs +++ b/CoreRemoting/RemotingClient.cs @@ -238,14 +238,16 @@ public async Task ConnectAsync() lock(_syncObject) _activeCalls = new Dictionary(); - await _channel.ConnectAsync(); + await _channel.ConnectAsync() + .ConfigureAwait(false); if (_channel.RawMessageTransport.LastException != null) throw _channel.RawMessageTransport.LastException; await _handshakeCompletedTaskSource.Task.Timeout( _config.ConnectionTimeout, () => - throw new NetworkException("Handshake with server failed.")); + throw new NetworkException("Handshake with server failed.")) + .ConfigureAwait(false); await AuthenticateAsync() .ConfigureAwait(false); @@ -311,11 +313,15 @@ public async Task DisconnectAsync(bool quiet = false) //_goodbyeCompletedWaitHandle.Reset(); if (await _channel.RawMessageTransport.SendMessageAsync(rawData).ConfigureAwait(false)) - await _goodbyeCompletedTaskSource.Task.Timeout(10); + await _goodbyeCompletedTaskSource.Task.Timeout(10).ConfigureAwait(false); } // lock (_syncObject) // TODO: why we are locking here? - await _channel?.DisconnectAsync(); + { + var channel = _channel; + if (channel != null) + await channel.DisconnectAsync().ConfigureAwait(false); + } OnDisconnected(); _handshakeCompletedTaskSource = new(); @@ -425,7 +431,8 @@ await _rawMessageTransport.SendMessageAsync(rawData) await _authenticationCompletedTaskSource.Task.Timeout( _config.AuthenticationTimeout, () => - throw new SecurityException("Authentication timeout.")); + throw new SecurityException("Authentication timeout.")) + .ConfigureAwait(false); if (!_isAuthenticated) throw new SecurityException("Authentication failed. Please check credentials."); @@ -468,7 +475,7 @@ private void OnMessage(byte[] rawMessage) => Task.Run(() => // A wire message could have been tampered with and couldn't be deserialized break; } - }); + }).ConfigureAwait(false); private WireMessage TryDeserialize(byte[] rawMessage) { @@ -731,7 +738,8 @@ await _rawMessageTransport.SendMessageAsync(rawData) await clientRpcContext.Task.Timeout( _config.InvocationTimeout, - $"Invocation timeout ({_config.InvocationTimeout}) exceeded."); + $"Invocation timeout ({_config.InvocationTimeout}) exceeded.") + .ConfigureAwait(false); return clientRpcContext; } diff --git a/CoreRemoting/RemotingSession.cs b/CoreRemoting/RemotingSession.cs index 01a6e4d..083b6ed 100644 --- a/CoreRemoting/RemotingSession.cs +++ b/CoreRemoting/RemotingSession.cs @@ -277,13 +277,13 @@ private async void OnReceiveMessage(byte[] rawMessage) switch (message.MessageType.ToLower()) { case "auth": - await ProcessAuthenticationRequestMessage(message); + await ProcessAuthenticationRequestMessage(message).ConfigureAwait(false); break; case "rpc": - await ProcessRpcMessage(message); + await ProcessRpcMessage(message).ConfigureAwait(false); break; case "goodbye": - await ProcessGoodbyeMessage(message); + await ProcessGoodbyeMessage(message).ConfigureAwait(false); break; default: OnErrorOccured("Invalid message type " + message.MessageType + ".", ex: null); diff --git a/CoreRemoting/ServiceProxy.cs b/CoreRemoting/ServiceProxy.cs index 1990f4e..e5a551e 100644 --- a/CoreRemoting/ServiceProxy.cs +++ b/CoreRemoting/ServiceProxy.cs @@ -63,7 +63,7 @@ void IServiceProxy.Shutdown() } /// - /// Intercepts a synchronous call of a member on the proxy object. + /// Intercepts a synchronous call of a member on the proxy object. /// /// Intercepted invocation details /// Thrown if a remoting operation has been failed @@ -77,7 +77,7 @@ protected override void Intercept(IInvocation invocation) if (oneWay && returnType != typeof(void)) throw new NotSupportedException("OneWay methods must not have a return type."); - + var arguments = MapArguments(invocation.Arguments); var remoteMethodCallMessage = @@ -87,19 +87,20 @@ protected override void Intercept(IInvocation invocation) targetMethod: method, args: arguments); - var sendTask = + var sendTask = _client.InvokeRemoteMethod(remoteMethodCallMessage, oneWay); + sendTask.ConfigureAwait(false); if (!sendTask.Wait( _client.Config.SendTimeout == 0 - ? -1 // Infinite + ? -1 // Infinite : _client.Config.SendTimeout * 1000)) { throw new TimeoutException($"Send timeout ({_client.Config.SendTimeout}) exceeded."); } var clientRpcContext = sendTask.Result; - + if (clientRpcContext.Error) { if (clientRpcContext.RemoteException == null) @@ -119,7 +120,7 @@ protected override void Intercept(IInvocation invocation) var parameterInfos = method.GetParameters(); var serializer = _client.Serializer; - + foreach (var outParameterValue in resultMessage.OutParameters) { var parameterInfo = @@ -153,17 +154,17 @@ protected override void Intercept(IInvocation invocation) ? returnValueEnvelope.Value : resultMessage.ReturnValue; - // Create a proxy to remote service, if return type is a service reference + // Create a proxy to remote service, if return type is a service reference if (returnValue is ServiceReference serviceReference) returnValue = _client.CreateProxy(serviceReference); invocation.ReturnValue = returnValue; - + CallContext.RestoreFromSnapshot(resultMessage.CallContextSnapshot); } /// - /// Intercepts a asynchronous call of a member on the proxy object. + /// Intercepts an asynchronous call of a member on the proxy object. /// /// Intercepted invocation details /// Asynchronous running task @@ -178,7 +179,7 @@ protected override async ValueTask InterceptAsync(IAsyncInvocation invocation) if (oneWay && returnType != typeof(void)) throw new NotSupportedException("OneWay methods must not have a return type."); - + var arguments = MapArguments(invocation.Arguments); var remoteMethodCallMessage = @@ -188,9 +189,10 @@ protected override async ValueTask InterceptAsync(IAsyncInvocation invocation) targetMethod: method, args: arguments); - var clientRpcContext = - await _client.InvokeRemoteMethod(remoteMethodCallMessage, oneWay); - + var clientRpcContext = + await _client.InvokeRemoteMethod(remoteMethodCallMessage, oneWay) + .ConfigureAwait(false); + if (clientRpcContext.Error) { if (clientRpcContext.RemoteException == null) @@ -216,7 +218,7 @@ protected override async ValueTask InterceptAsync(IAsyncInvocation invocation) CallContext.RestoreFromSnapshot(resultMessage.CallContextSnapshot); } - + /// /// Maps a delegate argument into a serializable RemoteDelegateInfo object. /// @@ -294,7 +296,7 @@ private object[] MapArguments(IEnumerable arguments) return argument; }).ToArray(); - + return mappedArguments; } } diff --git a/CoreRemoting/Toolbox/TaskExtensions.cs b/CoreRemoting/Toolbox/TaskExtensions.cs index b5140e1..6843288 100644 --- a/CoreRemoting/Toolbox/TaskExtensions.cs +++ b/CoreRemoting/Toolbox/TaskExtensions.cs @@ -22,7 +22,7 @@ public static Task Timeout(this Task task, double secTimeout, string me public static async Task Timeout(this Task task, double secTimeout, Action throwAction) { if (secTimeout <= 0) - return await task; + return await task.ConfigureAwait(false); var delay = Task.Delay(TimeSpan.FromSeconds(secTimeout)); var result = await Task.WhenAny(task, delay).ConfigureAwait(false); @@ -46,7 +46,7 @@ public static async Task Timeout(this Task task, double secTimeout, Action throw { if (secTimeout <= 0) { - await task; + await task.ConfigureAwait(false); return; }