From e955c4b9aaa996c356aa80e6af655f5e0a8d2770 Mon Sep 17 00:00:00 2001 From: Pawel Kadluczka Date: Wed, 22 Mar 2017 11:06:47 -0700 Subject: [PATCH] Handling exceptions thrown when invoking a hub method (#332) --- .../HubConnection.cs | 29 +++++++++++------ .../HubConnectionTests.cs | 32 +++++++++++++++---- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs b/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs index 2a8ef35908..c784250269 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs @@ -102,8 +102,6 @@ namespace Microsoft.AspNetCore.SignalR.Client public Task Invoke(string methodName, Type returnType, params object[] args) => Invoke(methodName, returnType, CancellationToken.None, args); public async Task Invoke(string methodName, Type returnType, CancellationToken cancellationToken, params object[] args) { - // TODO: we should reject calls to here after the connection is "done" or has not been started (e.g. sending an invocation failed) - _logger.LogTrace("Preparing invocation of '{0}', with return type '{1}' and {2} args", methodName, returnType.AssemblyQualifiedName, args.Length); // Create an invocation descriptor. @@ -134,15 +132,26 @@ namespace Microsoft.AspNetCore.SignalR.Client _logger.LogTrace("Invocation #{0}: {1} {2}({3})", descriptor.Id, returnType.FullName, methodName, argsList); } - var ms = new MemoryStream(); - await _adapter.WriteMessageAsync(descriptor, ms, cancellationToken); + try + { + var ms = new MemoryStream(); + await _adapter.WriteMessageAsync(descriptor, ms, cancellationToken); - _logger.LogInformation("Sending Invocation #{0}", descriptor.Id); + _logger.LogInformation("Sending Invocation #{0}", descriptor.Id); - // TODO: Format.Text - who, where and when decides about the format of outgoing messages - // TODO HIGH: Handle return value/Exception from SendAsync - await _connection.SendAsync(ms.ToArray(), MessageType.Text, cancellationToken); - _logger.LogInformation("Sending Invocation #{0} complete", descriptor.Id); + // TODO: Format.Text - who, where and when decides about the format of outgoing messages + await _connection.SendAsync(ms.ToArray(), MessageType.Text, cancellationToken); + _logger.LogInformation("Sending Invocation #{0} complete", descriptor.Id); + } + catch (Exception ex) + { + _logger.LogError(0, ex, "Sending Invocation #{0} failed", descriptor.Id); + irq.Completion.TrySetException(ex); + lock (_pendingCallsLock) + { + _pendingCalls.Remove(descriptor.Id); + } + } // Return the completion task. It will be completed by ReceiveMessages when the response is received. return await irq.Completion.Task; @@ -294,7 +303,7 @@ namespace Microsoft.AspNetCore.SignalR.Client public InvocationRequest(CancellationToken cancellationToken, Type resultType) { - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Completion = tcs; CancellationToken = cancellationToken; Registration = cancellationToken.Register(() => tcs.TrySetCanceled()); diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs index ad9d910ee3..80dd61ba58 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging; using Moq; using Moq.Protected; using Xunit; +using System.IO; namespace Microsoft.AspNetCore.SignalR.Client.Tests { @@ -92,24 +93,43 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests } } - [Fact(Skip = "Not implemented")] + [Fact] public async Task InvokeThrowsIfHubConnectionNotStarted() { - var hubConnection = new HubConnection(new Uri("http://fakeuri.org"), Mock.Of(), Mock.Of()); + var hubConnection = new HubConnection(new Uri("http://fakeuri.org")); var exception = await Assert.ThrowsAsync(async () => await hubConnection.Invoke("test")); - Assert.Equal("Cannot invoke methods on non-started connections.", exception.Message); + Assert.Equal("Cannot send messages when the connection is not in the Connected state.", exception.Message); } - [Fact(Skip = "Not implemented")] + [Fact] public async Task InvokeThrowsIfHubConnectionDisposed() { - var hubConnection = new HubConnection(new Uri("http://fakeuri.org"), Mock.Of(), Mock.Of()); + var hubConnection = new HubConnection(new Uri("http://fakeuri.org")); await hubConnection.DisposeAsync(); var exception = await Assert.ThrowsAsync(async () => await hubConnection.Invoke("test")); - Assert.Equal("Cannot invoke methods on disposed connections.", exception.Message); + Assert.Equal("Cannot send messages when the connection is not in the Connected state.", exception.Message); + } + + [Fact] + public async Task InvokeThrowsIfSerializingMessageFails() + { + var mockConnection = new Mock(); + + var exception = new InvalidOperationException(); + var mockInvocationAdapter = new Mock(); + mockInvocationAdapter + .Setup(a => a.WriteMessageAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromException(exception)); + + var hubConnection = new HubConnection(mockConnection.Object, mockInvocationAdapter.Object, null); + await hubConnection.StartAsync(Mock.Of()); + + var actualException = + await Assert.ThrowsAsync(async () => await hubConnection.Invoke("test")); + Assert.Same(exception, actualException); } [Fact]