Skip to content

Commit a4f3065

Browse files
committed
Fixes
1 parent 32d0698 commit a4f3065

File tree

3 files changed

+61
-20
lines changed

3 files changed

+61
-20
lines changed

src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/FailingQuickGrid.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,6 @@ internal class FailingQuickGrid<TGridItem> : QuickGrid<TGridItem>, IAsyncDisposa
3535
public new async ValueTask DisposeAsync()
3636
{
3737
DisposeAsyncWasCalled = true;
38-
// Intentionally do nothing to prevent _disposeBool from being set to true
39-
// This means the OnAfterRenderAsync method will not detect that the component is disposed
40-
// and will proceed to call init() even after disposal, demonstrating the race condition
41-
42-
// DO NOT call base.DisposeAsync() - this is the key to simulating the race condition
4338
await Task.CompletedTask;
4439
}
4540

src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/GridRaceConditionTest.cs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,18 @@ public async Task CanCorrectlyDisposeAsync()
3939
// Complete the JS module loading
4040
moduleLoadCompletion.SetResult();
4141

42-
// Assert that init was not called after disposal
42+
// Wait until after OnAfterRenderAsync has completed to test the disposal of the jsModule
43+
var notFailingGrid = testComponent.NotFailingGrid;
44+
await notFailingGrid.OnAfterRenderCompleted;
45+
46+
// Assert that init was not called after disposal and JsModule was disposed of
4347
Assert.False(testJsRuntime.InitWasCalledAfterDisposal,
4448
"Init should not be called on a disposed component.");
45-
await Task.Yield();
4649
Assert.True(testJsRuntime.JsModuleDisposed);
4750
}
4851

4952
[Fact]
50-
public async Task FailingQuickGrid_CallsInitAfterDisposal_DemonstratesRaceCondition()
53+
public async Task FailingQuickGridCallsInitAfterDisposal()
5154
{
5255
var moduleLoadCompletion = new TaskCompletionSource();
5356
var moduleImportStarted = new TaskCompletionSource();
@@ -69,21 +72,17 @@ public async Task FailingQuickGrid_CallsInitAfterDisposal_DemonstratesRaceCondit
6972
testJsRuntime.MarkDisposed();
7073
await renderer.DisposeAsync();
7174

72-
// Verify our FailingQuickGrid's DisposeAsync was called and _disposeBool should still be false
73-
var failingGrid = testComponent.FailingQuickGrid;
74-
Assert.NotNull(failingGrid);
75-
Assert.True(failingGrid.DisposeAsyncWasCalled, "FailingQuickGrid.DisposeAsync should have been called");
76-
Assert.True(failingGrid.IsWasDisposedFalse(), "_wasDisposed should still be false since we didn't call base.DisposeAsync()");
77-
7875
// Complete the JS module loading - this allows the FailingQuickGrid's OnAfterRenderAsync to continue
7976
// and demonstrate the race condition by calling init after disposal
8077
moduleLoadCompletion.SetResult();
8178

8279
// Wait for OnAfterRenderAsync to complete - deterministic timing instead of arbitrary delay
80+
var failingGrid = testComponent.FailingQuickGrid;
8381
await failingGrid.OnAfterRenderCompleted;
8482

85-
// Assert that init WAS called after disposal (demonstrating the race condition bug)
83+
// Assert that init WAS called after disposal
8684
// The FailingQuickGrid's OnAfterRenderAsync should have called init despite being disposed
85+
// The FailingQuickGrid should not have disposed of JsModule
8786
Assert.True(testJsRuntime.InitWasCalledAfterDisposal,
8887
$"FailingQuickGrid should call init after disposal, demonstrating the race condition bug. " +
8988
$"InitWasCalledAfterDisposal: {testJsRuntime.InitWasCalledAfterDisposal}, " +
@@ -122,16 +121,14 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
122121
b.AddAttribute(1, "Property", (System.Linq.Expressions.Expression<Func<Person, int>>)(p => p.Id));
123122
b.CloseComponent();
124123
}));
125-
if (typeof(TGrid) != typeof(QuickGrid<Person>))
126-
{
127-
builder.AddComponentReferenceCapture(3, component => _grid = (TGrid)component);
128-
}
124+
builder.AddComponentReferenceCapture(3, component => _grid = (TGrid)component);
129125
builder.CloseComponent();
130126
}
131127
}
132128

133-
internal class SimpleTestComponent : BaseTestComponent<QuickGrid<Person>>
129+
internal class SimpleTestComponent : BaseTestComponent<NotFailingGrid<Person>>
134130
{
131+
public NotFailingGrid<Person> NotFailingGrid => Grid;
135132
}
136133

137134
internal class FailingGridTestComponent : BaseTestComponent<FailingQuickGrid<Person>>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text;
7+
using Microsoft.JSInterop;
8+
9+
namespace Microsoft.AspNetCore.Components.QuickGrid.Tests;
10+
/// <summary>
11+
/// A QuickGrid implementation that uses the same implementation of the basic QuickGrid with the additions of the OnAfterRenderCompleted task.
12+
/// </summary>
13+
/// /// <typeparam name="TGridItem">The type of data represented by each row in the grid.</typeparam>
14+
internal class NotFailingGrid<TGridItem> : QuickGrid<TGridItem>
15+
{
16+
[Inject] private IJSRuntime JS { get; set; } = default!;
17+
18+
private readonly TaskCompletionSource _onAfterRenderCompleted = new();
19+
20+
private bool _completionSignaled;
21+
22+
/// <summary>
23+
/// Task that completes when OnAfterRenderAsync has finished executing.
24+
/// This allows tests to wait deterministically for the race condition to occur.
25+
/// </summary>
26+
public Task OnAfterRenderCompleted => _onAfterRenderCompleted.Task;
27+
28+
protected override async Task OnAfterRenderAsync(bool firstRender)
29+
{
30+
try
31+
{
32+
if (firstRender)
33+
{
34+
await base.OnAfterRenderAsync(firstRender);
35+
_onAfterRenderCompleted.TrySetResult();
36+
_completionSignaled = true;
37+
return;
38+
}
39+
}
40+
finally
41+
{
42+
if (firstRender && _completionSignaled)
43+
{
44+
_onAfterRenderCompleted.TrySetResult();
45+
_completionSignaled = true;
46+
}
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)