This commit is contained in:
parent
18b81bacce
commit
03357bf92b
|
|
@ -108,20 +108,31 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
{
|
||||
await Renderer.InvokeAsync(async () =>
|
||||
{
|
||||
SetCurrentCircuitHost(this);
|
||||
|
||||
for (var i = 0; i < Descriptors.Count; i++)
|
||||
try
|
||||
{
|
||||
var (componentType, domElementSelector) = Descriptors[i];
|
||||
await Renderer.AddComponentAsync(componentType, domElementSelector);
|
||||
SetCurrentCircuitHost(this);
|
||||
_initialized = true; // We're ready to accept incoming JSInterop calls from here on
|
||||
|
||||
await OnCircuitOpenedAsync(cancellationToken);
|
||||
await OnConnectionUpAsync(cancellationToken);
|
||||
|
||||
// We add the root components *after* the circuit is flagged as open.
|
||||
// That's because AddComponentAsync waits for quiescence, which can take
|
||||
// arbitrarily long. In the meantime we might need to be receiving and
|
||||
// processing incoming JSInterop calls or similar.
|
||||
for (var i = 0; i < Descriptors.Count; i++)
|
||||
{
|
||||
var (componentType, domElementSelector) = Descriptors[i];
|
||||
await Renderer.AddComponentAsync(componentType, domElementSelector);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// We have to handle all our own errors here, because the upstream caller
|
||||
// has to fire-and-forget this
|
||||
Renderer_UnhandledException(this, ex);
|
||||
}
|
||||
|
||||
await OnCircuitOpenedAsync(cancellationToken);
|
||||
|
||||
await OnConnectionUpAsync(cancellationToken);
|
||||
});
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public async void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
/// <summary>
|
||||
/// Intended for framework use only. Applications should not call this method directly.
|
||||
/// </summary>
|
||||
public async Task<string> StartCircuit(string uriAbsolute, string baseUriAbsolute)
|
||||
public string StartCircuit(string uriAbsolute, string baseUriAbsolute)
|
||||
{
|
||||
var circuitClient = new CircuitClientProxy(Clients.Caller, Context.ConnectionId);
|
||||
|
||||
|
|
@ -76,8 +76,11 @@ namespace Microsoft.AspNetCore.Components.Server
|
|||
|
||||
circuitHost.UnhandledException += CircuitHost_UnhandledException;
|
||||
|
||||
// If initialization fails, this will throw. The caller will fail if they try to call into any interop API.
|
||||
await circuitHost.InitializeAsync(Context.ConnectionAborted);
|
||||
// Fire-and-forget the initialization process, because we can't block the
|
||||
// SignalR message loop (we'd get a deadlock if any of the initialization
|
||||
// logic relied on receiving a subsequent message from SignalR), and it will
|
||||
// take care of its own errors anyway.
|
||||
_ = circuitHost.InitializeAsync(Context.ConnectionAborted);
|
||||
|
||||
_circuitRegistry.Register(circuitHost);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -81,6 +82,46 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
|||
handler2.VerifyAll();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitializeAsync_ReportsOwnAsyncExceptions()
|
||||
{
|
||||
// Arrange
|
||||
var handler = new Mock<CircuitHandler>(MockBehavior.Strict);
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
var reportedErrors = new List<UnhandledExceptionEventArgs>();
|
||||
|
||||
handler
|
||||
.Setup(h => h.OnCircuitOpenedAsync(It.IsAny<Circuit>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(tcs.Task)
|
||||
.Verifiable();
|
||||
|
||||
var circuitHost = TestCircuitHost.Create(handlers: new[] { handler.Object });
|
||||
circuitHost.UnhandledException += (sender, errorInfo) =>
|
||||
{
|
||||
Assert.Same(circuitHost, sender);
|
||||
reportedErrors.Add(errorInfo);
|
||||
};
|
||||
|
||||
// Act
|
||||
var initializeAsyncTask = circuitHost.InitializeAsync(new CancellationToken());
|
||||
|
||||
// Assert: No synchronous exceptions
|
||||
handler.VerifyAll();
|
||||
Assert.Empty(reportedErrors);
|
||||
|
||||
// Act: Trigger async exception
|
||||
var ex = new InvalidTimeZoneException();
|
||||
tcs.SetException(ex);
|
||||
|
||||
// Assert: The top-level task still succeeds, because the intended usage
|
||||
// pattern is fire-and-forget.
|
||||
await initializeAsyncTask;
|
||||
|
||||
// Assert: The async exception was reported via the side-channel
|
||||
Assert.Same(ex, reportedErrors.Single().ExceptionObject);
|
||||
Assert.False(reportedErrors.Single().IsTerminating);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_InvokesCircuitHandler()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue