Skip to content

Commit b6574ac

Browse files
committed
fix: AssertNoUnhandledExceptions, SetDirectParameters, and DisposeComponents blocking concurrent render tree updates
1 parent 70c1338 commit b6574ac

1 file changed

Lines changed: 53 additions & 44 deletions

File tree

src/bunit.core/Rendering/TestRenderer.cs

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -165,55 +165,58 @@ public void DisposeComponents()
165165
if (disposed)
166166
throw new ObjectDisposedException(nameof(TestRenderer));
167167

168-
// The dispatcher will always return a completed task,
169-
// when dealing with an IAsyncDisposable.
170-
// Therefore checking for a completed task and awaiting it
171-
// will only work on IDisposable
172-
var disposeTask = Dispatcher.InvokeAsync(() =>
168+
lock (renderTreeUpdateLock)
173169
{
174-
ResetUnhandledException();
175-
176-
foreach (var root in rootComponents)
170+
// The dispatcher will always return a completed task,
171+
// when dealing with an IAsyncDisposable.
172+
// Therefore checking for a completed task and awaiting it
173+
// will only work on IDisposable
174+
Dispatcher.InvokeAsync(() =>
177175
{
178-
root.Detach();
179-
}
180-
});
176+
ResetUnhandledException();
181177

182-
if (!disposeTask.IsCompleted)
183-
{
184-
disposeTask.GetAwaiter().GetResult();
185-
}
178+
foreach (var root in rootComponents)
179+
{
180+
root.Detach();
181+
}
182+
});
186183

187-
rootComponents.Clear();
188-
AssertNoUnhandledExceptions();
184+
rootComponents.Clear();
185+
AssertNoUnhandledExceptions();
186+
}
189187
}
190188

191189
/// <inheritdoc/>
192190
internal void SetDirectParameters(IRenderedFragmentBase renderedComponent, ParameterView parameters)
193191
{
194-
Dispatcher.InvokeAsync(() =>
192+
// Calling SetDirectParameters updates the render tree
193+
// if the event contains associated data.
194+
lock (renderTreeUpdateLock)
195195
{
196-
try
196+
Dispatcher.InvokeAsync(() =>
197197
{
198-
IsBatchInProgress = true;
198+
try
199+
{
200+
IsBatchInProgress = true;
199201

200-
var componentState = GetRequiredComponentStateMethod.Invoke(this, new object[] { renderedComponent.ComponentId })!;
201-
var setDirectParametersMethod = componentState.GetType().GetMethod("SetDirectParameters", BindingFlags.Public | BindingFlags.Instance)!;
202-
setDirectParametersMethod.Invoke(componentState, new object[] { parameters });
203-
}
204-
catch (TargetInvocationException ex) when (ex.InnerException is not null)
205-
{
206-
HandleException(ex.InnerException);
207-
}
208-
finally
209-
{
210-
IsBatchInProgress = false;
211-
}
202+
var componentState = GetRequiredComponentStateMethod.Invoke(this, new object[] { renderedComponent.ComponentId })!;
203+
var setDirectParametersMethod = componentState.GetType().GetMethod("SetDirectParameters", BindingFlags.Public | BindingFlags.Instance)!;
204+
setDirectParametersMethod.Invoke(componentState, new object[] { parameters });
205+
}
206+
catch (TargetInvocationException ex) when (ex.InnerException is not null)
207+
{
208+
HandleException(ex.InnerException);
209+
}
210+
finally
211+
{
212+
IsBatchInProgress = false;
213+
}
212214

213-
ProcessPendingRender();
214-
});
215+
ProcessPendingRender();
216+
});
215217

216-
AssertNoUnhandledExceptions();
218+
AssertNoUnhandledExceptions();
219+
}
217220
}
218221

219222
/// <inheritdoc/>
@@ -508,17 +511,23 @@ private void ResetUnhandledException()
508511

509512
private void AssertNoUnhandledExceptions()
510513
{
511-
if (capturedUnhandledException is Exception unhandled && !disposed)
514+
// Ensure we are not throwing an exception while a render is ongoing.
515+
// This could lead to the renderer being disposed which could lead to
516+
// tests failing that should not be failing.
517+
lock (renderTreeUpdateLock)
512518
{
513-
capturedUnhandledException = null;
514-
515-
if (unhandled is AggregateException aggregateException && aggregateException.InnerExceptions.Count == 1)
516-
{
517-
ExceptionDispatchInfo.Capture(aggregateException.InnerExceptions[0]).Throw();
518-
}
519-
else
519+
if (capturedUnhandledException is Exception unhandled && !disposed)
520520
{
521-
ExceptionDispatchInfo.Capture(unhandled).Throw();
521+
capturedUnhandledException = null;
522+
523+
if (unhandled is AggregateException aggregateException && aggregateException.InnerExceptions.Count == 1)
524+
{
525+
ExceptionDispatchInfo.Capture(aggregateException.InnerExceptions[0]).Throw();
526+
}
527+
else
528+
{
529+
ExceptionDispatchInfo.Capture(unhandled).Throw();
530+
}
522531
}
523532
}
524533
}

0 commit comments

Comments
 (0)