Repository navigation
AOT: resolve built-in actor ref providers from constant type names so the trimmer preserves them - #8599
Merged
Aaronontheweb merged 4 commits intoSep 22, 2026
Conversation
…trimmer can preserve them `ActorSystemImpl.ConfigureProvider` used to load every provider through a single `Type.GetType(_settings.ProviderClass)` call. The argument is a string read from HOCON, so the trimmer and the Native AOT compiler cannot tell which type that call site loads (IL2057) and neither `Akka.Remote` nor `Akka.Cluster` gets rooted -- `akka.actor.provider = cluster` then fails at runtime in a trimmed or AOT-published app even though the assembly is referenced. `ProviderSelection` already carries the three built-in providers as compile-time constants, so switch on `Settings.ProviderSelectionType` and resolve each one from its own constant: `Local` constructs `LocalActorRefProvider` directly, `Remote` and `Cluster` go through a dedicated helper, and anything else takes the fully dynamic path as before. The helpers have to be shaped the way they are. `Type.GetType` and `Activator.CreateInstance` must sit in the SAME method with the constant inlined at the `Type.GetType` call -- that is what lets the trimmer resolve the assembly-qualified name statically and keep the type plus its constructor. Hoisting the constant into a local, a parameter or a field defeats it. The explanatory comments say so; keep them. `Settings` no longer validates `ProviderClass` through `Type.GetType` for the built-in three. That validation is the same unanalyzable lookup, so it would reintroduce the warning and, under Native AOT, fail at construction time for a provider that `ConfigureProvider` could resolve perfectly well. Validation now runs only when the selection is `ProviderSelection.Custom`; a missing `Akka.Remote` / `Akka.Cluster` reference is reported where the provider is constructed, with a message that names the package.
…d provider type-name guard specs; record ProviderClass change in BREAKING_CHANGES_V1.6.md `ProviderSelection.GetProvider` handled the `"local"` alias and the assembly-qualified names of the remote and cluster providers, but never the local provider's own type name. Both spellings -- the bare `"Akka.Actor.LocalActorRefProvider"` that ships as the default in `akka.conf` and `ProviderSelection.Local.Instance.Fqn` -- fell through to `Custom`, so a default boot missed the new `Local` arm in `ConfigureProvider` and went back through the dynamic path the previous commit set out to avoid. Add both cases alongside the existing ones. Consequence: `Settings.ProviderClass` on a default boot now reports `"Akka.Actor.LocalActorRefProvider, Akka"` instead of the bare name, since `ProviderClass` is just the selection's `Fqn`. `ConfigurationSpec` asserted the bare string; it now compares against `ProviderSelection.Local.Instance.Fqn`. New specs: - `Akka.Tests.Actor.ProviderSelectionSpec` covers both spellings mapping to `Local.Instance`, and the `ProviderClass` value on a default boot. - `Akka.Remote.Tests.ProviderTypeNameSpec` and `Akka.Cluster.Tests.ProviderTypeNameSpec` assert that `Type.GetType(ProviderSelection.RemoteActorRefProvider)` and `Type.GetType(ProviderSelection.ClusterActorRefProvider)` resolve to the expected `IActorRefProvider`. The constants are the only thing standing between the config and the type now, and a rename would not break the build -- only the runtime lookup. These turn that into a test failure.
Aaronontheweb
commented
Sep 22, 2026
Aaronontheweb
left a comment
Member
Author
There was a problem hiding this comment.
LGTM but might need some clean-up
Member
Author
There was a problem hiding this comment.
Validates that the constant is correct - makes sense.
| switch (providerClass) | ||
| { | ||
| case "local": | ||
| case "Akka.Actor.LocalActorRefProvider": // additional case for the bare type name used by akka.conf |
Member
Author
There was a problem hiding this comment.
Good, adds a measure of backwards compat
| // them through the ProviderClass property would reintroduce a dynamic Type.GetType call | ||
| // that the trimmer cannot see through (IL2057), and under Native AOT it would fail here | ||
| // even though the provider itself is perfectly resolvable. | ||
| if (ProviderSelectionType is ProviderSelection.Custom) |
…vider type-name specs into a theory Review feedback on akkadotnet#8599. `CreateRemoteProvider` / `CreateClusterProvider` / `CreateCustomProvider` were three copies of the same four lines. They existed because the first draft relied on inlining the constant at the `Type.GetType` call, which forces one method per constant. Annotating the parameter instead removes that constraint: the trimmer treats a `string` parameter carrying `DynamicallyAccessedMembers` as a type name, resolves a constant argument at the call site, and keeps the type plus the public constructor. One `CreateProvider` now serves all three arms, taking the type name and the message to use when it does not resolve. Verified rather than assumed, because the whole point of the PR is that the trimmer can still see the type: - `dotnet build src/core/Akka/Akka.csproj -c Release -p:IsAotCompatible=true` keeps the total at 38 IL warnings. `Type.GetType` inside `CreateProvider` no longer warns at all -- the annotation satisfies it -- and the remote and cluster call sites stay clean. The one provider warning left moved from IL2057 inside the old `CreateCustomProvider` to IL2072 at the custom arm's call site, naming `Settings.ProviderClass` as the unannotated source. That is the honest answer for a type named only in HOCON; a later change puts it behind a feature switch. - Native AOT canary (`PublishAot`, linux-x64, whole `Akka` assembly rooted, an app whose only reference to the provider is the HOCON string): `provider = cluster` constructs `Akka.Cluster.ClusterActorRefProvider` out of `Akka.Cluster`, `provider = remote` constructs `Akka.Remote.RemoteActorRefProvider` out of `Akka.Remote`, and `provider = local` boots the system to completion. The first two still hit the known, unrelated `ProtobufSerializer` type-load failure in `ConfigureSerialization`, which runs after `ConfigureProvider`. Specs: `Akka.Remote.Tests.ProviderTypeNameSpec` is gone and the Akka.Cluster.Tests one is a single theory over all three constants -- Akka.Cluster.Tests references Akka.Remote, so every constant resolves in that one project. The core `GetProvider` mapping spec is a theory too, over the alias, the bare type name and the assembly-qualified constant.
Aaronontheweb
commented
Sep 22, 2026
Aaronontheweb
left a comment
Member
Author
There was a problem hiding this comment.
Trimmed the tautological bits: dropped the Local row from the Cluster theory (the Local arm constructs directly and never reads its constant, so that row guarded nothing) and the second assertion in the core default-config fact (it restated Local.Instance.Fqn). What's left: the Remote/Cluster constant guard, the GetProvider mapping theory over the three spellings, and one fact proving the string that ships in akka.conf lands on Local.
Aaronontheweb
enabled auto-merge (squash)
September 22, 2026 16:51
Aaronontheweb
disabled auto-merge
September 22, 2026 18:47
This was referenced Sep 24, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Changes
Resolves the three built-in actor ref providers (
local,remote,cluster) from the compile-time constant type names that already live onProviderSelection, with theType.GetTypecall inlined at each site, so the trimmer and the Native AOT compiler can see which type is loaded and keep it.Why. On
devtoday, a Native AOT publish of any app dies on the very first type load inSettings..ctor:'akka.actor.provider' is not a valid type name : 'Akka.Actor.LocalActorRefProvider'. The constant reachesType.GetTypeviaProviderSelection.Fqn→Settings.ProviderClass, so the trimmer sees a runtime string (IL2057) and drops the type. Per the IL2057 rule, a statically known type name is preserved.Verified against the real
Akka.Remote/Akka.Clusterassemblies under Native AOT (net10.0, linux-x64), with the provider chosen only by HOCON, no Remote/Cluster type named in app code, and neither assembly rooted:devSettings.cs:100,ActorSystemImpl.cs:447Settings..ctorprovider = clusterAkka.Cluster.ClusterActorRefProviderconstructedprovider = remoteAkka.Remote.RemoteActorRefProviderconstructedAkka.Clusternot referencedConfigurationException: akka.actor.provider = cluster, but Akka.Cluster is not referenced by this application.What changed
ActorSystemImpl.ConfigureProviderswitches onProviderSelectionType;Localis a directnew,Remote/Clustergo throughprivate statichelpers that inline the constant and callActivator.CreateInstancein the same method (load-bearing for dataflow; commented). Custom providers keep the existing reflection path.Settingsonly runs theType.GetType(ProviderClass)validation forProviderSelection.Custom.ProviderSelection.GetProvidernow maps both spellings of the local provider name (akka.conf's bare"Akka.Actor.LocalActorRefProvider"and the assembly-qualified form) toLocal.Instance; previously both fell through toCustom, so a default boot never hit theLocalarm.Akka.Remote.TestsandAkka.Cluster.Testsassert each constant still resolves, so a rename can't silently break the string. Core spec for theGetProvidermapping.BREAKING_CHANGES_V1.6.mdentry:Settings.ProviderClasson a default boot now reports"Akka.Actor.LocalActorRefProvider, Akka", and the missing-assembly error moves fromSettingstoActorSystemImplwith a message that names the package.No public API changes (all new members are
private static);Akka.API.Testspasses unchanged. FullAkka.Testssuite: 1310 passed / 0 failed / 23 skipped.Not in this PR: with this change, an AOT boot proceeds to
Serialization..ctorand dies inGetSerializerIdentifierFromConfig, which resolves everyserialization-identifierskey withthrowOnError: true. That is the next fix.Part of #7246.
Checklist
For significant changes, please ensure that the following have been completed (delete if not relevant):