Avoid null refs in BlazorIgntior when Disposed

Fixes https://github.com/aspnet/AspNetCore/issues/14257
This commit is contained in:
Pranav K 2019-09-23 14:51:25 -07:00
parent 8430a30abc
commit 0e7c873d62
6 changed files with 84 additions and 69 deletions

View File

@ -27,6 +27,7 @@ Building ASP.NET Core on Windows requires:
```ps1
PS> ./eng/scripts/InstallJdk.ps1
```
* Chrome - Selenium-based tests require a version of Chrome to be installed. Download and install it from [https://www.google.com/chrome]
### macOS/Linux

View File

@ -238,6 +238,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsP
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorServerApp", "Samples\BlazorServerApp\BlazorServerApp.csproj", "{BBF37AF9-8290-4B70-8BA8-0F6017B3B620}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Ignitor", "Ignitor", "{29B5A306-7273-4649-8B04-26234D71ADBD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1583,8 +1585,8 @@ Global
{DA137BD4-F7F1-4D53-855F-5EC40CEA36B0} = {2FC10057-7A0A-4E34-8302-879925BC0102}
{0CDAB70B-71DC-43BE-ACB7-AD2EE3541FFB} = {2FC10057-7A0A-4E34-8302-879925BC0102}
{F88118E1-6F4A-4F89-B047-5FFD2889B9F0} = {2FC10057-7A0A-4E34-8302-879925BC0102}
{A78CE874-76B7-46FE-8009-1ED5258BA0AA} = {D6712550-0DA2-49C8-88E1-F04CAB982BF4}
{FC2A1EB0-A116-4689-92B7-239B1DCCF4CA} = {D6712550-0DA2-49C8-88E1-F04CAB982BF4}
{A78CE874-76B7-46FE-8009-1ED5258BA0AA} = {29B5A306-7273-4649-8B04-26234D71ADBD}
{FC2A1EB0-A116-4689-92B7-239B1DCCF4CA} = {29B5A306-7273-4649-8B04-26234D71ADBD}
{74D21785-2FAB-4266-B7C4-E311EC8EE0DF} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{E4C01A3F-D3C1-4639-A6A9-930E918843DD} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684} = {A27FF193-195B-4474-8E6C-840B2E339373}

View File

@ -56,7 +56,7 @@ namespace Ignitor
private CancellableOperation<CapturedAttachComponentCall> NextAttachComponentReceived { get; set; }
private CancellableOperation<CapturedRenderBatch> NextBatchReceived { get; set; }
internal CancellableOperation<CapturedRenderBatch> NextBatchReceived { get; set; }
private CancellableOperation<string> NextErrorReceived { get; set; }
@ -88,7 +88,7 @@ namespace Ignitor
public Task<CapturedRenderBatch> PrepareForNextBatch(TimeSpan? timeout)
{
if (NextBatchReceived?.Completion != null)
if (NextBatchReceived != null && !NextBatchReceived.Disposed)
{
throw new InvalidOperationException("Invalid state previous task not completed");
}
@ -100,7 +100,7 @@ namespace Ignitor
public Task<CapturedJSInteropCall> PrepareForNextJSInterop(TimeSpan? timeout)
{
if (NextJSInteropReceived?.Completion != null)
if (NextJSInteropReceived != null && !NextJSInteropReceived.Disposed)
{
throw new InvalidOperationException("Invalid state previous task not completed");
}
@ -112,7 +112,7 @@ namespace Ignitor
public Task<string> PrepareForNextDotNetInterop(TimeSpan? timeout)
{
if (NextDotNetInteropCompletionReceived?.Completion != null)
if (NextDotNetInteropCompletionReceived != null && !NextDotNetInteropCompletionReceived.Disposed)
{
throw new InvalidOperationException("Invalid state previous task not completed");
}
@ -124,7 +124,7 @@ namespace Ignitor
public Task<string> PrepareForNextCircuitError(TimeSpan? timeout)
{
if (NextErrorReceived?.Completion != null)
if (NextErrorReceived != null && !NextErrorReceived.Disposed)
{
throw new InvalidOperationException("Invalid state previous task not completed");
}
@ -136,7 +136,7 @@ namespace Ignitor
public Task<Exception> PrepareForNextDisconnect(TimeSpan? timeout)
{
if (NextDisconnect?.Completion != null)
if (NextDisconnect != null && !NextDisconnect.Disposed)
{
throw new InvalidOperationException("Invalid state previous task not completed");
}
@ -396,7 +396,7 @@ namespace Ignitor
NextJSInteropReceived?.Completion?.TrySetResult(null);
}
private void OnRenderBatch(int id, byte[] data)
protected virtual void OnRenderBatch(int id, byte[] data)
{
var capturedBatch = new CapturedRenderBatch(id, data);
@ -499,56 +499,6 @@ namespace Ignitor
return element;
}
private class CancellableOperation<TResult>
{
public CancellableOperation(TimeSpan? timeout)
{
Timeout = timeout;
Initialize();
}
public TimeSpan? Timeout { get; }
public TaskCompletionSource<TResult> Completion { get; set; }
public CancellationTokenSource Cancellation { get; set; }
public CancellationTokenRegistration CancellationRegistration { get; set; }
private void Initialize()
{
Completion = new TaskCompletionSource<TResult>(TaskContinuationOptions.RunContinuationsAsynchronously);
Completion.Task.ContinueWith(
(task, state) =>
{
var operation = (CancellableOperation<TResult>)state;
operation.Dispose();
},
this,
TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues
if (Timeout != null && Timeout != System.Threading.Timeout.InfiniteTimeSpan && Timeout != TimeSpan.MaxValue)
{
Cancellation = new CancellationTokenSource(Timeout.Value);
CancellationRegistration = Cancellation.Token.Register(
(self) =>
{
var operation = (CancellableOperation<TResult>)self;
operation.Completion.TrySetCanceled(operation.Cancellation.Token);
operation.Cancellation.Dispose();
operation.CancellationRegistration.Dispose();
},
this);
}
}
private void Dispose()
{
Completion = null;
Cancellation.Dispose();
CancellationRegistration.Dispose();
}
}
private string[] ReadMarkers(string content)
{
content = content.Replace("\r\n", "").Replace("\n", "");

View File

@ -0,0 +1,64 @@
// 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;
using System.Threading;
using System.Threading.Tasks;
namespace Ignitor
{
internal class CancellableOperation<TResult>
{
public CancellableOperation(TimeSpan? timeout)
{
Timeout = timeout;
Completion = new TaskCompletionSource<TResult>(TaskContinuationOptions.RunContinuationsAsynchronously);
Completion.Task.ContinueWith(
(task, state) =>
{
var operation = (CancellableOperation<TResult>)state;
operation.Dispose();
},
this,
TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues
if (Timeout != null && Timeout != System.Threading.Timeout.InfiniteTimeSpan && Timeout != TimeSpan.MaxValue)
{
Cancellation = new CancellationTokenSource(Timeout.Value);
CancellationRegistration = Cancellation.Token.Register(
(self) =>
{
var operation = (CancellableOperation<TResult>)self;
operation.Completion.TrySetCanceled(operation.Cancellation.Token);
operation.Cancellation.Dispose();
operation.CancellationRegistration.Dispose();
},
this);
}
}
public TimeSpan? Timeout { get; }
public TaskCompletionSource<TResult> Completion { get; }
public CancellationTokenSource Cancellation { get; }
public CancellationTokenRegistration CancellationRegistration { get; }
public bool Disposed { get; private set; }
private void Dispose()
{
if (Disposed)
{
return;
}
Disposed = true;
Completion.TrySetCanceled(Cancellation.Token);
Cancellation.Dispose();
CancellationRegistration.Dispose();
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.E2ETesting
var instance = await SeleniumStandaloneServer.GetInstanceAsync(output);
var attempt = 0;
var maxAttempts = 3;
const int maxAttempts = 3;
do
{
try
@ -132,18 +132,16 @@ namespace Microsoft.AspNetCore.E2ETesting
return (driver, logs);
}
catch
catch (Exception ex)
{
if (attempt >= maxAttempts)
{
throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is irresponsive");
}
output.WriteLine($"Error initializing RemoteWebDriver: {ex.Message}");
}
attempt++;
} while (attempt < maxAttempts);
// We will never get here. Keeping the compiler happy.
throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is unresponsive");
throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is irresponsive");
}
}
}