Skip to content

AOT: resolve built-in actor ref providers from constant type names so the trimmer preserves them - #8599

Merged
Aaronontheweb merged 4 commits into
akkadotnet:devfrom
Aaronontheweb:fix/aot-provider-constant-type-names
Sep 22, 2026
Merged

Aaronontheweb merged 4 commits into
akkadotnet:devfrom
Aaronontheweb:fix/aot-provider-constant-type-names

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Member

Changes

Resolves the three built-in actor ref providers (local, remote, cluster) from the compile-time constant type names that already live on ProviderSelection, with the Type.GetType call inlined at each site, so the trimmer and the Native AOT compiler can see which type is loaded and keep it.

Why. On dev today, a Native AOT publish of any app dies on the very first type load in Settings..ctor: 'akka.actor.provider' is not a valid type name : 'Akka.Actor.LocalActorRefProvider'. The constant reaches Type.GetType via ProviderSelection.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.Cluster assemblies 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:

provider-site IL warnings runtime
dev IL2057 at Settings.cs:100, ActorSystemImpl.cs:447 fails in Settings..ctor
this PR, provider = cluster none Akka.Cluster.ClusterActorRefProvider constructed
this PR, provider = remote none Akka.Remote.RemoteActorRefProvider constructed
this PR, Akka.Cluster not referenced none ConfigurationException: akka.actor.provider = cluster, but Akka.Cluster is not referenced by this application.

What changed

  • ActorSystemImpl.ConfigureProvider switches on ProviderSelectionType; Local is a direct new, Remote/Cluster go through private static helpers that inline the constant and call Activator.CreateInstance in the same method (load-bearing for dataflow; commented). Custom providers keep the existing reflection path.
  • Settings only runs the Type.GetType(ProviderClass) validation for ProviderSelection.Custom.
  • ProviderSelection.GetProvider now maps both spellings of the local provider name (akka.conf's bare "Akka.Actor.LocalActorRefProvider" and the assembly-qualified form) to Local.Instance; previously both fell through to Custom, so a default boot never hit the Local arm.
  • Guard specs in Akka.Remote.Tests and Akka.Cluster.Tests assert each constant still resolves, so a rename can't silently break the string. Core spec for the GetProvider mapping.
  • BREAKING_CHANGES_V1.6.md entry: Settings.ProviderClass on a default boot now reports "Akka.Actor.LocalActorRefProvider, Akka", and the missing-assembly error moves from Settings to ActorSystemImpl with a message that names the package.

No public API changes (all new members are private static); Akka.API.Tests passes unchanged. Full Akka.Tests suite: 1310 passed / 0 failed / 23 skipped.

Not in this PR: with this change, an AOT boot proceeds to Serialization..ctor and dies in GetSerializerIdentifierFromConfig, which resolves every serialization-identifiers key with throwOnError: 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):

…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 Aaronontheweb added AOT Ahead-of-Time (AOT) Compilation akka-actor labels Sep 22, 2026

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but might need some clean-up

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, adds a measure of backwards compat

Comment thread src/core/Akka/Actor/Internal/ActorSystemImpl.cs Outdated
Comment thread src/core/Akka.Cluster.Tests/ProviderTypeNameSpec.cs Outdated
// 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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

…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 Aaronontheweb left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Aaronontheweb left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

akka-actor AOT Ahead-of-Time (AOT) Compilation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant