Skip to content

Commit 3144cf8

Browse files
committed
fix: AssertNoUnhandledExceptions, SetDirectParameters, and DisposeComponents blocking concurrent render tree updates
1 parent 56bcb8a commit 3144cf8

File tree

1 file changed

+53
-44
lines changed

1 file changed

+53
-44
lines changed

src/bunit.core/Rendering/TestRenderer.cs

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

158-
// The dispatcher will always return a completed task,
159-
// when dealing with an IAsyncDisposable.
160-
// Therefore checking for a completed task and awaiting it
161-
// will only work on IDisposable
162-
var disposeTask = Dispatcher.InvokeAsync(() =>
158+
lock (renderTreeUpdateLock)
163159
{
164-
ResetUnhandledException();
165-
166-
foreach (var root in rootComponents)
160+
// The dispatcher will always return a completed task,
161+
// when dealing with an IAsyncDisposable.
162+
// Therefore checking for a completed task and awaiting it
163+
// will only work on IDisposable
164+
Dispatcher.InvokeAsync(() =>
167165
{
168-
root.Detach();
169-
}
170-
});
166+
ResetUnhandledException();
171167

172-
if (!disposeTask.IsCompleted)
173-
{
174-
disposeTask.GetAwaiter().GetResult();
175-
}
168+
foreach (var root in rootComponents)
169+
{
170+
root.Detach();
171+
}
172+
});
176173

177-
rootComponents.Clear();
178-
AssertNoUnhandledExceptions();
174+
rootComponents.Clear();
175+
AssertNoUnhandledExceptions();
176+
}
179177
}
180178

181179
/// <inheritdoc/>
182180
internal void SetDirectParameters(IRenderedFragmentBase renderedComponent, ParameterView parameters)
183181
{
184-
Dispatcher.InvokeAsync(() =>
182+
// Calling SetDirectParameters updates the render tree
183+
// if the event contains associated data.
184+
lock (renderTreeUpdateLock)
185185
{
186-
try
186+
Dispatcher.InvokeAsync(() =>
187187
{
188-
IsBatchInProgress = true;
188+
try
189+
{
190+
IsBatchInProgress = true;
189191

190-
var componentState = GetRequiredComponentStateMethod.Invoke(this, new object[] { renderedComponent.ComponentId })!;
191-
var setDirectParametersMethod = componentState.GetType().GetMethod("SetDirectParameters", BindingFlags.Public | BindingFlags.Instance)!;
192-
setDirectParametersMethod.Invoke(componentState, new object[] { parameters });
193-
}
194-
catch (TargetInvocationException ex) when (ex.InnerException is not null)
195-
{
196-
HandleException(ex.InnerException);
197-
}
198-
finally
199-
{
200-
IsBatchInProgress = false;
201-
}
192+
var componentState = GetRequiredComponentStateMethod.Invoke(this, new object[] { renderedComponent.ComponentId })!;
193+
var setDirectParametersMethod = componentState.GetType().GetMethod("SetDirectParameters", BindingFlags.Public | BindingFlags.Instance)!;
194+
setDirectParametersMethod.Invoke(componentState, new object[] { parameters });
195+
}
196+
catch (TargetInvocationException ex) when (ex.InnerException is not null)
197+
{
198+
HandleException(ex.InnerException);
199+
}
200+
finally
201+
{
202+
IsBatchInProgress = false;
203+
}
202204

203-
ProcessPendingRender();
204-
});
205+
ProcessPendingRender();
206+
});
205207

206-
AssertNoUnhandledExceptions();
208+
AssertNoUnhandledExceptions();
209+
}
207210
}
208211

209212
/// <inheritdoc/>
@@ -498,17 +501,23 @@ private void ResetUnhandledException()
498501

499502
private void AssertNoUnhandledExceptions()
500503
{
501-
if (capturedUnhandledException is Exception unhandled && !disposed)
504+
// Ensure we are not throwing an exception while a render is ongoing.
505+
// This could lead to the renderer being disposed which could lead to
506+
// tests failing that should not be failing.
507+
lock (renderTreeUpdateLock)
502508
{
503-
capturedUnhandledException = null;
504-
505-
if (unhandled is AggregateException aggregateException && aggregateException.InnerExceptions.Count == 1)
506-
{
507-
ExceptionDispatchInfo.Capture(aggregateException.InnerExceptions[0]).Throw();
508-
}
509-
else
509+
if (capturedUnhandledException is Exception unhandled && !disposed)
510510
{
511-
ExceptionDispatchInfo.Capture(unhandled).Throw();
511+
capturedUnhandledException = null;
512+
513+
if (unhandled is AggregateException aggregateException && aggregateException.InnerExceptions.Count == 1)
514+
{
515+
ExceptionDispatchInfo.Capture(aggregateException.InnerExceptions[0]).Throw();
516+
}
517+
else
518+
{
519+
ExceptionDispatchInfo.Capture(unhandled).Throw();
520+
}
512521
}
513522
}
514523
}

0 commit comments

Comments
 (0)