You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(serialization): generate explicit closed serializer contexts for NativeAOT - #11382
Runtime generic construction cannot reliably provide native code for every closed serialization shape, particularly nested collections with value-type arguments. Applications need compile-time registrations which make codec and copier dependencies visible to the native compiler, alongside the serializer's existing metadata-based capabilities.
Solution
Add [GenerateSerializerContext<List<Dictionary<string, int>>>] on a partial SerializerContext, with AddSerializerContext service/builder registration. The generic attribute declares its root through its type argument using C# 11 or later, so the compiler enforces closed attribute arguments. Multiple attributes and partial declarations contribute to one generated graph. The generator follows serialized members and supported collection dependencies, constructs closed concrete implementations from the existing Orleans serializer pipeline, and retains aliases, type identifiers, reference tracking, and deep-copy semantics.
JIT and NativeAOT use one generated-first resolution pipeline: closed factories and canonical caches take priority, followed by registered implementation metadata and dependency injection. The common type resolver caches declared closed types and resolves additional names through preserved reflection metadata. Existing type-name filters and component validation apply throughout. Context registration composes with ordinary serializer registration.
Generated struct value serializers alias the same canonical field codec. Generic model factories include the closed array implementation services requested by their generated code, preserving optimized direct byte-array dispatch. Referenced codec and copier implementations supply their actual constructor and service-member metadata, including private fields and generated service properties, to determine the closed dependency graph. NativeAOT implementations use direct member access or static accessors; JIT applications also support generated dynamic accessors.
Closed service construction publishes an entire completed dependency graph atomically. Nested activation and generic-materialization failures fault the graph even when an outer constructor catches them; pending automatic cache entries roll back with the graph. Recursive dependencies retain provider-owned partially constructed instances. Optional copiers query and cache completed shallow-copy results during operations, preserving identity while bounding work across shared dependency graphs. Previously completed services remain reusable after failure.
During construction, dependencies resolve through provider-owned services and explicit TypeManifestOptions.AddSerializerService factories. External dependencies are supplied through closed constructor arguments or factories returning captured instances. External DI lookups fault a pending graph with registration guidance, keeping dependencies stable across separately built providers and mutable source collections. Outside construction, the stable facade delegates ordinary and keyed resolution to the actual built provider, preserving its registration snapshot, selection rules, and lifetimes.
Scope and rationale
NativeAOT publication targets .NET 10 and later. Support includes List<Dictionary<string, int>>, primitive leaves, generated class/struct/enum models, nullable values, zero-based arrays, and referenced generated models provided through implementation metadata. Closed codec graphs are bounded to 1,024 types. Separate metadata traversal follows generic arguments, array elements, implemented interfaces, and declaring types with limits of 1,024 closed types and 128 nested dependencies; visited types terminate finite cycles. Dictionary context registrations support the default equality comparer and report custom-comparer inputs explicitly. Ordinary activation selects the existing comparer-aware dictionary constructor. Managed array fallbacks retain multidimensional serialization and non-zero-bound copying through their preserved constructors.
NativeAOT reflection activation and generic materialization operate on preserved constructors and available closed native implementations. Contexts make the required closed implementations explicit to the compiler. Unsupported runtime instantiations and services produce meaningful errors. Narrow metadata/backend annotations describe these preservation boundaries; an internal polyfill retains the relevant annotations in the netstandard asset.
Generated API surfaces, compiled documentation, focused source/runtime contracts, and strict NativeAOT Contexts/Factories discovery manifests integrate with the centralized smoke matrix. The reusable static-factory infrastructure also supports the RPC-response work in #11379, with context discovery and documentation owned by this PR.
Adds compile-time serializer contexts to Orleans so NativeAOT can compile closed codec and copier dependency graphs without runtime generic construction.
Changes:
Adds context generation, registration APIs, and explicit type resolution.
Introduces transactional service construction and a generated-only feature switch.
Adds documentation, runtime contracts, and NativeAOT smoke scenarios.
For a supported Payload<T> with an [Id(0)] T[] member, a context rooted at Payload<byte> registers only ByteArrayCodec/ByteArrayCopier for the closed member type. The canonical generic implementation instead requests ArrayCodec<byte>/ArrayCopier<byte>, so resolving the model throws CodecNotFoundException. Register the concrete dependencies required by the generic-definition implementation, including their element services, in addition to the specialized closed-type implementations. Preserve the specialized byte[] wire format and add a Payload<byte> round-trip/copy regression test.
Caught automatic child failures do not roll back transactions
Automatic constructor failures here never set the active transaction's _constructionFailure; only exceptions escaping a registered factory do. In ordinary mode, an outer closed factory can catch an automatically activated child's failure and return successfully. Its incomplete root and any pending automatic dependencies then commit instead of rolling back. Record the failure before rethrowing from the generic helper, the non-generic CodecProvider activation path, and DI construction. Add a regression test where an automatic child caches a dependency, throws, and the outer factory catches the exception.
Referenced models are described using the consumer's GenerateFieldIds setting. A referenced implementation assembly built with OrleansGenerateFieldIds=PublicProperties can contain valid serialized auto-properties without [Id] attributes. A default-configured consumer treats those members as unassigned and reports ORLEANS0115, instead of traversing the producer's serialized dependencies. Preserve and consume the producer's effective member-selection metadata, and add a cross-project test with different field-ID settings. Explicit member IDs or a per-model field-ID setting can serve as a workaround.
The newest successful coverage run tested 6bf11ad, not current main 1cc211b.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Runtime generic construction cannot reliably provide native code for every closed serialization shape, particularly nested collections with value-type arguments. Applications need compile-time registrations which make codec and copier dependencies visible to the native compiler, alongside the serializer's existing metadata-based capabilities.
Solution
Add
[GenerateSerializerContext<List<Dictionary<string, int>>>]on a partialSerializerContext, withAddSerializerContextservice/builder registration. The generic attribute declares its root through its type argument using C# 11 or later, so the compiler enforces closed attribute arguments. Multiple attributes and partial declarations contribute to one generated graph. The generator follows serialized members and supported collection dependencies, constructs closed concrete implementations from the existing Orleans serializer pipeline, and retains aliases, type identifiers, reference tracking, and deep-copy semantics.JIT and NativeAOT use one generated-first resolution pipeline: closed factories and canonical caches take priority, followed by registered implementation metadata and dependency injection. The common type resolver caches declared closed types and resolves additional names through preserved reflection metadata. Existing type-name filters and component validation apply throughout. Context registration composes with ordinary serializer registration.
Generated struct value serializers alias the same canonical field codec. Generic model factories include the closed array implementation services requested by their generated code, preserving optimized direct byte-array dispatch. Referenced codec and copier implementations supply their actual constructor and service-member metadata, including private fields and generated service properties, to determine the closed dependency graph. NativeAOT implementations use direct member access or static accessors; JIT applications also support generated dynamic accessors.
Closed service construction publishes an entire completed dependency graph atomically. Nested activation and generic-materialization failures fault the graph even when an outer constructor catches them; pending automatic cache entries roll back with the graph. Recursive dependencies retain provider-owned partially constructed instances. Optional copiers query and cache completed shallow-copy results during operations, preserving identity while bounding work across shared dependency graphs. Previously completed services remain reusable after failure.
During construction, dependencies resolve through provider-owned services and explicit
TypeManifestOptions.AddSerializerServicefactories. External dependencies are supplied through closed constructor arguments or factories returning captured instances. External DI lookups fault a pending graph with registration guidance, keeping dependencies stable across separately built providers and mutable source collections. Outside construction, the stable facade delegates ordinary and keyed resolution to the actual built provider, preserving its registration snapshot, selection rules, and lifetimes.Scope and rationale
NativeAOT publication targets .NET 10 and later. Support includes
List<Dictionary<string, int>>, primitive leaves, generated class/struct/enum models, nullable values, zero-based arrays, and referenced generated models provided through implementation metadata. Closed codec graphs are bounded to 1,024 types. Separate metadata traversal follows generic arguments, array elements, implemented interfaces, and declaring types with limits of 1,024 closed types and 128 nested dependencies; visited types terminate finite cycles. Dictionary context registrations support the default equality comparer and report custom-comparer inputs explicitly. Ordinary activation selects the existing comparer-aware dictionary constructor. Managed array fallbacks retain multidimensional serialization and non-zero-bound copying through their preserved constructors.NativeAOT reflection activation and generic materialization operate on preserved constructors and available closed native implementations. Contexts make the required closed implementations explicit to the compiler. Unsupported runtime instantiations and services produce meaningful errors. Narrow metadata/backend annotations describe these preservation boundaries; an internal polyfill retains the relevant annotations in the netstandard asset.
Generated API surfaces, compiled documentation, focused source/runtime contracts, and strict NativeAOT
Contexts/Factoriesdiscovery manifests integrate with the centralized smoke matrix. The reusable static-factory infrastructure also supports the RPC-response work in #11379, with context discovery and documentation owned by this PR.Microsoft Reviewers: Open in CodeFlow