Skip to content

Implement hot reload support with cache clearing and registry reset#39

Open
koenbeuk wants to merge 1 commit intomainfrom
feat/hot-reload-support
Open

Implement hot reload support with cache clearing and registry reset#39
koenbeuk wants to merge 1 commit intomainfrom
feat/hot-reload-support

Conversation

@koenbeuk
Copy link
Copy Markdown
Collaborator

Introduce functionality for hot reload that includes cache clearing and a registry reset mechanism. This enhancement allows for dynamic updates to expression trees without requiring a full application restart.

Copilot AI review requested due to automatic review settings April 17, 2026 01:49
Comment on lines +36 to +39
catch
{
continue;
}
if (reset is null) continue;

try { reset.Invoke(null, null); }
catch { /* best-effort; stale registry stays stale */ }
Comment on lines +27 to +46
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.IsDynamic) continue;

Type? registryType;
try
{
registryType = assembly.GetType("ExpressiveSharp.Generated.ExpressionRegistry", throwOnError: false);
}
catch
{
continue;
}

var reset = registryType?.GetMethod("ResetMap", BindingFlags.Static | BindingFlags.NonPublic);
if (reset is null) continue;

try { reset.Invoke(null, null); }
catch { /* best-effort; stale registry stays stale */ }
}
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 17, 2026

Codecov Report

❌ Patch coverage is 88.88889% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...essiveSharp/Services/ExpressiveHotReloadHandler.cs 81.25% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds hot reload support to ExpressiveSharp by wiring a .NET MetadataUpdateHandler that clears runtime caches and forces generated expression registries to rebuild after metadata updates, enabling updated [Expressive] bodies to propagate without restarting the app.

Changes:

  • Add ExpressiveHotReloadHandler (assembly-registered) to reset generated registries and clear resolver/replacer caches on hot reload.
  • Update generated ExpressionRegistry shape to support a ResetMap() entry point (and adjust generator baselines accordingly).
  • Add unit tests for cache clearing behavior and handler registration; document hot reload support in the README.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/ExpressiveSharp.Tests/Services/ExpressiveHotReloadHandlerTests.cs Adds unit tests validating cache clearing and MetadataUpdateHandler registration.
tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/RegistryTests.SingleProperty_RegistryContainsEntry.verified.txt Updates verified generator output to reflect mutable _map and ResetMap().
tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/RegistryTests.SingleMethod_RegistryContainsEntry.verified.txt Updates verified generator output to reflect mutable _map and ResetMap().
tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/RegistryTests.MultipleExpressives_AllRegistered.verified.txt Updates verified generator output to reflect mutable _map and ResetMap().
tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/RegistryTests.MethodOverloads_BothRegistered.verified.txt Updates verified generator output to reflect mutable _map and ResetMap().
src/ExpressiveSharp/Services/ExpressiveResolver.cs Adds a hot-reload-specific cache invalidation path while preserving scan filter/type name cache; exposes test hooks.
src/ExpressiveSharp/Services/ExpressiveReplacer.cs Adds hot-reload cache invalidation for compiler-generated-closure detection cache.
src/ExpressiveSharp/Services/ExpressiveHotReloadHandler.cs Introduces MetadataUpdateHandler implementation that resets generated registries and clears caches.
src/ExpressiveSharp.Generator/Registry/ExpressionRegistryEmitter.cs Emits mutable registry map and ResetMap() method for hot reload rebuilds.
README.md Documents hot reload compatibility (dotnet watch).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +154 to +156
writer.WriteLine("private static Dictionary<nint, LambdaExpression> _map = Build();");
writer.WriteLine();
writer.WriteLine("internal static void ResetMap() => _map = Build();");
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExpressionRegistry’s _map is now reassigned at runtime via ResetMap(), but the generated code publishes the new Dictionary with a plain assignment. If lookups can run concurrently with hot reload, this risks unsafe publication under the .NET memory model (another thread may observe a partially-initialized dictionary instance). Generate ResetMap using Volatile.Write/Interlocked.Exchange and have TryGet read the field via Volatile.Read (or copy to a local via Volatile.Read) before calling TryGetValue.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +16
public static void ClearCache(Type[]? updatedTypes)
{
ResetGeneratedRegistries();
ExpressiveResolver.ClearCachesForMetadataUpdate();
ExpressiveReplacer.ClearCachesForMetadataUpdate();
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClearCache ignores the updatedTypes payload and instead scans every loaded assembly on each hot reload. In larger apps this can be noticeably expensive (and can repeatedly hit reflection exceptions), even when only one project/type changed. Consider using updatedTypes (when non-null/non-empty) to derive the small set of affected assemblies and only attempt registry resets for those, falling back to a full scan only when the runtime doesn’t provide updated types.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'ExpressiveSharp Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.

Benchmark suite Current: 984628e Previous: ad1718a Ratio
ExpressiveSharp.Benchmarks.GeneratorBenchmarks.RunGenerator_Incremental_NoiseChange(ExpressiveCount: 1) 54020.761697622445 ns (± 455.1528365685914) 35737.250479239 ns (± 381.0304886918274) 1.51

This comment was automatically generated by workflow using github-action-benchmark.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants