Harden StartCircuit (#12825)
* Harden StartCircuit Fixes: #12057 Adds some upfront argument validation as well as error handling for circuit intialization failures.
This commit is contained in:
parent
cd613fe703
commit
d52d7e3284
|
|
@ -278,6 +278,10 @@ namespace Microsoft.AspNetCore.Components
|
||||||
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
[Microsoft.AspNetCore.Components.ParameterAttribute]
|
||||||
public Microsoft.AspNetCore.Components.RenderFragment Body { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public Microsoft.AspNetCore.Components.RenderFragment Body { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
}
|
}
|
||||||
|
public sealed partial class LocationChangeException : System.Exception
|
||||||
|
{
|
||||||
|
public LocationChangeException(string message, System.Exception innerException) { }
|
||||||
|
}
|
||||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||||
public readonly partial struct MarkupString
|
public readonly partial struct MarkupString
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An exception thrown when <see cref="NavigationManager.LocationChanged"/> throws an exception.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class LocationChangeException : Exception
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="LocationChangeException"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The exception message.</param>
|
||||||
|
/// <param name="innerException">The inner exception.</param>
|
||||||
|
public LocationChangeException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices.ComTypes;
|
|
||||||
using Microsoft.AspNetCore.Components.Routing;
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components
|
namespace Microsoft.AspNetCore.Components
|
||||||
|
|
@ -129,8 +128,9 @@ namespace Microsoft.AspNetCore.Components
|
||||||
|
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
|
|
||||||
Uri = uri;
|
// Setting BaseUri before Uri so they get validated.
|
||||||
BaseUri = baseUri;
|
BaseUri = baseUri;
|
||||||
|
Uri = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -201,7 +201,14 @@ namespace Microsoft.AspNetCore.Components
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void NotifyLocationChanged(bool isInterceptedLink)
|
protected void NotifyLocationChanged(bool isInterceptedLink)
|
||||||
{
|
{
|
||||||
_locationChanged?.Invoke(this, new LocationChangedEventArgs(_uri, isInterceptedLink));
|
try
|
||||||
|
{
|
||||||
|
_locationChanged?.Invoke(this, new LocationChangedEventArgs(_uri, isInterceptedLink));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new LocationChangeException("An exception occurred while dispatching a location changed event.", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssertInitialized()
|
private void AssertInitialized()
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,24 @@ namespace Microsoft.AspNetCore.Components
|
||||||
Assert.Equal(expectedResult, actualResult);
|
Assert.Equal(expectedResult, actualResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("scheme://host/", "otherscheme://host/")]
|
||||||
|
[InlineData("scheme://host/", "scheme://otherhost/")]
|
||||||
|
[InlineData("scheme://host/path/", "scheme://host/")]
|
||||||
|
public void Initialize_ThrowsForInvalidBaseRelativePaths(string baseUri, string absoluteUri)
|
||||||
|
{
|
||||||
|
var navigationManager = new TestNavigationManager();
|
||||||
|
|
||||||
|
var ex = Assert.Throws<ArgumentException>(() =>
|
||||||
|
{
|
||||||
|
navigationManager.Initialize(baseUri, absoluteUri);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
$"The URI '{absoluteUri}' is not contained by the base URI '{baseUri}'.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("scheme://host/", "otherscheme://host/")]
|
[InlineData("scheme://host/", "otherscheme://host/")]
|
||||||
[InlineData("scheme://host/", "scheme://otherhost/")]
|
[InlineData("scheme://host/", "scheme://otherhost/")]
|
||||||
|
|
@ -76,9 +94,18 @@ namespace Microsoft.AspNetCore.Components
|
||||||
|
|
||||||
private class TestNavigationManager : NavigationManager
|
private class TestNavigationManager : NavigationManager
|
||||||
{
|
{
|
||||||
|
public TestNavigationManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public TestNavigationManager(string baseUri = null, string uri = null)
|
public TestNavigationManager(string baseUri = null, string uri = null)
|
||||||
{
|
{
|
||||||
Initialize(baseUri ?? "http://example.com/", uri ?? "http://example.com/welcome-page");
|
Initialize(baseUri ?? "http://example.com/", uri ?? baseUri ?? "http://example.com/welcome-page");
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void Initialize(string baseUri, string uri)
|
||||||
|
{
|
||||||
|
base.Initialize(baseUri, uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void NavigateToCore(string uri, bool forceLoad)
|
protected override void NavigateToCore(string uri, bool forceLoad)
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
string uri,
|
string uri,
|
||||||
ClaimsPrincipal user)
|
ClaimsPrincipal user)
|
||||||
{
|
{
|
||||||
|
// We do as much intialization as possible eagerly in this method, which makes the error handling
|
||||||
|
// story much simpler. If we throw from here, it's handled inside the initial hub method.
|
||||||
var components = ResolveComponentMetadata(httpContext, client);
|
var components = ResolveComponentMetadata(httpContext, client);
|
||||||
|
|
||||||
var scope = _scopeFactory.CreateScope();
|
var scope = _scopeFactory.CreateScope();
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,18 @@ namespace Microsoft.AspNetCore.Components.Server
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (baseUri == null ||
|
||||||
|
uri == null ||
|
||||||
|
!Uri.IsWellFormedUriString(baseUri, UriKind.Absolute) ||
|
||||||
|
!Uri.IsWellFormedUriString(uri, UriKind.Absolute))
|
||||||
|
{
|
||||||
|
// We do some really minimal validation here to prevent obviously wrong data from getting in
|
||||||
|
// without duplicating too much logic.
|
||||||
|
Log.InvalidInputData(_logger);
|
||||||
|
_ = NotifyClientError(Clients.Caller, $"The uris provided are invalid.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var circuitClient = new CircuitClientProxy(Clients.Caller, Context.ConnectionId);
|
var circuitClient = new CircuitClientProxy(Clients.Caller, Context.ConnectionId);
|
||||||
if (DefaultCircuitFactory.ResolveComponentMetadata(Context.GetHttpContext(), circuitClient).Count == 0)
|
if (DefaultCircuitFactory.ResolveComponentMetadata(Context.GetHttpContext(), circuitClient).Count == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -122,26 +134,35 @@ namespace Microsoft.AspNetCore.Components.Server
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var circuitHost = _circuitFactory.CreateCircuitHost(
|
try
|
||||||
Context.GetHttpContext(),
|
{
|
||||||
circuitClient,
|
var circuitHost = _circuitFactory.CreateCircuitHost(
|
||||||
baseUri,
|
Context.GetHttpContext(),
|
||||||
uri,
|
circuitClient,
|
||||||
Context.User);
|
baseUri,
|
||||||
|
uri,
|
||||||
|
Context.User);
|
||||||
|
|
||||||
circuitHost.UnhandledException += CircuitHost_UnhandledException;
|
circuitHost.UnhandledException += CircuitHost_UnhandledException;
|
||||||
|
|
||||||
// Fire-and-forget the initialization process, because we can't block the
|
// 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
|
// 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
|
// logic relied on receiving a subsequent message from SignalR), and it will
|
||||||
// take care of its own errors anyway.
|
// take care of its own errors anyway.
|
||||||
_ = circuitHost.InitializeAsync(Context.ConnectionAborted);
|
_ = circuitHost.InitializeAsync(Context.ConnectionAborted);
|
||||||
|
|
||||||
_circuitRegistry.Register(circuitHost);
|
// It's safe to *publish* the circuit now because nothing will be able
|
||||||
|
// to run inside it until after InitializeAsync completes.
|
||||||
CircuitHost = circuitHost;
|
_circuitRegistry.Register(circuitHost);
|
||||||
|
CircuitHost = circuitHost;
|
||||||
return circuitHost.CircuitId;
|
return circuitHost.CircuitId;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.CircuitInitializationFailed(_logger, ex);
|
||||||
|
NotifyClientError(Clients.Caller, "The circuit failed to initialize.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -292,6 +313,12 @@ namespace Microsoft.AspNetCore.Components.Server
|
||||||
private static readonly Action<ILogger, string, Exception> _circuitTerminatedGracefully =
|
private static readonly Action<ILogger, string, Exception> _circuitTerminatedGracefully =
|
||||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, "CircuitTerminatedGracefully"), "Circuit '{CircuitId}' terminated gracefully");
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, "CircuitTerminatedGracefully"), "Circuit '{CircuitId}' terminated gracefully");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _invalidInputData =
|
||||||
|
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(8, "InvalidInputData"), "Call to '{CallSite}' received invalid input data");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Exception> _circuitInitializationFailed =
|
||||||
|
LoggerMessage.Define(LogLevel.Debug, new EventId(9, "CircuitInitializationFailed"), "Circuit initialization failed");
|
||||||
|
|
||||||
public static void NoComponentsRegisteredInEndpoint(ILogger logger, string endpointDisplayName)
|
public static void NoComponentsRegisteredInEndpoint(ILogger logger, string endpointDisplayName)
|
||||||
{
|
{
|
||||||
_noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null);
|
_noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null);
|
||||||
|
|
@ -317,6 +344,10 @@ namespace Microsoft.AspNetCore.Components.Server
|
||||||
public static void CircuitHostNotInitialized(ILogger logger, [CallerMemberName] string callSite = "") => _circuitHostNotInitialized(logger, callSite, null);
|
public static void CircuitHostNotInitialized(ILogger logger, [CallerMemberName] string callSite = "") => _circuitHostNotInitialized(logger, callSite, null);
|
||||||
|
|
||||||
public static void CircuitTerminatedGracefully(ILogger logger, string circuitId) => _circuitTerminatedGracefully(logger, circuitId, null);
|
public static void CircuitTerminatedGracefully(ILogger logger, string circuitId) => _circuitTerminatedGracefully(logger, circuitId, null);
|
||||||
|
|
||||||
|
public static void InvalidInputData(ILogger logger, [CallerMemberName] string callSite = "") => _invalidInputData(logger, callSite, null);
|
||||||
|
|
||||||
|
public static void CircuitInitializationFailed(ILogger logger, Exception exception) => _circuitInitializationFailed(logger, exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,65 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
|
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
|
||||||
"StartCircuit",
|
"StartCircuit",
|
||||||
baseUri,
|
baseUri,
|
||||||
baseUri.GetLeftPart(UriPartial.Authority)));
|
baseUri + "/home"));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actualError = Assert.Single(Errors);
|
||||||
|
Assert.Matches(expectedError, actualError);
|
||||||
|
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotStartCircuitWithNullData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "The uris provided are invalid.";
|
||||||
|
var rootUri = _serverFixture.RootUri;
|
||||||
|
var uri = new Uri(rootUri, "/subdir");
|
||||||
|
Assert.True(await Client.ConnectAsync(uri, prerendered: false, connectAutomatically: false), "Couldn't connect to the app");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync("StartCircuit", null, null));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actualError = Assert.Single(Errors);
|
||||||
|
Assert.Matches(expectedError, actualError);
|
||||||
|
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotStartCircuitWithInvalidUris()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "The uris provided are invalid.";
|
||||||
|
var rootUri = _serverFixture.RootUri;
|
||||||
|
var uri = new Uri(rootUri, "/subdir");
|
||||||
|
Assert.True(await Client.ConnectAsync(uri, prerendered: false, connectAutomatically: false), "Couldn't connect to the app");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync("StartCircuit", uri.AbsoluteUri, "/foo"));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actualError = Assert.Single(Errors);
|
||||||
|
Assert.Matches(expectedError, actualError);
|
||||||
|
Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a hand-chosen example of something that will cause an exception in creating the circuit host.
|
||||||
|
// We want to test this case so that we know what happens when creating the circuit host blows up.
|
||||||
|
[Fact]
|
||||||
|
public async Task StartCircuitCausesInitializationError()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "The circuit failed to initialize.";
|
||||||
|
var rootUri = _serverFixture.RootUri;
|
||||||
|
var uri = new Uri(rootUri, "/subdir");
|
||||||
|
Assert.True(await Client.ConnectAsync(uri, prerendered: false, connectAutomatically: false), "Couldn't connect to the app");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
//
|
||||||
|
// These are valid URIs by the BaseUri doesn't contain the Uri - so it fails to initialize.
|
||||||
|
await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync("StartCircuit", uri, "http://example.com"), TimeSpan.FromHours(1));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var actualError = Assert.Single(Errors);
|
var actualError = Assert.Single(Errors);
|
||||||
|
|
|
||||||
|
|
@ -76,38 +76,38 @@ namespace Ignitor
|
||||||
return NextBatchReceived.Completion.Task;
|
return NextBatchReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PrepareForNextJSInterop()
|
public Task PrepareForNextJSInterop(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextJSInteropReceived?.Completion != null)
|
if (NextJSInteropReceived?.Completion != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextJSInteropReceived = new CancellableOperation(DefaultLatencyTimeout);
|
NextJSInteropReceived = new CancellableOperation(timeout);
|
||||||
|
|
||||||
return NextJSInteropReceived.Completion.Task;
|
return NextJSInteropReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PrepareForNextDotNetInterop()
|
public Task PrepareForNextDotNetInterop(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextDotNetInteropCompletionReceived?.Completion != null)
|
if (NextDotNetInteropCompletionReceived?.Completion != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextDotNetInteropCompletionReceived = new CancellableOperation(DefaultLatencyTimeout);
|
NextDotNetInteropCompletionReceived = new CancellableOperation(timeout);
|
||||||
|
|
||||||
return NextDotNetInteropCompletionReceived.Completion.Task;
|
return NextDotNetInteropCompletionReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PrepareForNextCircuitError()
|
public Task PrepareForNextCircuitError(TimeSpan? timeout)
|
||||||
{
|
{
|
||||||
if (NextErrorReceived?.Completion != null)
|
if (NextErrorReceived?.Completion != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid state previous task not completed");
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
NextErrorReceived = new CancellableOperation(DefaultLatencyTimeout);
|
NextErrorReceived = new CancellableOperation(timeout);
|
||||||
|
|
||||||
return NextErrorReceived.Completion.Task;
|
return NextErrorReceived.Completion.Task;
|
||||||
}
|
}
|
||||||
|
|
@ -139,23 +139,23 @@ namespace Ignitor
|
||||||
await task;
|
await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExpectJSInterop(Func<Task> action)
|
public async Task ExpectJSInterop(Func<Task> action, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
var task = WaitForJSInterop();
|
var task = WaitForJSInterop(timeout);
|
||||||
await action();
|
await action();
|
||||||
await task;
|
await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExpectDotNetInterop(Func<Task> action)
|
public async Task ExpectDotNetInterop(Func<Task> action, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
var task = WaitForDotNetInterop();
|
var task = WaitForDotNetInterop(timeout);
|
||||||
await action();
|
await action();
|
||||||
await task;
|
await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExpectCircuitError(Func<Task> action)
|
public async Task ExpectCircuitError(Func<Task> action, TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
var task = WaitForCircuitError();
|
var task = WaitForCircuitError(timeout);
|
||||||
await action();
|
await action();
|
||||||
await task;
|
await task;
|
||||||
}
|
}
|
||||||
|
|
@ -175,42 +175,42 @@ namespace Ignitor
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task WaitForJSInterop()
|
private async Task WaitForJSInterop(TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
if (ImplicitWait)
|
if (ImplicitWait)
|
||||||
{
|
{
|
||||||
if (DefaultLatencyTimeout == null)
|
if (DefaultLatencyTimeout == null && timeout == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await PrepareForNextJSInterop();
|
await PrepareForNextJSInterop(timeout ?? DefaultLatencyTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task WaitForDotNetInterop()
|
private async Task WaitForDotNetInterop(TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
if (ImplicitWait)
|
if (ImplicitWait)
|
||||||
{
|
{
|
||||||
if (DefaultLatencyTimeout == null)
|
if (DefaultLatencyTimeout == null && timeout == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await PrepareForNextDotNetInterop();
|
await PrepareForNextDotNetInterop(timeout ?? DefaultLatencyTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task WaitForCircuitError()
|
private async Task WaitForCircuitError(TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
if (ImplicitWait)
|
if (ImplicitWait)
|
||||||
{
|
{
|
||||||
if (DefaultLatencyTimeout == null)
|
if (DefaultLatencyTimeout == null && timeout == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await PrepareForNextCircuitError();
|
await PrepareForNextCircuitError(timeout ?? DefaultLatencyTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,7 +246,7 @@ namespace Ignitor
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await ExpectRenderBatch(
|
await ExpectRenderBatch(
|
||||||
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, new Uri(uri.GetLeftPart(UriPartial.Authority))),
|
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, uri),
|
||||||
TimeSpan.FromSeconds(10));
|
TimeSpan.FromSeconds(10));
|
||||||
return CircuitId != null;
|
return CircuitId != null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue