Skip to content

Commit 4e04b81

Browse files
Store event handler IDs as longs. Fixes #12058 (#12305)
* Change event handler IDs to be longs * Update unit tests * Enable detailed errors for test app * CR: Explicitly check if we exceed Number.MAX_SAFE_INTEGER * Update ref assemblies
1 parent 010ffe6 commit 4e04b81

27 files changed

+181
-120
lines changed

src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public partial class WebAssemblyRenderer : Microsoft.AspNetCore.Components.Rende
6262
public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get { throw null; } }
6363
public System.Threading.Tasks.Task AddComponentAsync(System.Type componentType, string domElementSelector) { throw null; }
6464
public System.Threading.Tasks.Task AddComponentAsync<TComponent>(string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
65-
public override System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
65+
public override System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
6666
protected override void Dispose(bool disposing) { }
6767
protected override void HandleException(System.Exception exception) { }
6868
protected override System.Threading.Tasks.Task UpdateDisplayAsync(in Microsoft.AspNetCore.Components.Rendering.RenderBatch batch) { throw null; }

src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ protected override void HandleException(Exception exception)
120120
}
121121

122122
/// <inheritdoc />
123-
public override Task DispatchEventAsync(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
123+
public override Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
124124
{
125125
// Be sure we only run one event handler at once. Although they couldn't run
126126
// simultaneously anyway (there's only one thread), they could run nested on
@@ -183,12 +183,12 @@ private async Task ProcessNextDeferredEventAsync()
183183

184184
readonly struct IncomingEventInfo
185185
{
186-
public readonly int EventHandlerId;
186+
public readonly ulong EventHandlerId;
187187
public readonly EventFieldInfo EventFieldInfo;
188188
public readonly UIEventArgs EventArgs;
189189
public readonly TaskCompletionSource<object> TaskCompletionSource;
190190

191-
public IncomingEventInfo(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
191+
public IncomingEventInfo(ulong eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
192192
{
193193
EventHandlerId = eventHandlerId;
194194
EventFieldInfo = eventFieldInfo;

src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,7 @@ public readonly partial struct RenderBatch
724724
{
725725
private readonly object _dummy;
726726
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<int> DisposedComponentIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
727-
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<int> DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
727+
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<ulong> DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
728728
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame> ReferenceFrames { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
729729
public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiff> UpdatedComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
730730
}
@@ -735,7 +735,7 @@ public Renderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Lo
735735
public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } }
736736
protected internal virtual void AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) { }
737737
protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; }
738-
public virtual System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
738+
public virtual System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
739739
public void Dispose() { }
740740
protected virtual void Dispose(bool disposing) { }
741741
protected abstract void HandleException(System.Exception exception);

src/Components/Components/src/RenderTree/RenderTreeFrame.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
1010
/// <summary>
1111
/// Represents an entry in a tree of user interface (UI) items.
1212
/// </summary>
13-
[StructLayout(LayoutKind.Explicit)]
13+
[StructLayout(LayoutKind.Explicit, Pack = 4)]
1414
public readonly struct RenderTreeFrame
1515
{
1616
// Note that the struct layout has to be valid in both 32-bit and 64-bit runtime platforms,
@@ -90,7 +90,7 @@ public readonly struct RenderTreeFrame
9090
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>
9191
/// gets the ID of the corresponding event handler, if any.
9292
/// </summary>
93-
[FieldOffset(8)] public readonly int AttributeEventHandlerId;
93+
[FieldOffset(8)] public readonly ulong AttributeEventHandlerId;
9494

9595
/// <summary>
9696
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
@@ -267,7 +267,7 @@ private RenderTreeFrame(int sequence, bool isMarkup, string textOrMarkup)
267267
}
268268

269269
// Attribute constructor
270-
private RenderTreeFrame(int sequence, string attributeName, object attributeValue, int attributeEventHandlerId, string attributeEventUpdatesAttributeName)
270+
private RenderTreeFrame(int sequence, string attributeName, object attributeValue, ulong attributeEventHandlerId, string attributeEventUpdatesAttributeName)
271271
: this()
272272
{
273273
FrameType = RenderTreeFrameType.Attribute;
@@ -337,7 +337,7 @@ internal RenderTreeFrame WithAttributeSequence(int sequence)
337337
internal RenderTreeFrame WithComponent(ComponentState componentState)
338338
=> new RenderTreeFrame(Sequence, componentSubtreeLength: ComponentSubtreeLength, ComponentType, componentState, ComponentKey);
339339

340-
internal RenderTreeFrame WithAttributeEventHandlerId(int eventHandlerId)
340+
internal RenderTreeFrame WithAttributeEventHandlerId(ulong eventHandlerId)
341341
=> new RenderTreeFrame(Sequence, attributeName: AttributeName, AttributeValue, eventHandlerId, AttributeEventUpdatesAttributeName);
342342

343343
internal RenderTreeFrame WithAttributeValue(object attributeValue)

src/Components/Components/src/Rendering/RenderBatch.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using Microsoft.AspNetCore.Components.RenderTree;
@@ -30,13 +30,13 @@ public readonly struct RenderBatch
3030
/// <summary>
3131
/// Gets the IDs of the event handlers that were disposed.
3232
/// </summary>
33-
public ArrayRange<int> DisposedEventHandlerIDs { get; }
33+
public ArrayRange<ulong> DisposedEventHandlerIDs { get; }
3434

3535
internal RenderBatch(
3636
ArrayRange<RenderTreeDiff> updatedComponents,
3737
ArrayRange<RenderTreeFrame> referenceFrames,
3838
ArrayRange<int> disposedComponentIDs,
39-
ArrayRange<int> disposedEventHandlerIDs)
39+
ArrayRange<ulong> disposedEventHandlerIDs)
4040
{
4141
UpdatedComponents = updatedComponents;
4242
ReferenceFrames = referenceFrames;

src/Components/Components/src/Rendering/RenderBatchBuilder.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ internal class RenderBatchBuilder : IDisposable
1818
// Primary result data
1919
public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; } = new ArrayBuilder<RenderTreeDiff>();
2020
public ArrayBuilder<int> DisposedComponentIds { get; } = new ArrayBuilder<int>();
21-
public ArrayBuilder<int> DisposedEventHandlerIds { get; } = new ArrayBuilder<int>();
21+
public ArrayBuilder<ulong> DisposedEventHandlerIds { get; } = new ArrayBuilder<ulong>();
2222

2323
// Buffers referenced by UpdatedComponentDiffs
2424
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>(64);

src/Components/Components/src/Rendering/RenderTreeUpdater.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
77
{
88
internal class RenderTreeUpdater
99
{
10-
public static void UpdateToMatchClientState(RenderTreeBuilder renderTreeBuilder, int eventHandlerId, object newFieldValue)
10+
public static void UpdateToMatchClientState(RenderTreeBuilder renderTreeBuilder, ulong eventHandlerId, object newFieldValue)
1111
{
1212
// We only allow the client to supply string or bool currently, since those are the only kinds of
1313
// values we output on attributes that go to the client

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ internal static class Log
2222
private static readonly Action<ILogger, int, Type, Exception> _disposingComponent =
2323
LoggerMessage.Define<int, Type>(LogLevel.Debug, new EventId(4, "DisposingComponent"), "Disposing component {ComponentId} of type {ComponentType}");
2424

25-
private static readonly Action<ILogger, int, string, Exception> _handlingEvent =
26-
LoggerMessage.Define<int, string>(LogLevel.Debug, new EventId(5, "HandlingEvent"), "Handling event {EventId} of type '{EventType}'");
25+
private static readonly Action<ILogger, ulong, string, Exception> _handlingEvent =
26+
LoggerMessage.Define<ulong, string>(LogLevel.Debug, new EventId(5, "HandlingEvent"), "Handling event {EventId} of type '{EventType}'");
2727

2828
public static void InitializingComponent(ILogger logger, ComponentState componentState, ComponentState parentComponentState)
2929
{
@@ -56,7 +56,7 @@ internal static void DisposingComponent(ILogger<Renderer> logger, ComponentState
5656
}
5757
}
5858

59-
internal static void HandlingEvent(ILogger<Renderer> logger, int eventHandlerId, UIEventArgs eventArgs)
59+
internal static void HandlingEvent(ILogger<Renderer> logger, ulong eventHandlerId, UIEventArgs eventArgs)
6060
{
6161
_handlingEvent(logger, eventHandlerId, eventArgs?.Type ?? "null", null);
6262
}

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ public abstract partial class Renderer : IDisposable
2020
private readonly IServiceProvider _serviceProvider;
2121
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
2222
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
23-
private readonly Dictionary<int, EventCallback> _eventBindings = new Dictionary<int, EventCallback>();
24-
private readonly Dictionary<int, int> _eventHandlerIdReplacements = new Dictionary<int, int>();
23+
private readonly Dictionary<ulong, EventCallback> _eventBindings = new Dictionary<ulong, EventCallback>();
24+
private readonly Dictionary<ulong, ulong> _eventHandlerIdReplacements = new Dictionary<ulong, ulong>();
2525
private readonly ILogger<Renderer> _logger;
2626

2727
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
2828
private bool _isBatchInProgress;
29-
private int _lastEventHandlerId = 0;
29+
private ulong _lastEventHandlerId;
3030
private List<Task> _pendingTasks;
3131

3232
/// <summary>
@@ -206,7 +206,7 @@ private ComponentState AttachAndInitComponent(IComponent component, int parentCo
206206
/// A <see cref="Task"/> which will complete once all asynchronous processing related to the event
207207
/// has completed.
208208
/// </returns>
209-
public virtual Task DispatchEventAsync(int eventHandlerId, EventFieldInfo fieldInfo, UIEventArgs eventArgs)
209+
public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, UIEventArgs eventArgs)
210210
{
211211
EnsureSynchronizationContext();
212212

@@ -354,15 +354,15 @@ protected internal virtual void AddToRenderQueue(int componentId, RenderFragment
354354
}
355355
}
356356

357-
internal void TrackReplacedEventHandlerId(int oldEventHandlerId, int newEventHandlerId)
357+
internal void TrackReplacedEventHandlerId(ulong oldEventHandlerId, ulong newEventHandlerId)
358358
{
359359
// Tracking the chain of old->new replacements allows us to interpret incoming EventFieldInfo
360360
// values even if they refer to an event handler ID that's since been superseded. This is essential
361361
// for tree patching to work in an async environment.
362362
_eventHandlerIdReplacements.Add(oldEventHandlerId, newEventHandlerId);
363363
}
364364

365-
private int FindLatestEventHandlerIdInChain(int eventHandlerId)
365+
private ulong FindLatestEventHandlerIdInChain(ulong eventHandlerId)
366366
{
367367
while (_eventHandlerIdReplacements.TryGetValue(eventHandlerId, out var replacementEventHandlerId))
368368
{
@@ -573,7 +573,7 @@ private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
573573
}
574574
}
575575

576-
private void RemoveEventHandlerIds(ArrayRange<int> eventHandlerIds, Task afterTaskIgnoreErrors)
576+
private void RemoveEventHandlerIds(ArrayRange<ulong> eventHandlerIds, Task afterTaskIgnoreErrors)
577577
{
578578
if (eventHandlerIds.Count == 0)
579579
{
@@ -598,7 +598,7 @@ private void RemoveEventHandlerIds(ArrayRange<int> eventHandlerIds, Task afterTa
598598

599599
// Factor out the async part into a separate local method purely so, in the
600600
// synchronous case, there's no state machine or task construction
601-
async Task ContinueAfterTask(ArrayRange<int> eventHandlerIds, Task afterTaskIgnoreErrors)
601+
async Task ContinueAfterTask(ArrayRange<ulong> eventHandlerIds, Task afterTaskIgnoreErrors)
602602
{
603603
// We need to delay the actual removal (e.g., until we've confirmed the client
604604
// has processed the batch and hence can be sure not to reuse the handler IDs
@@ -637,7 +637,7 @@ private async Task GetErrorHandledTask(Task taskToHandle)
637637
}
638638
}
639639

640-
private void UpdateRenderTreeToMatchClientState(int eventHandlerId, EventFieldInfo fieldInfo)
640+
private void UpdateRenderTreeToMatchClientState(ulong eventHandlerId, EventFieldInfo fieldInfo)
641641
{
642642
var componentState = GetOptionalComponentState(fieldInfo.ComponentId);
643643
if (componentState != null)

src/Components/Components/test/RenderTreeDiffBuilderTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ public void RecognizesAttributeEventHandlerValuesChanged()
859859
Assert.Equal(0, entry.ReferenceFrameIndex);
860860
});
861861
AssertFrame.Attribute(referenceFrames[0], "onbar", addedHandler);
862-
Assert.NotEqual(0, removedEventHandlerFrame.AttributeEventHandlerId);
862+
Assert.NotEqual(default, removedEventHandlerFrame.AttributeEventHandlerId);
863863
Assert.Equal(
864864
new[] { removedEventHandlerFrame.AttributeEventHandlerId },
865865
batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
@@ -1592,7 +1592,7 @@ public void PreservesEventHandlerIdsForRetainedEventHandlers()
15921592
Assert.Empty(result.Edits);
15931593
AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler);
15941594
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
1595-
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
1595+
Assert.NotEqual(default, oldAttributeFrame.AttributeEventHandlerId);
15961596
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
15971597
Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
15981598
}
@@ -1619,7 +1619,7 @@ public void PreservesEventHandlerIdsForRetainedEventHandlers_SlowPath()
16191619
Assert.Single(result.Edits);
16201620
AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler);
16211621
AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
1622-
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
1622+
Assert.NotEqual(default, oldAttributeFrame.AttributeEventHandlerId);
16231623
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
16241624
Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
16251625
}

src/Components/Components/test/RendererTest.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3313,7 +3313,7 @@ public void EventFieldInfoCanPatchTreeSoDiffDoesNotUpdateAttribute(string oldVal
33133313
Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type);
33143314
var attributeFrame = batch2.ReferenceFrames[edit.ReferenceFrameIndex];
33153315
AssertFrame.Attribute(attributeFrame, "ontestevent", typeof(Action<UIChangeEventArgs>));
3316-
Assert.NotEqual(0, attributeFrame.AttributeEventHandlerId);
3316+
Assert.NotEqual(default, attributeFrame.AttributeEventHandlerId);
33173317
Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId);
33183318
});
33193319
}
@@ -3365,7 +3365,7 @@ public void EventFieldInfoWorksWhenEventHandlerIdWasSuperseded()
33653365
Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type);
33663366
var attributeFrame = latestBatch.ReferenceFrames[edit.ReferenceFrameIndex];
33673367
AssertFrame.Attribute(attributeFrame, "ontestevent", typeof(Action<UIChangeEventArgs>));
3368-
Assert.NotEqual(0, attributeFrame.AttributeEventHandlerId);
3368+
Assert.NotEqual(default, attributeFrame.AttributeEventHandlerId);
33693369
Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId);
33703370
});
33713371
}

0 commit comments

Comments
 (0)