Skip to content

AOT: guard akka.extensions and custom akka.actor.provider reflection behind Akka.DynamicTypeLoading - #8670

Merged
Aaronontheweb merged 4 commits into
akkadotnet:devfrom
Aaronontheweb:aot/extensions-provider-switch-off
Sep 30, 2026
Merged

Aaronontheweb merged 4 commits into
akkadotnet:devfrom
Aaronontheweb:aot/extensions-provider-switch-off

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Changes

Closes the last two IL2xxx sites the AOT canary's unrooted publish hit (#7246); see
src/aot/Akka.AOT.App/README.md for the canary itself.

  • ActorSystemImpl.LoadExtensions(): an akka.extensions entry that isn't one of Akka's
    first-party extensions (AOT: ExtensionsSetup and a first-party akka.extensions table #8648) now checks AkkaFeatures.IsDynamicTypeLoadingSupported
    before falling through to Type.GetType. Off, it throws
    ConfigurationException(AkkaFeatures.NotBuiltIn(...)) naming the setting, the value and
    the switch, pointing at ExtensionsSetup. On, unchanged. The Type.GetType call itself
    moves into a new [RequiresUnreferencedCode] helper, ResolveExtensionType.
  • Custom akka.actor.provider: both Settings' validation and
    ActorSystemImpl.ConfigureProvider's fallback arm needed the same guard - Settings runs
    first and would already reject a bad value, but the trim analyzer has no way to know that,
    so ConfigureProvider's IL2072 needed its own fix. ConfigureProvider's switch expression
    became an if/else if chain (matching ConfigureScheduler/the logger settings) so the
    AkkaFeatures.IsDynamicTypeLoadingSupported guard is visible to the analyzer, and the
    fallback now calls a new CreateCustomProvider ([RequiresUnreferencedCode]) instead of
    the existing CreateProvider, whose typeName parameter carries
    [DynamicallyAccessedMembers(PublicConstructors)] - Settings.ProviderClass has no such
    annotation, so passing it through that parameter warned IL2072 regardless of the guard.

Deletes the three now-fixed lines from aot-warnings.baseline.txt's UNROOTED section
(extensions, ConfigureProvider, Settings ctor). TypeCache/manifest resolution is the
remaining UNROOTED entry, tracked in a follow-up PR - this PR and that one both touch
aot-warnings.baseline.txt, so expect a small conflict between them depending on merge
order.

Review fixes (61d34ab)

Two issues found in review, both fixed on this branch:

  1. ProviderSelection.GetProvider matched by exact string. A spelling variant of a
    built-in provider - no space after the comma (e.g.
    "Akka.Cluster.ClusterActorRefProvider,Akka.Cluster"), or a versioned
    assembly-qualified name - fell through to Custom. With the switch off that meant a
    working built-in provider wrongly threw NotBuiltIn. GetProvider now normalizes
    through TypeExtensions.TrySplitTypeName (the AOT M1-G: one matching rule for every built-in type name #8613 rule: strip the assembly identity,
    split at the comma, compare the assembly case-insensitively) against a small
    BuiltInProviders table, so every accepted spelling of the three built-ins maps to the
    same ProviderSelection. This also fixes Settings.HasCluster being wrongly false for
    such a spelling - independent of the switch, a pre-existing bug. Added a classification
    theory (ProviderSelection.GetProvider directly) plus an end-to-end theory over both
    switch states proving the spelling now fails for the provider's own reason
    ("Akka.Cluster is not referenced by this application", since Akka.Tests doesn't
    reference Akka.Cluster/Akka.Remote) rather than NotBuiltIn, and a Local-provider
    spelling-variant test that boots a real ActorSystem end to end (Local ships in
    Akka.dll, so it's the one variant this project can actually construct).
  2. A first-party extension whose assembly is absent was misclassified as not-built-in.
    TryCreateFirstPartyExtension returns null both when a name isn't in the first-party
    table and when it is but the assembly won't load (e.g. Akka.DistributedData listed but
    not deployed) - AOT: ExtensionsSetup and a first-party akka.extensions table #8648 promised the latter is logged and skipped, same as reflection. With
    the switch off this PR's new guard didn't distinguish the two and threw NotBuiltIn for
    both. Added ActorSystemImpl.IsFirstPartyExtensionName, which reports a table hit
    regardless of whether the assembly loads; LoadExtensions now checks it before the
    switch-off guard, so an absent-module name logs-and-skips in either switch state, and only
    a name that isn't in the table at all is rejected as not built in. Covered in
    ExtensionsSetupSpec (the new method directly) and AkkaFeaturesSpec (an end-to-end
    ActorSystem.Create over both switch states with the same absent DistributedDataProvider
    name the existing table-level test already uses).

Verification

  • dotnet publish src/aot/Akka.AOT.App -r linux-x64 -c Release -p:TrimmerSingleWarn=false
    (unrooted): 4 warnings on dev -> 1 on this branch (TypeCache only, unchanged by the
    review fixes). Canary still prints [canary] OK, exit 0.
  • Same publish with -p:RootAkka=true: dotnet run scripts/CheckAotWarnings.cs reports 9
    in-scope warnings against 9 baseline entries, no new, none stale.
  • dotnet test src/core/Akka.Tests -c Release --framework net10.0 --filter FullyQualifiedName~Akka.Tests.Actor|FullyQualifiedName~Akka.Tests.Util:
    729 passed, 12 skipped (pre-existing), 0 failed (was 717 before the review-fix commit;
    the +12 are the new spelling/absent-assembly cases).
  • dotnet test src/core/Akka.Tests -c Release --framework net10.0 --filter FullyQualifiedName~AkkaFeaturesSpec|FullyQualifiedName~ExtensionsSetupSpec:
    56 passed, 0 failed.
  • dotnet test src/core/Akka.API.Tests -c Release --framework net10.0 --filter FullyQualifiedName~ApproveCore:
    passes with no diff - every new member is internal/private, so there is no public API
    change.

Breaking changes

Component Type Change Migration
Akka (core / actor system bootstrap) Behavior ActorSystemImpl.LoadExtensions and the custom akka.actor.provider validation in Settings and ActorSystemImpl.ConfigureProvider now put their Type.GetType fallback behind the Akka.DynamicTypeLoading switch, closing the last two IL2xxx warnings the AOT canary's unrooted publish hit (#7246). With the switch on, both resolve exactly as before. With it off: an akka.extensions entry that is not one of Akka's first-party extensions (#8648) now throws ConfigurationException from AkkaFeatures.NotBuiltIn naming the setting, the value and the switch, in place of logging "is not an 'ExtensionId'" and skipping it; a custom (non-built-in) akka.actor.provider likewise throws from AkkaFeatures.NotBuiltIn instead of attempting Type.GetType at all -- on the JIT that used to succeed for any provider that actually resolved, since nothing enforced the switch at this site yet; only a trimmed/AOT publish caught it, as an IL2057/IL2072 build-time warning rather than a runtime failure. Two review fixes are folded in: (1) ProviderSelection.GetProvider now normalizes akka.actor.provider the same way the BuiltIn* tables do (#8613) before falling back to Custom, so a spelling variant of a built-in provider -- no space after the comma, or a versioned assembly-qualified name -- is recognized as the built-in instead of being misclassified as custom (which, with the switch off, would have wrongly thrown NotBuiltIn for a name that is in fact built in; this also fixes Settings.HasCluster being wrongly false for such a spelling, independent of the switch); (2) a first-party akka.extensions name (#8648) whose assembly is not deployed now logs and skips exactly as it does with the switch on, instead of throwing NotBuiltIn -- only a name that is not in the first-party table at all is rejected that way. None at the default -- the switch is on unless a project file explicitly turns it off. A trimmed or Native AOT application that turns the switch off must register extensions through ExtensionsSetup instead of akka.extensions, and keep akka.actor.provider at local, remote or cluster (or the assembly-qualified name of the built-in provider type).

(The shared BREAKING_CHANGES_V1.6.md ledger itself is not touched by this PR -- it is updated in a separate batch PR.)

Checklist

  • dotnet build -c Release -warnaserror for Akka and Akka.Tests
  • New/updated specs green (AkkaFeaturesSpec, ExtensionsSetupSpec, plus the existing
    Akka.Tests.Actor / Akka.Tests.Util suites for regressions)
  • Akka.API.Tests ApproveCore: no diff (no public API touched)
  • AOT canary: unrooted publish down from 4 warnings to 1; rooted CheckAotWarnings
    reports no new warnings
  • Breaking change documented in this PR's Breaking changes section (the shared ledger file is updated separately)

…hind the DynamicTypeLoading switch

ActorSystemImpl.LoadExtensions and the custom-provider validation in Settings /
ActorSystemImpl.ConfigureProvider were the last two IL2xxx sites the AOT canary's
unrooted publish hit (akkadotnet#7246). Both now check AkkaFeatures.IsDynamicTypeLoadingSupported
before falling back to Type.GetType, throwing ConfigurationException(AkkaFeatures.NotBuiltIn(...))
when the switch is off, matching the pattern already used for the scheduler, log
formatter and stdout logger.

With the switch on, behavior is unchanged. With it off: an akka.extensions entry that
isn't a first-party extension (akkadotnet#8648) throws instead of being logged and skipped, and
a custom akka.actor.provider throws instead of going through reflection.

Removes the corresponding entries from the AOT warning baseline; the unrooted canary
publish is now warning-free except for TypeCache (tracked separately). Adds
AkkaFeaturesSpec coverage for both switch states and a BREAKING_CHANGES_V1.6.md row.
@Aaronontheweb Aaronontheweb added the AOT Ahead-of-Time (AOT) Compilation label Sep 30, 2026
…t-party extensions as not-built-in

Two review fixes on top of the extensions/provider switch-off guard:

1. ProviderSelection.GetProvider matched akka.actor.provider by exact string, so
   a spelling variant of a built-in provider - no space after the comma (e.g.
   "Akka.Cluster.ClusterActorRefProvider,Akka.Cluster"), or a versioned
   assembly-qualified name - fell through to Custom. With the switch off that
   meant a working built-in provider threw NotBuiltIn. GetProvider now
   normalizes through TypeExtensions.TrySplitTypeName (the akkadotnet#8613 rule: strip
   the assembly identity, split at the comma, compare the assembly
   case-insensitively) against a small BuiltInProviders table, so every
   accepted spelling maps to the same ProviderSelection instead. This also
   fixes Settings.HasCluster being wrongly false for such a spelling,
   independent of the switch.

2. LoadExtensions treated a first-party extensions-table name whose assembly
   isn't deployed (TryCreateFirstPartyExtension returns null either way) the
   same as a name that isn't in the table at all, so with the switch off it
   threw NotBuiltIn for a name akkadotnet#8648 already promised to log-and-skip. A new
   IsFirstPartyExtensionName reports a table hit regardless of whether the
   assembly loads, so LoadExtensions can log-and-skip the absent-module case
   before reaching the switch-off guard - matching the switch-on behavior,
   which already fell through to the same log message.

Tests added to AkkaFeaturesSpec (provider spelling classification and its
end-to-end consequence in both switch states, plus the absent-assembly
extension case) and ExtensionsSetupSpec (IsFirstPartyExtensionName). Updates
the BREAKING_CHANGES_V1.6.md row to name both cases.
# Conflicts:
#	BREAKING_CHANGES_V1.6.md

@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

continue;
}

if (!AkkaFeatures.IsDynamicTypeLoadingSupported)

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

@Aaronontheweb
Aaronontheweb merged commit 352396b into akkadotnet:dev Sep 30, 2026
14 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AOT Ahead-of-Time (AOT) Compilation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant