Skip to content

AOT: ExtensionsSetup and a first-party akka.extensions table - #8648

Merged
Aaronontheweb merged 2 commits into
devfrom
feature/aot-ext-1-extensions-setup
Sep 30, 2026
Merged

Aaronontheweb merged 2 commits into
devfrom
feature/aot-ext-1-extensions-setup

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Changes

First PR of a stack that lets extensions start without type names in HOCON, so Native AOT apps with Akka.DynamicTypeLoading off can use them. Additive only; no behavior change with the switch on.

  • ExtensionsSetup (new, Akka.Actor.Setup): holds IExtensionIds, exposed as a read-only list. Create rejects null ids. Like any Setup, a second one passed to ActorSystemSetup.And replaces the first. LoadExtensions reads it at the same point in startup as akka.extensions. Setup ids go first, then the HOCON list; an extension named in both registers once, from the Setup's id, because RegisterExtension keys by extension type.

  • First-party table in ActorSystemImpl: an akka.extensions entry naming one of these resolves from a literal type name instead of Type.GetType on the HOCON string:

    • Akka.DistributedData.DistributedDataProvider, Akka.DistributedData
    • Akka.Cluster.Tools.PublishSubscribe.DistributedPubSubExtensionProvider, Akka.Cluster.Tools
    • Akka.Cluster.Tools.Client.ClusterClientReceptionistExtensionProvider, Akka.Cluster.Tools
    • Akka.Cluster.Metrics.ClusterMetricsExtensionProvider, Akka.Cluster.Metrics

    Matching is strict on type name plus assembly (assembly identity is ignored, assembly name case-insensitive, same as Type.GetType). Each row passes its own literal to a helper whose parameter is [DynamicallyAccessedMembers(PublicParameterlessConstructor)], the AOT: resolve built-in actor ref providers from constant type names so the trimmer preserves them #8599 / AOT: core module-serializer table (#8626, part 1) #8645 pattern. Type.GetType is called only when a row matches. An assembly that is absent or won't load counts as absent and falls through to today's reflection path, which logs the same error as before.

  • One InternalsVisibleTo, for Akka.Cluster.Metrics.Tests, so it can check its table row against reflection. The other rows are checked from Akka.Cluster.Sharding.Tests, which already had internals access.

  • The AOT warning baseline's LoadExtensions comment now says the table exists and the entry stays until the reflection path is guarded.

No switch-off throws here; that comes in a later PR.

A note on "failing" extensions

Today akka.extensions logs and skips only names it can't resolve or construct. If CreateExtension throws, ActorSystem.Create fails. A Setup holds ids that already exist, so the only failure left is CreateExtension, and it behaves the same way on both paths. The spec checks that parity instead of a log-and-skip.

Tests

  • Akka.Tests/Actor/Setup/ExtensionsSetupSpec (an AkkaSpec): Setup registers an extension; a throwing extension fails startup the same way via HOCON and via Setup; HOCON plus Setup registers the extension once, from the Setup's id (two id classes with separate counters); null ids are rejected; the table ignores names that aren't first-party (bare name, wrong assembly, unknown type) and names whose assembly is absent.
  • Akka.Cluster.Sharding.Tests/FirstPartyExtensionSpec (DData and both Cluster.Tools rows, several spellings) and Akka.Cluster.Metrics.Tests/FirstPartyExtensionSpec: the table returns the same type as Type.GetType on the raw name.
  • Existing HOCON-driven specs that now go through the table still pass: DistributedPubSubConfigSpec, ClusterClientSerializerSpec, ClusterMetricsAutostartSpec.

Checklist

  • dotnet build -c Release -warnaserror for Akka and the touched test projects
  • New specs green on repeated runs
  • Akka.Tests Actor filter: 571 passed, 12 skipped, 0 failed (Actor + Util before the review fixes: 712 passed, 12 skipped)
  • Akka.API.Tests: ApproveCore updated only for ExtensionsSetup and the Metrics InternalsVisibleTo line
  • AOT canary: unrooted publish runs and prints [canary] OK with the same 4 warnings; rooted CheckAotWarnings reports no new warnings (12/12 baseline)

@Aaronontheweb Aaronontheweb added the AOT Ahead-of-Time (AOT) Compilation label Sep 25, 2026
@Aaronontheweb
Aaronontheweb added this pull request to stack #8662 September 29, 2026 13:53
@Aaronontheweb
Aaronontheweb force-pushed the feature/aot-ext-1-extensions-setup branch 2 times, most recently from ae47923 to 4b0a478 Compare September 29, 2026 18:02
@Aaronontheweb

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Adds ExtensionsSetup, which registers extensions by id instead of by
type name in akka.extensions. LoadExtensions reads it at the same point
in startup as the HOCON list; an extension named in both registers once.

akka.extensions entries naming DistributedData, DistributedPubSub,
ClusterClientReceptionist or ClusterMetrics now resolve from a table of
literal type names, so the trimmer can keep those providers. With
Akka.DynamicTypeLoading on (the default) behavior is unchanged: the
table resolves the same type Type.GetType would, and an assembly that is
absent or will not load falls through to the existing reflection path.
…tests moved to Sharding.Tests

- ExtensionsSetup exposes a read-only wrapper, rejects null ids, and
  documents that a second ExtensionsSetup replaces the first.
- First-party table members are #nullable enable and return IExtensionId?.
- DData and Cluster.Tools parity rows move to Akka.Cluster.Sharding.Tests,
  which already sees core internals; drop the Akka.DistributedData.Tests
  InternalsVisibleTo. Parity asserts against Type.GetType on the raw name.
- ExtensionsSetupSpec derives from AkkaSpec, and the dedupe test proves the
  Setup's id wins over the HOCON one.
- Update the LoadExtensions entry's comment in the AOT warning baseline.
@Aaronontheweb
Aaronontheweb force-pushed the feature/aot-ext-1-extensions-setup branch from 4b0a478 to 4f23e84 Compare September 30, 2026 00:47

@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

#nullable enable
// Akka's own extensions that akka.extensions commonly names: bare type name -> the assembly it must name, and a
// factory passing its own literal so the trimmer keeps that type. Nothing is probed unless a row matches.
private static readonly Dictionary<string, (string Assembly, Func<IExtensionId?> Create)> FirstPartyExtensions =

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

{
var extensions = new List<IExtensionId>();
// Setup ids go first; one also named in HOCON registers once, since RegisterExtension keys by type
var extensions = new List<IExtensionId>(_settings.Setup.Get<ExtensionsSetup>()

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

.And(ExtensionsSetup.Create(extensionIds));

[Fact(DisplayName = "An extension in ExtensionsSetup is registered when the ActorSystem starts")]
public async Task Should_register_extension_When_named_in_Setup()

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 51cf0df into dev Sep 30, 2026
17 checks passed
@Aaronontheweb
Aaronontheweb deleted the feature/aot-ext-1-extensions-setup branch September 30, 2026 01:57
Aaronontheweb added a commit to Aaronontheweb/akka.net that referenced this pull request 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.
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