Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Several refreshed/swap-updated maps/manifests are published with Volatile.Write (or plain assignment) but read without volatile semantics, risking stale reads across threads after hot reload.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR adds runtime support for refreshing Orleans’ generated type/grain manifests after a .NET Hot Reload metadata update, so newly added [GenerateSerializer] types, grain interfaces, and grain classes become usable without restarting silos/clients.
Changes:
- Introduces a
MetadataUpdateHandler-driven refresh pipeline with per-container participants (serialization first, then grain metadata) and opt-in registration whenMetadataUpdater.IsSupported. - Rebuilds/merges manifest-derived state (codecs, type-name allow/alias maps, well-known types, grain class maps, RPC proxy mappings, and local cluster/client manifests).
- Adds unit + end-to-end cluster tests covering refresh behavior and idempotency.
File summaries
| File | Description |
|---|---|
| test/Orleans.Serialization.UnitTests/HotReloadScenarioTypes.cs | Adds serializer scenario types used by refresh tests. |
| test/Orleans.Serialization.UnitTests/HotReloadRefreshTests.cs | Adds unit tests simulating refresh restoring serializer/type-name behavior. |
| test/Orleans.DefaultCluster.Tests/HotReloadGrainTests.cs | Adds an end-to-end test validating newly added grains become callable after refresh. |
| src/Orleans.Serialization/TypeSystem/TypeConverter.cs | Adds manifest re-application + negative-cache purge on refresh. |
| src/Orleans.Serialization/TypeSystem/TypeCodec.cs | Adds cache clearing for formatted type-name/type-key caches after refresh. |
| src/Orleans.Serialization/TypeSystem/CompoundTypeAliasTree.cs | Adds copy-on-write merge support for compound type aliases. |
| src/Orleans.Serialization/Session/WellKnownTypeCollection.cs | Rebuilds well-known type id maps on manifest refresh. |
| src/Orleans.Serialization/Serializers/CodecProvider.cs | Rebuilds codec metadata maps and clears caches after refresh; wires refresher construction for hot reload. |
| src/Orleans.Serialization/Orleans.Serialization.csproj | Expands InternalsVisibleTo to enable runtime/client refreshers to call internal refresh hooks. |
| src/Orleans.Serialization/Hosting/ServiceCollectionExtensions.cs | Registers the serialization hot reload refresher when supported. |
| src/Orleans.Serialization/Hosting/SerializationHotReloadRefresher.cs | Implements serialization-layer manifest re-run/merge and derived-cache refresh. |
| src/Orleans.Serialization/Hosting/HotReloadMetadataUpdateHandler.cs | Adds the metadata update handler + participant fan-out infrastructure. |
| src/Orleans.Runtime/Orleans.Runtime.csproj | Adds InternalsVisibleTo for DefaultCluster.Tests. |
| src/Orleans.Runtime/Manifest/SiloManifestProvider.cs | Allows rebuilding silo manifest/grain map after refresh. |
| src/Orleans.Runtime/Manifest/SiloHotReloadRefresher.cs | Adds silo-side refresh participant to rebuild/publish updated grain metadata and clear resolver caches. |
| src/Orleans.Runtime/Manifest/GrainClassMap.cs | Enables in-place grain class map updates after refresh. |
| src/Orleans.Runtime/Manifest/ClusterManifestProvider.cs | Publishes updated local manifest with minor version bump after refresh. |
| src/Orleans.Runtime/Hosting/DefaultSiloServices.cs | Registers silo hot reload refresher when supported. |
| src/Orleans.Runtime/GrainTypeManager/ClusterManifestSystemTarget.cs | Serves the live local manifest (so peers/clients can observe refresh). |
| src/Orleans.Core/Manifest/GrainVersionManifest.cs | Clears version/generic caches on local manifest updates. |
| src/Orleans.Core/Manifest/ClientManifestProvider.cs | Allows client manifest to be rebuilt after refresh. |
| src/Orleans.Core/Manifest/ClientHotReloadRefresher.cs | Adds client-side refresh participant to rebuild/publish local client manifest and clear caches. |
| src/Orleans.Core/Manifest/ClientClusterManifestProvider.cs | Allows replacing the local client manifest after refresh. |
| src/Orleans.Core/GrainReferences/GrainReferenceActivator.cs | Rebuilds the RPC proxy map after refresh. |
| src/Orleans.Core/Core/GrainInterfaceTypeToGrainTypeResolver.cs | Clears generic mapping cache after refresh. |
| src/Orleans.Core/Core/DefaultClientServices.cs | Registers client hot reload refresher when supported. |
| Orleans.slnx | Adds the HotReloadSpike playground project to the solution. |
Review details
- Files reviewed: 27/27 changed files
- Comments generated: 9
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
CI was failing across the matrix because this branch added \playground/HotReloadSpike/HotReloadSpike.csproj\ to \Orleans.slnx\ without including that project, producing MSB3202 before builds/tests could run. Commit \3bdfa41997\ removes the stale solution entry and also addresses all hot-reload publication review feedback. The full solution builds and the focused serialization and grain hot-reload suites pass locally; fresh CI is now running. |
There was a problem hiding this comment.
🔵 Needs a closer look
There are a couple of correctness/build-blocking issues (unnecessary using likely triggering IDE0005, and missing volatile publication semantics for compound alias tree readers) which should be addressed before merging.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/Orleans.Serialization/TypeSystem/CompoundTypeAliasTree.cs:5
MergeFrompublishes_childrenusingVolatile.Write, but_childrenitself is not declaredvolatile, so reads inTryGetChildare not guaranteed to have acquire semantics. Marking_childrenasvolatileensures safe publication of the copy-on-write dictionary on weak memory architectures.
src/Orleans.Serialization/Hosting/HotReloadMetadataUpdateHandler.cs:8using Orleans.Serialization.Hosting;is unnecessary here (the file is already innamespace Orleans.Serialization.Hosting;) and can trigger IDE0005 (unnecessary using) when code style is enforced as errors.
- Files reviewed: 26/26 changed files
- Comments generated: 0 new
- Review effort level: Lite
3bdfa41 to
a334e3c
Compare
There was a problem hiding this comment.
🟡 Changes recommended
At least one new unit test as written cannot pass because its type-name filter permanently denies the target type even after refresh.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
test/Orleans.Serialization.UnitTests/HotReloadRefreshTests.cs:92
ScenarioTypeNameFiltercurrently denies all matching type names permanently, which prevents this test from ever recovering after refresh. Make the filter behavior switchable so the test can simulate a filter policy change (or external allow-list update) across a hot reload batch.
private sealed class ScenarioTypeNameFilter : Orleans.Serialization.ITypeNameFilter
{
public bool? IsTypeNameAllowed(string typeName, string assemblyName)
=> typeName is not null && typeName.Contains(ScenarioNamespaceFragment, StringComparison.Ordinal) ? false : null;
}
- Files reviewed: 26/26 changed files
- Comments generated: 2
- Review effort level: Lite
Code coverage
Report-only conclusion: regressed. The current-main baseline is commit Coverage combines every CI test matrix job, including providers, CodeGen, .NET 8/10, Linux, Windows, and macOS, using canonical physical source and branch identities. The comparison remains report-only while normal line and branch variance is calibrated. Coverage details |
There was a problem hiding this comment.
🔵 Needs a closer look
A confirmed thread-visibility issue in ClusterManifestProvider.OnLocalManifestUpdated can prevent publishing the refreshed manifest under concurrent updates unless _current is read with volatile semantics.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/Orleans.Runtime/Manifest/ClusterManifestProvider.cs:83
- OnLocalManifestUpdated reads the evolving _current manifest using a plain read. Since _current is concurrently updated via Interlocked.Exchange in the AsyncEnumerable onPublished callback, using Volatile.Read here avoids stale reads that can cause TryPublishManifest to repeatedly propose a version which is not actually > the latest published manifest (and therefore fail the updateValidator), potentially preventing the local manifest update from being published.
- Files reviewed: 26/26 changed files
- Comments generated: 0 new
- Review effort level: Lite
87a82f0 to
a2b503a
Compare
There was a problem hiding this comment.
🟡 Changes recommended
There is at least one confirmed compile-time issue in the new cluster test and a confirmed concurrency hazard from mutating shared TypeManifestOptions collections in place during refresh.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 26/26 changed files
- Comments generated: 2
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
The refreshed manifest merge and TypeConverter update path have a correctness gap (compound type alias updates not being observed) plus a merge behavior which can silently overwrite well-known mappings during refresh.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 27/27 changed files
- Comments generated: 2
- Review effort level: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
It introduces broad runtime/serialization manifest refresh behavior with concurrency-sensitive cache publication and cluster/client manifest versioning changes that warrant careful human validation beyond automated review.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/Orleans.Serialization/Hosting/HotReloadMetadataUpdateHandler.cs:8
using Orleans.Serialization.Hosting;is redundant here since the file is already in theOrleans.Serialization.Hostingnamespace, and the directive is unused. Removing it avoids unnecessary imports and potential IDE0005 diagnostics during build.
test/Orleans.DefaultCluster.Tests/HotReloadGrainTests.cs:20- Several
usingdirectives at the top of the file appear to be unused (e.g.,Microsoft.Extensions.Options,Orleans.GrainReferences,Orleans.Metadata,Orleans.Runtime.Versions, and the extra Orleans.Serialization namespaces). Removing them helps keep the test clean and avoids potential build-time style diagnostics.
- Files reviewed: 27/27 changed files
- Comments generated: 0 new
- Review effort level: Lite
8229cd2 to
f5aacf1
Compare
There was a problem hiding this comment.
🟡 Changes recommended
There are warnings-as-errors build risks from unused using directives in newly added/modified files which should be cleaned up before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 27/27 changed files
- Comments generated: 2
- Review effort level: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
The change spans Orleans serialization + runtime manifest publication with concurrency-sensitive copy-on-write behavior, so it warrants final human review despite solid test coverage.
Review details
- Files reviewed: 27/27 changed files
- Comments generated: 0 new
- Review effort level: Lite
1157811 to
5bac50c
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
It changes core runtime/serialization metadata derivation and cross-thread cache publication behavior, which warrants final human review despite strong test coverage.
Review details
- Files reviewed: 27/27 changed files
- Comments generated: 0 new
- Review effort level: Lite
5bac50c to
c95a0f3
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The metadata update handler currently caches “no manifest provider attribute” results in a way which can become stale under hot reload, preventing refresh when an assembly gains the manifest provider attribute.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 27/27 changed files
- Comments generated: 1
- Review effort level: Lite
| var assembly = type.Assembly; | ||
| if (ManifestAssemblyCache.GetOrAdd(assembly, static a => a.IsDefined(typeof(TypeManifestProviderAttribute)))) | ||
| { | ||
| updatedAssemblies.Add(assembly); | ||
| } |
c95a0f3 to
32c54de
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
It changes core runtime/serialization metadata flows and concurrency-visible caches, so it needs final human validation despite only minor review comments.
Review details
- Files reviewed: 27/27 changed files
- Comments generated: 2
- Review effort level: Lite
| namespace DefaultCluster.Tests | ||
| { | ||
| /// <summary> |
| Assert.ThrowsAny<Exception>(() => | ||
| { | ||
| var grain = grainFactory.GetGrain<HotReloadScenario.IHotReloadAddedGrain>("before"); | ||
| grain.Ping().GetAwaiter().GetResult(); | ||
| }); |
32c54de to
fe5f676
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved moderate and critical findings affect refresh ordering, publication reliability, cache consistency, provider activation, and migratability.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (8)
src/Orleans.Core/Manifest/ClientHotReloadRefresher.cs:72
- Writing the new local manifest here is visible to
ClientClusterManifestProvider.RunAsync; if a gateway response is processed concurrently, it can publish a cluster manifest containing the new interfaces before_rpcProvider, the interface resolver, and_grainVersionManifesthave been refreshed. That creates a transient window where callers see metadata newer than the client caches. Update the dependent caches first and publish the local client manifest last.
_clientClusterManifestProvider.OnLocalManifestUpdated(_clientManifestProvider.ClientManifest);
src/Orleans.Runtime/Manifest/SiloHotReloadRefresher.cs:74
- Publishing the refreshed provider does not update
ManagementGrain, which captures bothLocalGrainManifestandCurrentin readonly fields when its activation is created (src/Orleans.Runtime/Core/ManagementGrain.cs:46-47). Any existing management activation will therefore continue rejectingGetActivationAddressand interface checks for grain metadata added by hot reload. Make management operations read the live provider or refresh/recreate those snapshots as part of this update.
_clusterManifestProvider.OnLocalManifestUpdated(_siloManifestProvider.SiloManifest);
src/Orleans.Runtime/Manifest/SiloHotReloadRefresher.cs:77
OnLocalManifestUpdatedmakes the new manifest visible toClusterManifestProviderbefore_rpcProvider,_grainInterfaceTypeToGrainTypeResolver, and_grainVersionManifesthave been refreshed. A concurrent request (or a peer which observes the new minor version) can therefore use the new manifest while this silo still has the old proxy, interface-resolution, or version state. Refresh these local consumers first and publish the cluster manifest last so the version bump is the visibility boundary.
_clusterManifestProvider.OnLocalManifestUpdated(_siloManifestProvider.SiloManifest);
_rpcProvider.OnManifestUpdated();
_grainInterfaceTypeToGrainTypeResolver.OnManifestUpdated();
_grainVersionManifest.OnLocalManifestUpdated(_siloManifestProvider.SiloManifest);
src/Orleans.Serialization/Hosting/SerializationHotReloadRefresher.cs:79
TypeManifestProviderAttributeis also valid for user-authored providers, and the provider-discovery contract keeps those providers out of reflection activation because they are configured through DI (test/Orleans.Core.Tests/ProviderRegistrationResolverTests.cs:81-103). This refresh path callsActivator.CreateInstancefor every attributed provider, so a custom provider with constructor dependencies is silently skipped and a parameterless one is instantiated outside its configured lifetime. Resolve registered providers through the service provider, or restrict reflective activation to generated providers.
if (Activator.CreateInstance(attribute.ProviderType, nonPublic: true) is IConfigureOptions<TypeManifestOptions> provider)
{
providers.Add(provider);
}
src/Orleans.Serialization/Hosting/SerializationHotReloadRefresher.cs:160
- Generated hot-reload assemblies can contain open generic codec types such as
Codec_GenericWithCtor<T>whose mutablegetField_/setField_fields are included by this scan.FieldInfo.SetValuecannot set a field on a declaring type which still contains generic parameters, so the first such field throws and the enclosing catch stops clearing accessors for every remaining generated type in the assembly. Handle generic type definitions separately (or clear through generated helpers) and continue per field/type instead of aborting the whole assembly.
if (field.Name.StartsWith("setField_", StringComparison.Ordinal) || field.Name.StartsWith("getField_", StringComparison.Ordinal))
{
field.SetValue(null, null);
src/Orleans.Serialization/Serializers/CodecProvider.cs:115
- These caches are cleared without coordinating with in-flight
TryGetCodec/copier calls. A call which started before the manifest swap can finish creating anAbstractTypeSerializer(or another fallback) and executeGetOrAddafter this clear, leaving the stale result cached and causing the newly registered generated codec to remain unused until a later refresh. Publish caches with a generation check or synchronize cache creation with manifest refresh so pre-refresh work cannot repopulate the new cache.
_untypedCodecs.Clear();
_typedCodecs.Clear();
_typedBaseCodecs.Clear();
_untypedCopiers.Clear();
_typedCopiers.Clear();
src/Orleans.Serialization/TypeSystem/TypeConverter.cs:170
- An in-flight
Parsecan pass the filter while the type is still absent, then be preempted beforeInspectTypeNameFilterswrites itsfalseverdict. If refresh runs and purges the dictionary during that gap, the old parse resumes and reinserts the negative entry, so the newly manifested type can still be rejected after refresh. Use a copy-on-write/versioned allow-list cache or synchronize verdict writes withOnManifestUpdated.
foreach (var entry in _allowedTypes)
{
if (!entry.Value)
{
_allowedTypes.TryRemove(entry.Key, out _);
src/Orleans.Serialization/TypeSystem/TypeConverter.cs:153
ConsumeMetadata(options)formats newly registered serializer and invokable types, and those generated invokables can use a newCompoundTypeAliasbranch (for example, the alias generated for a newly added grain interface).ResolveCompoundAliasTypestill reads the previous_compoundTypeAliasestree during that call, so it can throwTypeLoadExceptionbefore the refresh publishes the new manifest. Make the new alias tree available to metadata consumption without exposing a partially applied state.
_compoundTypeAliases = options.CompoundTypeAliases;
- Files reviewed: 27/27 changed files
- Comments generated: 3
- Review effort level: Lite
| _siloManifestProvider.OnManifestUpdated(); | ||
| _clusterManifestProvider.OnLocalManifestUpdated(_siloManifestProvider.SiloManifest); |
| for (var attempt = 0; attempt < 3; attempt++) | ||
| { | ||
| var current = _current; | ||
| if (current.Silos.TryGetValue(_localSiloAddress, out var existing) && ReferenceEquals(existing, localManifest)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var version = new MajorMinorVersion(current.Version.Major, current.Version.Minor + 1); | ||
| var updated = CreateClusterManifest(version, current.Silos.SetItem(_localSiloAddress, localManifest)); | ||
| if (TryPublishManifest(updated)) | ||
| { | ||
| return; | ||
| } | ||
| } |
| #if NET6_0_OR_GREATER | ||
| if (System.Reflection.Metadata.MetadataUpdater.IsSupported) | ||
| { | ||
| // Construct the hot reload refresher (if registered) so it subscribes before the first update. | ||
| _ = _serviceProvider.GetService<Hosting.SerializationHotReloadRefresher>(); | ||
| } |
…hot reload manifest provider
fe5f676 to
48556ba
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Ten unresolved moderate findings affect concurrency, cache refresh, manifest publication, and configuration preservation.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (10)
src/Orleans.Core/Core/GrainInterfaceTypeToGrainTypeResolver.cs:42
Clear()can race with the write at the end of an in-flight generic lookup: a call which captured the old cluster manifest can resume after this line and add its old result back into_genericMapping. A subsequent lookup can then use that stale mapping even though the refreshed manifest contains a different implementation. Publish a generation-aware cache or prevent lookups from inserting results from an older manifest.
internal void OnManifestUpdated() => _genericMapping.Clear();
src/Orleans.Core/Manifest/ClientHotReloadRefresher.cs:67
- Calling
DefaultGrainTypeOptionsProvider.Configureon the already-final_grainTypeOptionsalso repopulates interfaces which a client configuration intentionally removed. For example,test/Orleans.Core.Tests/ClientBuilderTests.cs:149-160clearsoptions.Interfacesto reject startup; after this refresh a hot-reload event adds all generated interfaces back, changing the client's configured manifest and proxy surface. Rebuild a scratch options object through the complete configuration pipeline, or otherwise preserve existing exclusions while merging new interfaces.
defaultProvider.Configure(_grainTypeOptions);
src/Orleans.Runtime/Manifest/ClusterManifestProvider.cs:104
- If a membership or peer-repair publication wins all three races, this method simply returns without publishing
localManifest.LocalGrainManifesthas already been replaced, butCurrentstill contains the old local manifest and no later path necessarily retries because the membership major version is unchanged; peers can therefore permanently miss the newly added grain metadata. The update needs a serialized publication/reconciliation path or a retry which cannot silently give up.
// Publish with a retry: a concurrently published manifest (e.g. built from membership processing
// before this update) can win the race while still carrying the previous local manifest.
for (var attempt = 0; attempt < 3; attempt++)
{
var current = _current;
src/Orleans.Runtime/Manifest/SiloHotReloadRefresher.cs:69
- Calling
DefaultGrainTypeOptionsProvider.Configuredirectly on the already-final_grainTypeOptionsrepopulates every generated grain class that startup configuration intentionally removed.GrainTypeOptionsis used for per-silo exclusions such asoptions.Classes.Remove(...)and evenoptions.Classes.Clear()(for example,test/Benchmarks/Ping/PingBenchmark.cs:37-40andtest/Orleans.Core.Tests/SiloBuilderTests.cs:295-304), so any hot-reload update can silently change activation and placement behavior. Rebuild a scratch options object through the complete configuration pipeline, or otherwise merge only newly discovered entries while preserving existing exclusions.
defaultProvider.Configure(_grainTypeOptions);
src/Orleans.Runtime/Manifest/SiloHotReloadRefresher.cs:74
GrainMigratabilityCheckeris a singleton which capturesclusterManifestProvider.LocalGrainManifestat construction and caches the result ofIsImmovable. RefreshingSiloManifestProviderandClusterManifestProviderhere does not update that checker, so a newly added ordinary grain is absent from the captured manifest, classified as immovable, and cached as non-migratable for rebalancing/repartitioning. The refresh must invalidate or update this consumer as well.
_siloManifestProvider.OnManifestUpdated();
_clusterManifestProvider.OnLocalManifestUpdated(_siloManifestProvider.SiloManifest);
src/Orleans.Serialization/Hosting/HotReloadMetadataUpdateHandler.cs:64
- The new tests call
SerializationHotReloadRefresher.RefreshandSiloHotReloadRefresher.Refreshdirectly, soUpdateApplicationitself is never exercised. That leaves the assembly filtering, phase ordering, weak-participant snapshot, and the documented no-throw behavior unverified; add a focused handler test which supplies manifest and non-manifest updates and a throwing participant.
public static void UpdateApplication(Type[]? updatedTypes)
{
try
src/Orleans.Serialization/Hosting/SerializationHotReloadRefresher.cs:18
- This changes the supported restart boundary, but the current guidance in
docs/site/src/content/docs/grains/code-generation.md:45-47still says a restart is required when an update introduces types through polymorphic or container members and does not describe the new grain-interface/class workflow. Update the hot-reload documentation to cover the refreshed manifest behavior and its stated limitations before merging.
/// Refreshes the serialization layer after a .NET Hot Reload update: re-runs the updated assemblies'
/// generated manifest providers, merges the result into the live <see cref="TypeManifestOptions"/>, and
/// refreshes the caches derived from it.
src/Orleans.Serialization/Serializers/CodecProvider.cs:97
- This lookup is the only place a serializer-only container subscribes the refresher, but it runs only from
Initialize, which is reached on the first codec lookup. An application can constructSerializer/CodecProvider, receive a metadata update, and only then perform its first serialization; no participant is registered for that update, so the live manifest is never merged and the newly generated type remains unavailable. The refresher needs to be subscribed before metadata updates, independently of first codec use.
_ = _serviceProvider.GetService<Hosting.SerializationHotReloadRefresher>();
src/Orleans.Serialization/TypeSystem/TypeCodec.cs:43
ConcurrentDictionary.Clear()does not fence an in-flightGetOrAddfactory. A type-name lookup which started before the manifest update can finish after these clears and insert the oldTypeKey, so subsequent writes can continue using the pre-refresh alias/name indefinitely. Cache population needs to be synchronized with refresh or versioned so stale factories cannot repopulate the current cache.
internal void ClearCaches()
{
_typeCache.Clear();
_typeKeyCache.Clear();
src/Orleans.Serialization/TypeSystem/TypeConverter.cs:170
- Removing the current negative entries is not sufficient when parsing can run concurrently with this refresh. A parse which observed the old denying filter can set
_allowedTypes[type] = falseafter this loop has removed it, leaving the stale denial in place and causing the newly allowed type to remain rejected. Coordinate the purge with verdict publication or associate cached verdicts with a manifest generation.
foreach (var entry in _allowedTypes)
{
if (!entry.Value)
{
_allowedTypes.TryRemove(entry.Key, out _);
- Files reviewed: 27/27 changed files
- Comments generated: 2
- Review effort level: Lite
| _siloManifestProvider.OnManifestUpdated(); | ||
| _clusterManifestProvider.OnLocalManifestUpdated(_siloManifestProvider.SiloManifest); | ||
| _rpcProvider.OnManifestUpdated(); | ||
| _grainInterfaceTypeToGrainTypeResolver.OnManifestUpdated(); | ||
| _grainVersionManifest.OnLocalManifestUpdated(_siloManifestProvider.SiloManifest); |
| ConsumeMetadata(metadata); | ||
|
|
||
| _untypedCodecs.Clear(); | ||
| _typedCodecs.Clear(); | ||
| _typedBaseCodecs.Clear(); |
Problem
Everything derived from the generated type manifests is captured once at startup: codec maps, type-name allow lists, the grain class map, RPC proxy mappings, and the silo manifest. A Hot Reload edit regenerates
Metadata_{Assembly}in place, but nothing re-reads it — so a type or grain added while the process runs fails withCodecNotFoundExceptionor an unresolvable grain interface until restart, even though its generated codec and proxy already exist.Solution
MetadataUpdateHandlerthat runs after an update batch is applied, filters to assemblies bearingTypeManifestProviderAttribute, and fans out to per-container refresh participants in two phases; the handler never throws.TypeManifestOptionsand merge additively into the live options, keeping the refresh idempotent.CodecProvidermetadata maps and clear its caches (including cachedAbstractTypeSerializerfallbacks), rebuildTypeConverteralias maps and purge negative type-name verdicts, rebuildWellKnownTypeCollection, and clearTypeCodecand generated accessor caches.ClusterManifestSystemTargetnow serves the live local manifest.MetadataUpdater.IsSupported, keeping production startup and steady state unchanged.With #10932's generation shape (
<OrleansHotReload>true</OrleansHotReload>), adding new[GenerateSerializer]types, grain interfaces, and grain classes now works without a restart in a running silo. Grain interface changes remain rude edits by EnC rules, direct wire serialization of a hot-reload-added type is blocked by a .NET runtime constraint-verification limitation (grain calls are unaffected), and peer silos still learn of manifest changes only through their existing refresh channels — a follow-up will address peer re-fetch.Microsoft Reviewers: Open in CodeFlow