Skip to content

Commit 0e7c873

Browse files
committed
Avoid null refs in BlazorIgntior when Disposed
Fixes #14257
1 parent 8430a30 commit 0e7c873

File tree

6 files changed

+84
-69
lines changed

6 files changed

+84
-69
lines changed

docs/BuildFromSource.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Building ASP.NET Core on Windows requires:
2727
```ps1
2828
PS> ./eng/scripts/InstallJdk.ps1
2929
```
30+
* Chrome - Selenium-based tests require a version of Chrome to be installed. Download and install it from [https://www.google.com/chrome]
3031
3132
### macOS/Linux
3233

src/Components/Components.sln

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

src/Components/Ignitor/src/BlazorClient.cs

Lines changed: 7 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public BlazorClient()
5656

5757
private CancellableOperation<CapturedAttachComponentCall> NextAttachComponentReceived { get; set; }
5858

59-
private CancellableOperation<CapturedRenderBatch> NextBatchReceived { get; set; }
59+
internal CancellableOperation<CapturedRenderBatch> NextBatchReceived { get; set; }
6060

6161
private CancellableOperation<string> NextErrorReceived { get; set; }
6262

@@ -88,7 +88,7 @@ public BlazorClient()
8888

8989
public Task<CapturedRenderBatch> PrepareForNextBatch(TimeSpan? timeout)
9090
{
91-
if (NextBatchReceived?.Completion != null)
91+
if (NextBatchReceived != null && !NextBatchReceived.Disposed)
9292
{
9393
throw new InvalidOperationException("Invalid state previous task not completed");
9494
}
@@ -100,7 +100,7 @@ public Task<CapturedRenderBatch> PrepareForNextBatch(TimeSpan? timeout)
100100

101101
public Task<CapturedJSInteropCall> PrepareForNextJSInterop(TimeSpan? timeout)
102102
{
103-
if (NextJSInteropReceived?.Completion != null)
103+
if (NextJSInteropReceived != null && !NextJSInteropReceived.Disposed)
104104
{
105105
throw new InvalidOperationException("Invalid state previous task not completed");
106106
}
@@ -112,7 +112,7 @@ public Task<CapturedJSInteropCall> PrepareForNextJSInterop(TimeSpan? timeout)
112112

113113
public Task<string> PrepareForNextDotNetInterop(TimeSpan? timeout)
114114
{
115-
if (NextDotNetInteropCompletionReceived?.Completion != null)
115+
if (NextDotNetInteropCompletionReceived != null && !NextDotNetInteropCompletionReceived.Disposed)
116116
{
117117
throw new InvalidOperationException("Invalid state previous task not completed");
118118
}
@@ -124,7 +124,7 @@ public Task<string> PrepareForNextDotNetInterop(TimeSpan? timeout)
124124

125125
public Task<string> PrepareForNextCircuitError(TimeSpan? timeout)
126126
{
127-
if (NextErrorReceived?.Completion != null)
127+
if (NextErrorReceived != null && !NextErrorReceived.Disposed)
128128
{
129129
throw new InvalidOperationException("Invalid state previous task not completed");
130130
}
@@ -136,7 +136,7 @@ public Task<string> PrepareForNextCircuitError(TimeSpan? timeout)
136136

137137
public Task<Exception> PrepareForNextDisconnect(TimeSpan? timeout)
138138
{
139-
if (NextDisconnect?.Completion != null)
139+
if (NextDisconnect != null && !NextDisconnect.Disposed)
140140
{
141141
throw new InvalidOperationException("Invalid state previous task not completed");
142142
}
@@ -396,7 +396,7 @@ private void OnBeginInvokeJS(int asyncHandle, string identifier, string argsJson
396396
NextJSInteropReceived?.Completion?.TrySetResult(null);
397397
}
398398

399-
private void OnRenderBatch(int id, byte[] data)
399+
protected virtual void OnRenderBatch(int id, byte[] data)
400400
{
401401
var capturedBatch = new CapturedRenderBatch(id, data);
402402

@@ -499,56 +499,6 @@ public ElementNode FindElementById(string id)
499499
return element;
500500
}
501501

502-
private class CancellableOperation<TResult>
503-
{
504-
public CancellableOperation(TimeSpan? timeout)
505-
{
506-
Timeout = timeout;
507-
Initialize();
508-
}
509-
510-
public TimeSpan? Timeout { get; }
511-
512-
public TaskCompletionSource<TResult> Completion { get; set; }
513-
514-
public CancellationTokenSource Cancellation { get; set; }
515-
516-
public CancellationTokenRegistration CancellationRegistration { get; set; }
517-
518-
private void Initialize()
519-
{
520-
Completion = new TaskCompletionSource<TResult>(TaskContinuationOptions.RunContinuationsAsynchronously);
521-
Completion.Task.ContinueWith(
522-
(task, state) =>
523-
{
524-
var operation = (CancellableOperation<TResult>)state;
525-
operation.Dispose();
526-
},
527-
this,
528-
TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues
529-
if (Timeout != null && Timeout != System.Threading.Timeout.InfiniteTimeSpan && Timeout != TimeSpan.MaxValue)
530-
{
531-
Cancellation = new CancellationTokenSource(Timeout.Value);
532-
CancellationRegistration = Cancellation.Token.Register(
533-
(self) =>
534-
{
535-
var operation = (CancellableOperation<TResult>)self;
536-
operation.Completion.TrySetCanceled(operation.Cancellation.Token);
537-
operation.Cancellation.Dispose();
538-
operation.CancellationRegistration.Dispose();
539-
},
540-
this);
541-
}
542-
}
543-
544-
private void Dispose()
545-
{
546-
Completion = null;
547-
Cancellation.Dispose();
548-
CancellationRegistration.Dispose();
549-
}
550-
}
551-
552502
private string[] ReadMarkers(string content)
553503
{
554504
content = content.Replace("\r\n", "").Replace("\n", "");
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace Ignitor
9+
{
10+
internal class CancellableOperation<TResult>
11+
{
12+
public CancellableOperation(TimeSpan? timeout)
13+
{
14+
Timeout = timeout;
15+
16+
Completion = new TaskCompletionSource<TResult>(TaskContinuationOptions.RunContinuationsAsynchronously);
17+
Completion.Task.ContinueWith(
18+
(task, state) =>
19+
{
20+
var operation = (CancellableOperation<TResult>)state;
21+
operation.Dispose();
22+
},
23+
this,
24+
TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues
25+
26+
if (Timeout != null && Timeout != System.Threading.Timeout.InfiniteTimeSpan && Timeout != TimeSpan.MaxValue)
27+
{
28+
Cancellation = new CancellationTokenSource(Timeout.Value);
29+
CancellationRegistration = Cancellation.Token.Register(
30+
(self) =>
31+
{
32+
var operation = (CancellableOperation<TResult>)self;
33+
operation.Completion.TrySetCanceled(operation.Cancellation.Token);
34+
operation.Cancellation.Dispose();
35+
operation.CancellationRegistration.Dispose();
36+
},
37+
this);
38+
}
39+
}
40+
41+
public TimeSpan? Timeout { get; }
42+
43+
public TaskCompletionSource<TResult> Completion { get; }
44+
45+
public CancellationTokenSource Cancellation { get; }
46+
47+
public CancellationTokenRegistration CancellationRegistration { get; }
48+
49+
public bool Disposed { get; private set; }
50+
51+
private void Dispose()
52+
{
53+
if (Disposed)
54+
{
55+
return;
56+
}
57+
58+
Disposed = true;
59+
Completion.TrySetCanceled(Cancellation.Token);
60+
Cancellation.Dispose();
61+
CancellationRegistration.Dispose();
62+
}
63+
}
64+
}

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Shared/E2ETesting/BrowserFixture.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public async Task DisposeAsync()
108108
var instance = await SeleniumStandaloneServer.GetInstanceAsync(output);
109109

110110
var attempt = 0;
111-
var maxAttempts = 3;
111+
const int maxAttempts = 3;
112112
do
113113
{
114114
try
@@ -132,18 +132,16 @@ public async Task DisposeAsync()
132132

133133
return (driver, logs);
134134
}
135-
catch
135+
catch (Exception ex)
136136
{
137-
if (attempt >= maxAttempts)
138-
{
139-
throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is irresponsive");
140-
}
137+
output.WriteLine($"Error initializing RemoteWebDriver: {ex.Message}");
141138
}
139+
142140
attempt++;
141+
143142
} while (attempt < maxAttempts);
144143

145-
// We will never get here. Keeping the compiler happy.
146-
throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is unresponsive");
144+
throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is irresponsive");
147145
}
148146
}
149147
}

0 commit comments

Comments
 (0)