Skip to content

Commit e7249c9

Browse files
Ryan Nowakrynowak
Ryan Nowak
authored andcommitted
Terminate circuit on error
Fixes: #11845 See: #12857 for detailed notes.
1 parent 88a3a74 commit e7249c9

27 files changed

+1873
-619
lines changed

src/Components/Components/src/Rendering/ComponentState.cs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,24 @@ public void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment rend
7575
batchBuilder.UpdatedComponentDiffs.Append(diff);
7676
}
7777

78-
public void DisposeInBatch(RenderBatchBuilder batchBuilder)
78+
public bool TryDisposeInBatch(RenderBatchBuilder batchBuilder, out Exception exception)
7979
{
8080
_componentWasDisposed = true;
81+
exception = null;
8182

82-
// TODO: Handle components throwing during dispose. Shouldn't break the whole render batch.
83-
if (Component is IDisposable disposable)
83+
try
8484
{
85-
disposable.Dispose();
85+
if (Component is IDisposable disposable)
86+
{
87+
disposable.Dispose();
88+
}
89+
}
90+
catch (Exception ex)
91+
{
92+
exception = ex;
8693
}
8794

95+
// We don't expect these things to throw.
8896
RenderTreeDiffBuilder.DisposeFrames(batchBuilder, CurrentRenderTree.GetFrames());
8997

9098
if (_hasAnyCascadingParameterSubscriptions)
@@ -93,13 +101,27 @@ public void DisposeInBatch(RenderBatchBuilder batchBuilder)
93101
}
94102

95103
DisposeBuffers();
104+
105+
return exception == null;
96106
}
97107

108+
// Callers expect this method to always return a faulted task.
98109
public Task NotifyRenderCompletedAsync()
99110
{
100111
if (Component is IHandleAfterRender handlerAfterRender)
101112
{
102-
return handlerAfterRender.OnAfterRenderAsync();
113+
try
114+
{
115+
return handlerAfterRender.OnAfterRenderAsync();
116+
}
117+
catch (OperationCanceledException cex)
118+
{
119+
return Task.FromCanceled(cex.CancellationToken);
120+
}
121+
catch (Exception ex)
122+
{
123+
return Task.FromException(ex);
124+
}
103125
}
104126

105127
return Task.CompletedTask;

src/Components/Components/src/Rendering/Renderer.cs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,16 +587,31 @@ private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
587587
Log.RenderingComponent(_logger, componentState);
588588
componentState.RenderIntoBatch(_batchBuilder, renderQueueEntry.RenderFragment);
589589

590+
List<Exception> exceptions = null;
591+
590592
// Process disposal queue now in case it causes further component renders to be enqueued
591593
while (_batchBuilder.ComponentDisposalQueue.Count > 0)
592594
{
593595
var disposeComponentId = _batchBuilder.ComponentDisposalQueue.Dequeue();
594596
var disposeComponentState = GetRequiredComponentState(disposeComponentId);
595597
Log.DisposingComponent(_logger, disposeComponentState);
596-
disposeComponentState.DisposeInBatch(_batchBuilder);
598+
if (!disposeComponentState.TryDisposeInBatch(_batchBuilder, out var exception))
599+
{
600+
exceptions ??= new List<Exception>();
601+
exceptions.Add(exception);
602+
}
597603
_componentStateById.Remove(disposeComponentId);
598604
_batchBuilder.DisposedComponentIds.Append(disposeComponentId);
599605
}
606+
607+
if (exceptions?.Count > 1)
608+
{
609+
HandleException(new AggregateException("Exceptions were encountered while disposing components.", exceptions));
610+
}
611+
else if (exceptions?.Count == 1)
612+
{
613+
HandleException(exceptions[0]);
614+
}
600615
}
601616

602617
private void RemoveEventHandlerIds(ArrayRange<ulong> eventHandlerIds, Task afterTaskIgnoreErrors)
@@ -681,6 +696,9 @@ private void UpdateRenderTreeToMatchClientState(ulong eventHandlerId, EventField
681696
/// <param name="disposing"><see langword="true"/> if this method is being invoked by <see cref="IDisposable.Dispose"/>, otherwise <see langword="false"/>.</param>
682697
protected virtual void Dispose(bool disposing)
683698
{
699+
// It's important that we handle all exceptions here before reporting any of them.
700+
// This way we can dispose all components before an error handler kicks in.
701+
List<Exception> exceptions = null;
684702
foreach (var componentState in _componentStateById.Values)
685703
{
686704
Log.DisposingComponent(_logger, componentState);
@@ -693,11 +711,21 @@ protected virtual void Dispose(bool disposing)
693711
}
694712
catch (Exception exception)
695713
{
696-
HandleException(exception);
714+
exceptions ??= new List<Exception>();
715+
exceptions.Add(exception);
697716
}
698717
}
718+
}
719+
720+
_batchBuilder.Dispose();
699721

700-
_batchBuilder.Dispose();
722+
if (exceptions?.Count > 1)
723+
{
724+
HandleException(new AggregateException("Exceptions were encountered while disposing components.", exceptions));
725+
}
726+
else if (exceptions?.Count == 1)
727+
{
728+
HandleException(exceptions[0]);
701729
}
702730
}
703731

0 commit comments

Comments
 (0)