Skip to content

Commit 1937c80

Browse files
authored
Avoid null refs in BlazorIgntior when Disposed (#14341)
* Avoid null refs in BlazorIgntior when Disposed Fixes #14257
2 parents 6317b34 + db8cef1 commit 1937c80

File tree

5 files changed

+78
-65
lines changed

5 files changed

+78
-65
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/Ignitor/src/BlazorClient.cs

Lines changed: 5 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}
@@ -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)