Skip to content

Commit 461a5f8

Browse files
jacdavisMackinnonBuck
authored andcommitted
Resolve change token leak in Blazor hot reload (#53750)
Fix of razor hotreload change token leak. This disposes the old change tokens after the ClearCache event or before overwriting. If something goes wrong and this isn't cleared before the next invocation of UpdateEndpoints on the razor data source, clear it and dispose of it then.
1 parent 6c42486 commit 461a5f8

File tree

2 files changed

+21
-1
lines changed

2 files changed

+21
-1
lines changed

src/Components/Endpoints/src/Builder/RazorComponentEndpointDataSource.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal class RazorComponentEndpointDataSource<[DynamicallyAccessedMembers(Comp
2828
private List<Endpoint>? _endpoints;
2929
private CancellationTokenSource _cancellationTokenSource;
3030
private IChangeToken _changeToken;
31+
private IDisposable? _disposable; // THREADING: protected by _lock
3132

3233
// Internal for testing.
3334
internal ComponentApplicationBuilder Builder => _builder;
@@ -45,6 +46,7 @@ public RazorComponentEndpointDataSource(
4546
_renderModeEndpointProviders = renderModeEndpointProviders.ToArray();
4647
_factory = factory;
4748
_hotReloadService = hotReloadService;
49+
HotReloadService.ClearCacheEvent += OnHotReloadClearCache;
4850
DefaultBuilder = new RazorComponentsEndpointConventionBuilder(
4951
_lock,
5052
builder,
@@ -139,12 +141,23 @@ private void UpdateEndpoints()
139141
_cancellationTokenSource = new CancellationTokenSource();
140142
_changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);
141143
oldCancellationTokenSource?.Cancel();
144+
oldCancellationTokenSource?.Dispose();
142145
if (_hotReloadService is { MetadataUpdateSupported : true })
143146
{
144-
ChangeToken.OnChange(_hotReloadService.GetChangeToken, UpdateEndpoints);
147+
_disposable?.Dispose();
148+
_disposable = ChangeToken.OnChange(_hotReloadService.GetChangeToken, UpdateEndpoints);
145149
}
146150
}
147151
}
152+
153+
public void OnHotReloadClearCache(Type[]? types)
154+
{
155+
lock (_lock)
156+
{
157+
_disposable?.Dispose();
158+
_disposable = null;
159+
}
160+
}
148161

149162
public override IChangeToken GetChangeToken()
150163
{

src/Components/Endpoints/src/DependencyInjection/HotReloadService.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public HotReloadService()
1818

1919
private CancellationTokenSource _tokenSource = new();
2020
private static event Action<Type[]?>? UpdateApplicationEvent;
21+
internal static event Action<Type[]?>? ClearCacheEvent;
2122

2223
public bool MetadataUpdateSupported { get; internal set; }
2324

@@ -27,11 +28,17 @@ public static void UpdateApplication(Type[]? changedTypes)
2728
{
2829
UpdateApplicationEvent?.Invoke(changedTypes);
2930
}
31+
32+
public static void ClearCache(Type[]? types)
33+
{
34+
ClearCacheEvent?.Invoke(types);
35+
}
3036

3137
private void NotifyUpdateApplication(Type[]? changedTypes)
3238
{
3339
var current = Interlocked.Exchange(ref _tokenSource, new CancellationTokenSource());
3440
current.Cancel();
41+
current.Dispose();
3542
}
3643

3744
public void Dispose()

0 commit comments

Comments
 (0)