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
Native RPC response handling selected pooled codecs and copiers by closing open generic implementations at runtime. Generate concrete, non-generic response holders and closed construction graphs for source-known method results and proxy dependencies.
The generator emits identical invocation and registration code for managed and NativeAOT publishing. Closed factories participate through the common provider resolution pipeline, with application registration precedence selecting services in either execution mode. Full emitted-source parity covers result models, collections, primitive responses, completion responses, and explicit serializer contexts.
IInvokable.Invoke(InvocationContext) is the common invocation contract. Runtime clients and observers reuse a context containing their provider-owned serialization services. Generated implementations invoke concrete targets, isolate successful results, and rent concrete holders directly. Completed tasks and value tasks use a synchronous entry; pending results use a concrete asynchronous completion method. Non-generic runtime code supplies compatibility adaptation and envelope release, preserving custom codec/copier selection and same-instance copier results. Response pools share one implementation keyed by the actual envelope type.
Response factory dependencies resolve and undergo compatibility checks during construction. Warmed invocations retain provider-scoped transactional factory resolution, with allocation-free published lookup and reused admission traversal storage.
Exceptions remain original through filters and are copied at delivery. A shared GrainCallInvoker supplies request metadata, filter-stage progression, and response lifetime for incoming and outgoing calls. It retains selected and superseded envelopes through filter continuations, supports restoring earlier selections, and releases owned envelopes exactly once.
Completion takes ownership of its response input. Typed completion extracts the payload and releases the envelope; untyped completion transfers a successful envelope to its consumer. Void consumption and completed outgoing filter chains release their envelopes. Messages release response bodies on successful writes, terminal disposal, and replacement while retaining them during actual retries. Explicit callback handoffs clear the message's ownership before completion.
IRawResponseWriter writes the existing raw protocol directly before the sender queries a response codec. Closed IRawResponseReader registrations reconstruct holders from the wire result type. Direct holders/readers require exact canonical response codec and copier types; custom subclasses preserve their raw wire and copying behavior through compatibility dispatch. Primitive codecs, reference cycles, shared identity, pool reset, completion responses, exceptions, and custom implementation selection retain their established semantics.
Inferred defaults use the existing metadata selector and complete constructor dependency descriptors. Admission validates bound generic constraints before comparing implementation identities, using generated closed argument metadata. Automatic metadata construction retains its ordinary dependency-injection boundary throughout the call chain; inferred default leaves participate in an existing closed transaction or start a transaction as a closed root. Explicit child registrations and selected converter contracts retain precedence throughout canonical codec, copier, and activator aliases.
The combined inferred response graph admits up to 1,024 closed executable types. Aggregate failures report ORLEANS0116 in managed and native builds while retaining holder declarations. Argument failures provide argument-specific guidance, including for one-way contracts. Every response-returning RPC emits the shared non-generic Response graph independently of automatic result admission, allowing explicit-only dictionary and runtime-selected generic result contracts to use runtime response dispatch.
Explicit serializer contexts and inferred RPC response graphs share the graph collector, constructor inspection, provider-owned construction scopes, and metadata registration contracts merged into main. NativeAOT targets .NET 10+, with actual managed .NET 8 compatibility retained. Native coverage uses the shared smoke project and centralized runner.
Draft remains held for fresh CI and final human review.
The current-main baseline is commit 165acd0d33 and uses the same reviewed coverage matrix.
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.
Record canonical service aliases independently of registration order and declare the exact services consumed by inferred factories. Preserve contract-aware parents and custom metadata precedence.
Emit RPC registrations unconditionally and retain managed/native/framework-symbol source parity.
Addressed the previously missed "Explicit child factories do not invalidate inferred parent services" feedback in e572455. Canonical implementation aliases now retain their service identity in either registration order, and generated dependency descriptors name the services each factory actually consumes. Parents which use canonical children yield to explicit child contracts; parents which consume those contracts directly remain admitted. This preserves custom metadata selection and applies to codec, copier, and activator aliases. RPC registrations are now emitted unconditionally, with parity checked for managed/native publishing and with/without NET5_0_OR_GREATER.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🔵 Needs a closer look
It broadly changes generated serialization graphs, response ownership, filtering, pooling, and wire transport and remains held for fresh CI and final human review.
Share candidate constraint validation, source-known argument metadata, immutable response plans, and construction-driven registration emission. Remove redundant manifest maps and pair each closed factory with its service identity.
Centralize response copy/dispose ownership and raw reader adaptation while retaining typed/runtime dispatch, filter isolation, graph publication, and native-safe factory selection.
This handoff has the same unconsumed-response leak on the client/observer path: OutsideRuntimeClient.SendResponse installs the envelope in a message, but ClientMessageCenter.SendMessage can call Message.Dispose and return when it is not running, and Message.Dispose clears rather than disposes a Response body. Since response is nulled after the normal return, the generated holder is then owned by neither side. Make the shared message-drop path release response envelopes before treating this transfer as complete.
A successful return from SendResponse does not guarantee that the message pipeline consumed this envelope. MessageCenter.SendMessage can synchronously drop response messages for expiry or a dead target and call Message.Dispose (MessageCenter.cs:183-188, 220-230), while Message.Dispose only clears _bodyObject (Message.cs:131-135). Clearing ownedResponse here therefore permanently leases the generated holder whenever the caller disappears during dispatch. Ensure every message-drop path disposes a Response body (preferably centrally) before ownership is relinquished.
Move InvokeAndCopy onto IInvokable, share request/stage/response lifetime in GrainCallInvoker, and remove the copy-boundary and ownership wrappers. Preserve original exceptions through filters and copy exception envelopes at delivery.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
It combines broad source-generation, serialization, pooling, filtering, and transport ownership changes and is explicitly awaiting fresh CI and final human review.
On net8.0, abstract type arguments are not rejected for a new() constraint. An abstract class can expose a public parameterless constructor, so the metadata check below reports the candidate as valid even though it cannot satisfy where T : new(). Admission can therefore select an unusable serializer implementation, and normal materialization propagates MakeGenericType's ArgumentException instead of skipping it. Abstract arguments must be rejected on every target framework.
Read an initialized published serializer service before constructing another graph scope. Keep admission and fault checks, and isolate the allocating factory closure on the cold path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Borrow a cleared, bounded per-thread visited set without caching admission decisions. Preserve provider-specific overrides and graph boundaries while removing 176 bytes from each warmed default-service lookup.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implemented and verified in 8292cc4b32615e2617a7964bb6946520155fcb42, preserving the concurrent terminal-ownership and response-subclass fixes.
The production change removes only the four redundant codec/copier lookups from generated response factory Resolve. The constructor still selects and validates all four dependencies; warmed calls continue through the existing provider-owned transactional service cache. No global cache, cached admission decision, or validation bypass was added.
Warmed silo traces and a resolver-only ablation identify substantial repeated default-contract admission, generic matching/reflection, and allocation. The integer holder already shallow-copies its value. A warmed lookup-only loop drops from about 6.05 us and 1,569 B/lookup to 0.30 us and zero bytes. The client already caches raw readers per message serializer, so equivalent per-call client resolution is not claimed. EventPipe stack attribution is sampled thread time, not hardware CPU attribution; kernel sampling was unavailable.
Final sockets-only comparison: before b16181e4f96e0f8f8e8b4ef86260cfc69b8fb96d, after 8292cc4b32615e2617a7964bb6946520155fcb42. .NET 10.0.12 Release, server GC, original i7-11850H affinities, 250 concurrent calls, 20 s warmup + 30 s measurement, three fresh-process repetitions with rotated variant order.
Workload
Before calls/s (range)
After calls/s (range)
Combined CPU us/call, before / after
Completion
346,737 (345,258-374,454)
350,451 (331,512-356,616)
31.97 / 32.50
Integer
251,592 (250,054-258,769)
328,414 (306,803-364,072)
55.18 / 32.10
Mutable cyclic reference
219,857 (219,433-221,862)
313,533 (290,111-325,120)
64.23 / 33.77
1 KiB echo
249,331 (246,857-251,115)
300,047 (297,616-316,169)
55.31 / 36.68
64 KiB echo
29,533 (17,245-29,557)
30,936 (17,463-31,017)
322.96 / 306.47
Sequential integer p50/p95/p99: 33.59/43.66/52.42 -> 24.09/29.80/34.90 us. Integer client allocations remain about 616 B/call (client-only, not total allocation); combined throughput RSS is 245.35 -> 248.49 MiB.
A separate matched recheck against current main c7056b3ab620e5d3144ea72b12d572dc398c988d gives main/optimized 287,174/313,042 calls/s, 33.47/32.32 CPU us/call, and sequential p50 29.88/29.65 us. Ranges overlap and this late cohort is slower than earlier cohorts, so this is recovery toward main, not a claimed stable throughput lead.
Across all cohorts: 186 unprofiled runs, 809,655,192 measured calls, zero failures. CPU package temperatures reached 100 C on the shared powersave host; the large-echo third repetitions suffered substantial slowdowns in both variants. Small completion/large-echo deltas and tail-latency noise are not claimed as across-the-board wins. Remaining measured pressure includes the shared message-serializer pool and messaging queues; optimized RSS remains above main.
Validation on the published revision:
Full serialization: 4,604 net8 / 4,608 net10 passed.
Full Core: 1,789 on each framework passed, including ownership/filter/terminal-message coverage.
Actual NativeAOT RpcResponses, Factories, and strict Contexts publish-and-run passed.
New regressions cover cold initialization/retry, dependency lookup counts, zero warmed allocation, canonical identity, rollback, fault propagation, provider overrides/isolation, and native publication. The warmed-resolution test fails on the old resolver.
Raw logs, process samples, traces, exact pins, source checksums, and CSV/JSON/Markdown tables are retained in the task's persistent artifacts. The unavailable root-owned historical typed harness was not silently substituted: every new variant uses the same reconstructed process-ping harness, and historical results and revision cohorts remain separate. No io_uring code was modified. Documentation source validation passes; full site dependency installation remains blocked by the pre-existing Astro/markdown peer conflict without changing validation settings.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
It substantially changes generated serialization, response ownership, filtering, and transport lifecycles, warranting the requested final human review.
0 open findings
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
Use a shared InvocationContext with generated synchronous completion and concrete async slow paths. Centralize compatibility adaptation in non-generic code, remove delegate-based response release dispatch, and unify pools by envelope type. Preserve construction-only dependency resolution, service precedence, exception timing and consuming ownership.
…target
Apply new()-constraint abstract-type rejection before generic closure on all supported frameworks. Cover metadata-free and captured selection, fallback identity and pending construction without attempting the rejected closure.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
It changes generator output, serialization construction, filtering, pooling, and transport ownership across several critical runtime boundaries and is explicitly awaiting final human review.
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.
Fixes #11375.
Native RPC response handling selected pooled codecs and copiers by closing open generic implementations at runtime. Generate concrete, non-generic response holders and closed construction graphs for source-known method results and proxy dependencies.
The generator emits identical invocation and registration code for managed and NativeAOT publishing. Closed factories participate through the common provider resolution pipeline, with application registration precedence selecting services in either execution mode. Full emitted-source parity covers result models, collections, primitive responses, completion responses, and explicit serializer contexts.
IInvokable.Invoke(InvocationContext)is the common invocation contract. Runtime clients and observers reuse a context containing their provider-owned serialization services. Generated implementations invoke concrete targets, isolate successful results, and rent concrete holders directly. Completed tasks and value tasks use a synchronous entry; pending results use a concrete asynchronous completion method. Non-generic runtime code supplies compatibility adaptation and envelope release, preserving custom codec/copier selection and same-instance copier results. Response pools share one implementation keyed by the actual envelope type.Response factory dependencies resolve and undergo compatibility checks during construction. Warmed invocations retain provider-scoped transactional factory resolution, with allocation-free published lookup and reused admission traversal storage.
Exceptions remain original through filters and are copied at delivery. A shared
GrainCallInvokersupplies request metadata, filter-stage progression, and response lifetime for incoming and outgoing calls. It retains selected and superseded envelopes through filter continuations, supports restoring earlier selections, and releases owned envelopes exactly once.Completion takes ownership of its response input. Typed completion extracts the payload and releases the envelope; untyped completion transfers a successful envelope to its consumer. Void consumption and completed outgoing filter chains release their envelopes. Messages release response bodies on successful writes, terminal disposal, and replacement while retaining them during actual retries. Explicit callback handoffs clear the message's ownership before completion.
IRawResponseWriterwrites the existing raw protocol directly before the sender queries a response codec. ClosedIRawResponseReaderregistrations reconstruct holders from the wire result type. Direct holders/readers require exact canonical response codec and copier types; custom subclasses preserve their raw wire and copying behavior through compatibility dispatch. Primitive codecs, reference cycles, shared identity, pool reset, completion responses, exceptions, and custom implementation selection retain their established semantics.Inferred defaults use the existing metadata selector and complete constructor dependency descriptors. Admission validates bound generic constraints before comparing implementation identities, using generated closed argument metadata. Automatic metadata construction retains its ordinary dependency-injection boundary throughout the call chain; inferred default leaves participate in an existing closed transaction or start a transaction as a closed root. Explicit child registrations and selected converter contracts retain precedence throughout canonical codec, copier, and activator aliases.
The combined inferred response graph admits up to 1,024 closed executable types. Aggregate failures report ORLEANS0116 in managed and native builds while retaining holder declarations. Argument failures provide argument-specific guidance, including for one-way contracts. Every response-returning RPC emits the shared non-generic
Responsegraph independently of automatic result admission, allowing explicit-only dictionary and runtime-selected generic result contracts to use runtime response dispatch.Explicit serializer contexts and inferred RPC response graphs share the graph collector, constructor inspection, provider-owned construction scopes, and metadata registration contracts merged into
main. NativeAOT targets .NET 10+, with actual managed .NET 8 compatibility retained. Native coverage uses the shared smoke project and centralized runner.Draft remains held for fresh CI and final human review.
Microsoft Reviewers: Open in CodeFlow