Skip to content

Prune proxy generator handler discovery to relevant assemblies - #2545

Merged
woksin merged 4 commits into
mainfrom
fix/proxy-generator-discovery-cleanup
Aug 13, 2026
Merged

Prune proxy generator handler discovery to relevant assemblies#2545
woksin merged 4 commits into
mainfrom
fix/proxy-generator-discovery-cleanup

Conversation

@woksin

@woksin woksin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up cleanup to the command response value handler declarations. Build-time handler discovery no longer materializes the type metadata of every dependency an application references, which is work every Release build paid for.

Changed

  • The proxy generator only inspects assemblies referencing Cratis.Arc.Core when discovering declared response value handlers, instead of every managed dependency of the application

Fixed

  • The proxy generator releases its metadata load context when initialization fails part way through, instead of leaking it and its assembly resolving handler until the next run
  • The proxy generator no longer fails when more than one copy of Cratis.Arc.Core is loaded while resolving handler declarations outside the current project graph

woksin added 4 commits August 13, 2026 11:23
The artifacts pinned net8.0;net9.0;net10.0 unconditionally, but
Directory.Build.props builds net10.0 only in Debug. Arc.Core, which
CommandResponseHandlerDependency references, is therefore net10.0-only
in Debug and restore failed with NU1201, so ProxyGenerator.Specs could
not be built or run in Debug at all.

Let both artifacts inherit the target frameworks like every other
project, so they follow Arc.Core in both configurations.
Moving the well-known type checks to name-based comparison left seven
cached Type fields assigned but never read. Each one still cost a
metadata type load on every initialization.
Handler discovery metadata-loaded every managed dependency of the
application and called GetTypes on each one, on every generator run.
Declaring a handler means implementing its contracts, so an assembly
that does not reference Cratis.Arc.Core cannot contribute a declaration
and does not need its type metadata materialized.

Skipping those assemblies also means the counterfeit-contract defense is
no longer what rejects a look-alike dependency in the end to end path, so
the assembly identity matching is now pinned directly by its own specs.
Publishing the assembly name cache and loading the well known types ran
after the guarded region, so a failure there escaped with the metadata
load context published and the assembly resolving handler still attached,
leaving both to leak until the next initialization.

Also stop throwing from the runtime fallback when more than one copy of
the contracts assembly is loaded, and take a single snapshot of the
loaded assemblies instead of enumerating them twice.
@woksin woksin added the patch label Aug 13, 2026
@woksin

woksin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Reviewer context (kept out of the description, which becomes the release notes).

Origin. Follow-ups from reviewing #2540. Items 1–3 of that review; the remaining two (splitting the 1,677-line TypeExtensions.cs, and guarding overly broad declarations such as ICommandResponseValueHandler<object>) are deliberately left out as separate pieces of work.

Internal changes with no user-facing effect:

  • Removed seven well-known Type fields left write-only when Declare server-consumed command response types #2540 moved those checks to name-based comparison. Each still cost a metadata type load per initialization.
  • CommandResponseHandlerDependency and FakeCommandResponseHandlerDependency pinned net8.0;net9.0;net10.0 unconditionally, while Directory.Build.props builds net10.0 only in Debug. Since the first references Arc.Core (net10.0-only in Debug), restore failed with NU1201 and ProxyGenerator.Specs could not be built or run in Debug at all on main. They now inherit the target frameworks like every other project.

On the specs. The new assembly filter means a dependency shipping look-alike contracts is skipped before the identity check runs, so and_a_value_is_claimed_by_counterfeit_handler_contracts would have kept passing for a new reason. The assembly-identity matching is therefore now pinned directly by when_getting_server_handled_command_response_value_type_names. Verified non-vacuous by mutation: replacing both ReferenceEquals checks with true fails should_not_declare_any_value_as_server_handled with the counterfeit value claimed.

Verification (local, net10.0 unless noted):

  • Debug ProxyGenerator.Specs build: 0 errors, 0 warnings — and it builds in Debug at all for the first time since Declare server-consumed command response types #2540
  • Release ProxyGenerator.Specs build (net8/net9/net10): 0 errors, 0 warnings; artifacts produce all three TFMs
  • Full Arc.slnx Release build: 45 projects, 0 errors, 0 warnings
  • Full ProxyGenerator.Specs: 1150/1150 Debug and 1150/1150 Release (1143 before, +7 new)
  • No generated proxy changed after a full Release rebuild, confirming the pruning is behavior-preserving on the real test apps

Not separately spec'd: the widened guard around InitializeWellKnownTypes is defensive — the ASP.NET shared framework path is always in the resolver, so I could not construct a reachable failure there. The reachable failure path stays covered by should_leave_no_project_assemblies_after_a_failed_initialization.

@woksin
woksin merged commit 83db3c6 into main Aug 13, 2026
58 of 59 checks passed
@woksin
woksin deleted the fix/proxy-generator-discovery-cleanup branch August 13, 2026 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant