Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3c92179
fix(serialization): statically register native RPC responses
ReubenBond Oct 1, 2026
da8b3b0
fix(codegen): register polymorphic native response dispatch
ReubenBond Oct 1, 2026
85fd6fc
fix(codegen): require explicit dictionary response comparer contracts
ReubenBond Oct 1, 2026
8281c81
fix(codegen): root generated response models in metadata mode
ReubenBond Oct 1, 2026
9628e8e
fix(codegen): define finite native response transport contracts
ReubenBond Oct 1, 2026
2cb20a7
fix(codegen): emit factories for completion-only RPC contracts
ReubenBond Oct 1, 2026
67683c0
fix(serialization): preserve referenced factory contracts and alias p…
ReubenBond Oct 1, 2026
2c97b77
fix(codegen): generate response factories for instance RPC methods
ReubenBond Oct 1, 2026
16b18d2
fix(serialization): preserve complete alias metadata and legacy trave…
ReubenBond Oct 2, 2026
da69737
fix(codegen): close canonical response model construction dependencies
ReubenBond Oct 2, 2026
5c2766d
fix(codegen): reject incomplete custom activator construction roots
ReubenBond Oct 2, 2026
59379b2
fix(codegen): close canonical value and array serializer services
ReubenBond Oct 2, 2026
69eed41
fix(codegen): close canonical tuple and RPC argument construction ser…
ReubenBond Oct 2, 2026
5d0be37
test(codegen): update canonical tuple response registration snapshot
ReubenBond Oct 2, 2026
6620ccd
feat(rpc): generate self-writing copied response holders
ReubenBond Oct 2, 2026
782777f
test(nativeaot): round-trip self-writing primitive responses
ReubenBond Oct 2, 2026
42b494e
fix(rpc): preserve custom selection for generated default graphs
ReubenBond Oct 2, 2026
c26bb16
fix(serialization): use matching metadata for default eligibility
ReubenBond Oct 2, 2026
8518188
test(codegen): refresh canonical default contract snapshots
ReubenBond Oct 2, 2026
07c5d15
fix(codegen): remove generated field-accessor markers
ReubenBond Oct 2, 2026
a7efd45
fix(codegen): close canonical pair construction dependencies
ReubenBond Oct 2, 2026
9bfd7e2
fix(codegen): close RPC constructor service dependencies
ReubenBond Oct 3, 2026
423d0ea
docs(serialization): align missing response diagnostics
ReubenBond Oct 3, 2026
8244d99
fix(codegen): gate concrete RPC factories independently of aliases
ReubenBond Oct 3, 2026
01e829b
fix(codegen): specialize canonical generic constructor contracts
ReubenBond Oct 3, 2026
51ae885
fix(codegen): diagnose aggregate RPC graph admission failures
ReubenBond Oct 3, 2026
4b5836d
fix(messaging): restrict raw response writers to response bodies
ReubenBond Oct 3, 2026
0920a0a
fix(codegen): disambiguate generated RPC response names
ReubenBond Oct 3, 2026
6b476a4
fix(rpc): preserve isolated response ownership through send
ReubenBond Oct 3, 2026
48af43a
fix(serialization): decline external dependencies in inferred graphs
ReubenBond Oct 3, 2026
b0d237d
fix(codegen): release compatibility response leases
ReubenBond Oct 3, 2026
89602d1
fix(rpc): restore post-filter isolation and close generic activators
ReubenBond Oct 5, 2026
cb8a886
fix(nativeaot): integrate rebased response factories and transport
ReubenBond Oct 5, 2026
74fbad9
fix(rpc): retain superseded filter response leases
ReubenBond Oct 6, 2026
b6a66d3
fix(nativeaot): preserve rooted serializer materialization
ReubenBond Oct 6, 2026
65709ca
fix(codegen): integrate runtime-independent RPC registrations
ReubenBond Oct 6, 2026
aec5a50
fix(rpc): preserve host construction and clarify argument admission
ReubenBond Oct 6, 2026
4d856ae
chore(api): regenerate all admitted RPC response surfaces
ReubenBond Oct 6, 2026
4df46fb
fix(serialization): preserve converter priority for inferred defaults
ReubenBond Oct 6, 2026
845d3ae
fix(codegen): bind metadata-only RPC compilations
ReubenBond Oct 6, 2026
586a083
fix(rpc): honor explicit child factories
ReubenBond Oct 6, 2026
7f74fe0
refactor(rpc): consolidate response planning and service resolution
ReubenBond Oct 7, 2026
cef92f2
fix(rpc): release response envelopes after successful remote writes
ReubenBond Oct 7, 2026
57fb0ac
fix(rpc): consume received response envelopes at completion boundaries
ReubenBond Oct 7, 2026
8ad231e
fix(aot): align metadata smoke with generic constraint admission
ReubenBond Oct 7, 2026
8d31c77
refactor(rpc): unify invocation contracts and filter lifetimes
ReubenBond Oct 7, 2026
eed5c64
fix(codegen): retain shared response graph for explicit RPC contracts
ReubenBond Oct 7, 2026
e3cc8d1
fix(rpc): release response bodies on terminal message ownership trans…
ReubenBond Oct 7, 2026
37d97ce
fix(rpc): preserve custom response codec and copier subclasses
ReubenBond Oct 7, 2026
4b8a52d
perf(serialization): avoid allocating on published service lookups
ReubenBond Oct 7, 2026
0eae555
perf(serialization): reuse default admission traversal state
ReubenBond Oct 7, 2026
1ff125c
perf(rpc): resolve response dependencies only during construction
ReubenBond Oct 7, 2026
e139213
refactor(rpc): streamline generated invocation and response pooling
ReubenBond Oct 8, 2026
0d39ed9
fix(serialization): reject abstract constructor constraints on every …
ReubenBond Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docs/site/src/content/docs/implementation/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,52 @@ For a return type marked through <xref:Orleans.Invocation.ReturnValueProxyAttrib

Arguments and result values use normal Orleans.Serialization codecs and copiers. Exceptions are represented by exception responses and rethrown by the caller completion source. A grain call with a `CancellationToken` exposes cancellation through the generated request, allowing the runtime to propagate cooperative cancellation. Void methods set the one-way invocation option in their request base, so no response completion source waits for a result.

### Closed RPC response factories

The runtime invokes <xref:Orleans.Serialization.Invocation.IInvokable.Invoke*> with a shared, provider-owned <xref:Orleans.Serialization.Invocation.InvocationContext> in the ordinary, incoming-filter, and observer paths. Its default implementation copies successful responses using the context's selected response copier. For supported non-generic methods using the built-in task and value-task request bases, generated implementations bind the selected serialization dependencies and rent a concrete, non-generic response holder after copying a mutable result. Completed tasks and value tasks consume their result directly; pending operations use a separate asynchronous completion method. Immutable results retain their existing copy semantics. Isolation completes before incoming filters resume, and the generated path creates one pooled envelope for the successful result. Exception envelopes retain the original exception through filters and are copied at delivery for every invocation.

Incoming filters receive an isolated result and can transform its envelope or nested payload references. The call-filter invoker retains each superseded response until completion, allowing filters to restore an earlier selection and releasing every unselected wrapper exactly once. After the filter chain completes, the runtime copies the selected result for delivery, preserving isolation for both local callers and deferred message serialization. Calls with no incoming filters transfer their invocation-time copy directly to the messaging pipeline. Expired and one-way requests release their owned response without an outgoing copy.

Generated holders implement <xref:Orleans.Serialization.Invocation.IRawResponseWriter>. The message serializer selects their direct writer before runtime-type codec lookup. Primitive holders call the existing static primitive codecs; reference holders use their bound concrete result codec and the message's serialization session. The wire representation retains the result-type header, field zero, end marker, null behavior, cycles, and shared references. Generated and compatibility responses share the pool implementation keyed by their concrete envelope type. Returning a generated holder to its pool clears both its result and its provider-owned factory binding.

The response factory constructor resolves and checks the selected result and response codecs and copiers. Subsequent invocations resolve the provider-owned factory through the existing transactional service cache without repeating those dependency lookups. Factory construction retains serializer initialization, recursive dependency resolution, and graph publication or rollback; a failed graph cannot publish a response factory.

Successful remote write completion releases each response envelope and clears the message's reference to it after recording the send. A transport-write failure retains the envelope for retry or rerouting. Terminal message disposal and body replacement release the owned envelope; serialization failure replaces it with an exception response or releases it on a terminal drop.

On receipt, the callback transfers envelope ownership to <xref:Orleans.Serialization.Invocation.IResponseCompletionSource.Complete*>. Typed completion extracts the result and releases the envelope, preserving the result payload for the caller. Untyped completion transfers a successful envelope to its awaiting consumer; void consumption releases it directly. Outgoing filters retain selected and superseded envelopes through their continuations, and the invocation releases them after extracting the final result or unwinding a failure. Application code calling `Complete` transfers ownership and leaves envelope disposal to the completion pipeline.

The receiving message serializer first consults <xref:Orleans.Serialization.Serializers.CodecProvider.TryGetRawResponseReader*> using the result type from the wire header. Generated <xref:Orleans.Serialization.Invocation.IRawResponseReader> registrations reconstruct a bound holder directly. Compatibility paths retain ordinary response codecs for custom response/payload implementations, custom invokable bases, and unresolved generic contracts. Generated direct factories activate only when the selected result services match the canonical implementations and the response codecs/copiers have the exact canonical types. Custom response subclasses retain their selected raw wire and copying behavior through the compatibility path.

For concrete `Task<TResult>` and `ValueTask<TResult>` method results, the generator emits identical invocation code and closed response registrations for JIT and NativeAOT execution. Provider registration precedence selects the services in both execution modes. The graph uses the concrete result implementations in <xref:Orleans.Serialization.Invocation.PooledResponseCodec`2> and <xref:Orleans.Serialization.Invocation.PooledResponseCopier`2>. Recursive result dependencies resolve with a construction caller after the response implementation has been allocated. Reference payload codecs and copiers preserve payload cycles and shared references, and the pooled response envelope retains the existing field and raw-message encoding.

Every response-returning RPC also registers polymorphic codec and copier dispatch for the non-generic <xref:Orleans.Serialization.Invocation.Response> boundary used by the runtime client, including results supplied entirely by explicit closed contracts. That dispatch selects the closed implementation for the actual response type and preserves the identity of immutable completed and exception responses. The native smoke uses <xref:Orleans.Serialization.DeepCopier`1> with `Response`, matching the runtime's response-copy boundary.

Completed response transport uses the existing generated codec and its canonical singleton activator, restoring <xref:Orleans.Serialization.Invocation.CompletedResponse.Instance> after a round-trip. Interfaces containing only non-generic `Task` or `ValueTask` methods also generate this shared response/completion graph.

The finite response graph supplies successful typed results and completed-response transport, plus immutable exception-envelope copying. Exception transport uses the selected <xref:Orleans.Serialization.Invocation.ExceptionResponse> codec and the exception and `Data` value dependencies. Closed factories provide statically compiled implementations; registered metadata and the existing exception codecs provide the remaining supported serialization contracts.

The generator prepares one response plan for holder naming, graph admission, and factory emission. Complete, partial, and referenced graphs share constructor-service descriptions and a registration emitter, so each dependency edge identifies the service consumed by its factory. Generated graphs register closed generic-argument metadata through <xref:Orleans.Serialization.Configuration.TypeManifestOptions.AddGenericArgumentMetadata*>. Candidate selection validates bound generic constraints before choosing an implementation, using the same validation for inferred defaults and ordinary lookup. Constructor-constrained reference arguments must be concrete types with public parameterless constructors on every target framework; invalid candidates yield to the next applicable registration before generic closure. Managed lookup retains runtime constraint validation for application-selected types whose metadata is supplied at runtime.

Generated invocation fallbacks and runtime delivery boundaries share non-generic envelope copy-and-dispose ownership logic. The selected copier retains its typed or runtime-type dispatch behavior, including immutable exception envelopes and custom copiers that return their input. Raw response transport resolves one reader per result type; <xref:Orleans.Serialization.Invocation.ResponseCodec> adapts compatibility codecs to <xref:Orleans.Serialization.Invocation.IRawResponseReader>, and supported generated readers retain precedence.

These supplemental registrations are emitted unconditionally and are defaults: explicit closed factory registrations take precedence in either configuration order. Inferred parent factories preserve each child's selected service: replacing a child contract selects parent construction through that contract, while parents consuming the explicit contract directly remain eligible. This applies throughout the dependency graph, including concrete canonical services and their codec, copier, and activator aliases. Generated factories and registered metadata use one resolution pipeline, with closed services selected first. Direct-holder factories and raw-reader registrations bind the selected provider implementations in both JIT and native execution. Custom application codec/copier selection uses the compatibility invocation and serialization path.

Generated metadata also supplies static response factories for closed generated result models. These factories construct the model's canonical generated codec and copier using their actual constructor signatures, including closed generic generated activators and available generated activators from referenced assemblies. Source-known arrays, tuples, collections, and surrogate value serializers contribute their closed construction services. Interface contracts retain registered metadata dispatch and propagate their dependency requirements. Reference-assembly construction uses the producer's available constructor contracts and explicitly identified members.

Inferred defaults participate when their complete construction graph uses closed service factories and provider-owned services. Admission compares matching metadata implementation identities, and the closed factories supply the executable services. Automatic metadata construction retains ordinary dependency-injection resolution throughout its call chain. Closed default roots start their own construction transactions, and explicit factories participate in the declared transaction. Constructor dependencies supplied by ordinary dependency-injection registrations select canonical metadata activation before the provider starts a serialization construction transaction. <xref:Orleans.Serialization.Configuration.TypeManifestOptions.AddDefaultSerializerService*> carries these dependency edges, preserving explicit registration priority, canonical service identity, and graph rollback. Default activation uses the existing reference- or value-type activator implementation, and custom activation retains its declared contract. Serializer contexts contribute their complete finite dependency graphs and validate each declared member shape.

The same collector closes source-known argument construction dependencies selected by generated proxy constructors. Reference and value tuples use their existing closed codec and copier implementations with their declared element services. Parameter-only one-way contracts register the required construction services while completion and result contracts also register their response graphs.

`OrleansValidateRpcResponseFactories` enables compile-time validation of the response graph and defaults to the executable project's `PublishAot` setting. Diagnostic `ORLEANS0116` identifies an unresolved generic result, a custom return adapter requiring an explicit response contract, or a result dependency outside the supported finite graph. Applications with runtime-selected generic results register every permitted closed <xref:Orleans.Serialization.Invocation.Response`1> graph explicitly through <xref:Orleans.Serialization.Configuration.TypeManifestOptions.AddSerializer*> and <xref:Orleans.Serialization.Configuration.TypeManifestOptions.AddSerializerService*> in a <xref:Orleans.Serialization.SerializerContext>, and set `OrleansValidateRpcResponseFactories=false` for the project supplying that contract. A missing native response registration reports the closed response type and requested serialization service at lookup.

The combined inferred response graph supports up to 1,024 closed executable types. RPC admission counts the codec/copier construction graph; explicit serializer contexts also validate their separate metadata traversal limits. A larger union produces `ORLEANS0116` in both managed and native builds, even when each method's graph fits individually. The generator retains response holder declarations so the size diagnostic identifies the admission failure directly. Argument admission failures use an argument-specific `ORLEANS0116` message directing the application to register the argument's closed codec, copier, and serialization service dependencies, including for one-way methods.

Dictionary results and dictionary members require an explicit closed registration which preserves the application's comparer contract. A dictionary's comparer is selected per value, so the method's declared result type alone supplies the key/value shape while the registration supplies comparer serialization and copying.

The focused .NET 10 NativeAOT smoke exercises the generated response graph for boolean, integer, and reference results, including recursive factory dependencies and payload identity. Full silo startup and RPC execution additionally require the native support for activation, request serialization, grain references, and runtime metadata. Managed .NET 8 tests exercise JIT compatibility and compiled-reference contracts.

Source: [RPC response factory generation](https://github.com/dotnet/orleans/blob/main/src/Orleans.CodeGenerator/RpcResponseGenerator.cs), [closed serializer factory graphs](https://github.com/dotnet/orleans/blob/main/src/Orleans.CodeGenerator/SerializerFactoryGenerator.cs), and [native response smoke](https://github.com/dotnet/orleans/blob/main/test/Orleans.NativeAotSmoke/RpcResponses.Contracts.cs).

### Request identity and dispatch

Generated request names are implementation details. Their wire identity is a compound alias containing:
Expand Down
1 change: 1 addition & 0 deletions src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ ORLEANS0112 | Usage | Error | Invalid RPC parameter field identifier
ORLEANS0113 | Usage | Warning | CancellationToken parameter is not last
ORLEANS0114 | Usage | Error | Invalid serializer context declaration
ORLEANS0115 | Usage | Error | Unsupported serializer context dependency
ORLEANS0116 | Usage | Error | RPC response requires a closed serializer factory
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ internal static class DiagnosticRuleId
public const string CancellationTokenNotLast = "ORLEANS0113";
public const string InvalidSerializerContext = "ORLEANS0114";
public const string UnsupportedSerializerContextType = "ORLEANS0115";
public const string UnsupportedRpcResponseFactory = "ORLEANS0116";
}
Loading
Loading