Skip to content

Hosting: register WithExtension<T> through ExtensionsSetup - #8649

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

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

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Changes

Second PR of the stack; builds on #8648 (ExtensionsSetup). Review that one first. This PR's own diff is one commit.

  • WithExtension<T>() now has [DynamicallyAccessedMembers(PublicParameterlessConstructor)] on T (same as core's ActorSystemWithExtensions.WithExtension<T, TI>). It captures a factory, and the ActorSystem factory passes the ids in an ExtensionsSetup instead of writing T's assembly-qualified name into akka.extensions.
  • Hosting stops reading akka.extensions. The old AddExtensions() called Type.GetType on every listed name, which is its own trim warning. It's gone. User-supplied akka.extensions reaches core unchanged, where the first-party table or reflection handles it.
  • WithExtensions(params Type[]) still uses reflection (Activator.CreateInstance(Type)) and now has [RequiresUnreferencedCode], which points callers to WithExtension<T>().
  • If the user added their own ExtensionsSetup via AddSetup, Hosting merges it with its own instead of replacing it.
  • WithDistributedData in Akka.Cluster.Hosting already calls WithExtension<DistributedDataProvider>(); no change needed. The Cluster.Hosting tests pass.

Observable changes (added to BREAKING_CHANGES_V1.6.md)

  • Settings.Config.GetStringList("akka.extensions") no longer lists extensions that Hosting registered. Use Settings.Setup.Get<ExtensionsSetup>() or HasExtension<T>() instead.
  • An akka.extensions entry that doesn't resolve used to be dropped silently by Hosting when WithExtension was also used. Now it reaches core, which logs its usual error.
  • An extension id whose constructor throws now fails startup with a ConfigurationException that names the type (inner exception attached). Before, core logged and skipped it.

Tests

Akka.Hosting.Tests/ExtensionsSpecs, new:

  • WithExtension<T> registers through ExtensionsSetup: the extension is there after startup, and the setup holds exactly that id.
  • Hosting doesn't write akka.extensions: a user HOCON list passes through exactly as given, and the WithExtension extension still starts.
  • A user-added ExtensionsSetup is kept alongside Hosting's.
  • A throwing extension-id constructor fails startup with a ConfigurationException naming the type.
  • An unresolvable akka.extensions entry next to WithExtension reaches core, which logs an error (captured by a small recording logger, since the error fires during startup before an EventFilter could be attached).

Checklist

  • dotnet build -c Release -warnaserror: Akka.Hosting, Cluster/Remote/Persistence.Hosting and all six Hosting test projects
  • ExtensionsSpecs 5x green
  • Hosting test projects: Hosting 145, Cluster.Hosting 34, Remote.Hosting 16, Persistence.Hosting 24, Hosting.TestKit 306, Hosting.TestKit.Xunit2 9; all passed
  • Akka.API.Tests: ApproveHosting changed only by the two new attributes
  • AOT canary: unrooted run prints [canary] OK with the same 4 warnings; rooted CheckAotWarnings reports no new warnings (the canary doesn't reference Hosting, so this is a no-regression check)

@Aaronontheweb Aaronontheweb added the AOT Ahead-of-Time (AOT) Compilation label Sep 25, 2026
@Aaronontheweb
Aaronontheweb force-pushed the feature/aot-ext-2-hosting-extensions-setup branch from 253c9f1 to b4d38bf Compare September 25, 2026 13:44
Aaronontheweb added a commit that referenced this pull request Sep 28, 2026
* Add missing trimming annotations to Akka.Hosting for Native AOT

AddAkka<T> forwards T into AddHostedService<T>, WithHealthCheck<T>
forwards T into ActivatorUtilities.GetServiceOrCreateInstance<T>, and
LoggerConfigBuilder.LogMessageFormatter reflects on its Type value with
GetConstructor - none of the three carried the
DynamicallyAccessedMembers(PublicConstructors) annotation those APIs
require, so a Native AOT publish trimmed away the constructor
reflection metadata and AddAkka failed at startup with "no suitable
constructor". Propagate the same annotation through
WithDefaultLogMessageFormatter<T>, which assigns into
LogMessageFormatter.

Grepped Akka.Hosting, Akka.Remote.Hosting, Akka.Cluster.Hosting and
Akka.Persistence.Hosting for the same pattern (AddHostedService,
AddSingleton<TService,TImpl>, ActivatorUtilities, Activator.CreateInstance<T>).
The other generic type parameters found (ActorRegistry's TKey/TActor,
Cluster.Hosting's per-entity TKey, Persistence.Hosting's TAdapter) are
only ever used as typeof(T) dictionary keys or config values, not
passed into a DynamicallyAccessedMembers-requiring API, so they need
no change. akka.extensions / WithExtension<T> is out of scope here -
PR #8649 covers it.

Updated the Hosting API approval baseline for the new attributes.

* Hosting AOT annotations: annotate the formatter field, drop the ledger row, shorter comments

The formatter getter returned an unannotated field (IL2078 under the trim
analyzer). The annotations are source- and binary-compatible, so they don't
need a BREAKING_CHANGES row.
@Aaronontheweb
Aaronontheweb added this pull request to stack #8662 September 29, 2026 13:53
@Aaronontheweb
Aaronontheweb force-pushed the feature/aot-ext-2-hosting-extensions-setup branch from b4d38bf to 525194e Compare September 29, 2026 18:24
Base automatically changed from feature/aot-ext-1-extensions-setup to dev September 30, 2026 01:57
WithExtension<T>() now captures a factory for T (annotated with
[DynamicallyAccessedMembers(PublicParameterlessConstructor)]) and the
ActorSystem factory passes the ids in an ExtensionsSetup, instead of
writing assembly-qualified names into akka.extensions. Hosting no longer
reads akka.extensions either, so its Type.GetType call is gone; any
user-supplied akka.extensions reaches core unchanged. An ExtensionsSetup
the user added through AddSetup is kept.

WithExtensions(params Type[]) still creates ids by reflection and is
marked [RequiresUnreferencedCode].
…ilure and HOCON pass-through

A throwing extension-id constructor now fails startup with a
ConfigurationException that names the type, instead of a bare
TargetInvocationException from DI. New specs cover that, and an
unresolvable akka.extensions entry next to WithExtension reaching core
and being logged there.
@Aaronontheweb
Aaronontheweb force-pushed the feature/aot-ext-2-hosting-extensions-setup branch from 525194e to 3a118d8 Compare September 30, 2026 02:00

@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

@Aaronontheweb
Aaronontheweb merged commit 6e20088 into dev Sep 30, 2026
17 checks passed
@Aaronontheweb
Aaronontheweb deleted the feature/aot-ext-2-hosting-extensions-setup branch September 30, 2026 02:39
@Aaronontheweb Aaronontheweb added this to the 1.6.0 milestone Sep 30, 2026
Aaronontheweb added a commit to Aaronontheweb/akka.net that referenced this pull request Sep 30, 2026
Review findings on the Hosting canary:
- The extension check always passed: WithExtension<T,TI>() resolves-or-creates, so marking it
  proved nothing. Require HasExtension<CanaryExtension>() before that call, which only reads.
- The health check accepted a report with zero entries. Require the 'akka.actorsystem' key
  WithActorSystemLivenessCheck() actually registers.
- Comments describing WithExtension<T>() as an akka.extensions/Type.GetType round-trip were
  stale since akkadotnet#8649, which moved it to ExtensionsSetup. Corrected in CanaryExtension.cs,
  Program.cs, and the README.
- WatchdogLoggerProvider now also accepts Information and waits for a dedicated marker line
  before the final ThrowIfAnyProblems, closing a race and independently proving Akka's logs
  reach Microsoft.Extensions.Logging - if the marker never arrives, the run fails.
- Dropped the CI self-test step and its fixture for this job: it only re-exercises the
  checker's "measured nothing" branch, which the core canary already covers.
- Dropped the unused RootAkka ItemGroup - this canary stays unrooted-only - and the
  now-redundant Akka/Akka.DependencyInjection ProjectReferences (both come in transitively
  through Akka.Hosting).
- Added a 300s timeout to the CI run step.

Cuts: removed NotifyActor/NotificationSink and AssertTellAsync (every Ask reply is already a
Tell), AddHocon/AssertHocon, and the redundant TrimmerSingleWarn CLI flag (set once in the
csproj). Shortened the README, YAML comments, csproj comments, and PrintFailure. Made
AssertExtensionAsync synchronous now that it has no await.
Aaronontheweb added a commit that referenced this pull request Sep 30, 2026
* AOT: Akka.Hosting canary app and CI job

Promotes the Akka.Hosting Native AOT prototype (spike/aot-hosting-canary) to a real canary
alongside the plain-core one:

- src/aot/Akka.Hosting.AOT.App: boots through Host.CreateApplicationBuilder + AddAkka(...),
  exercising AddHocon, a custom WithExtension<T>, WithActorSystemLivenessCheck, ConfigureLoggers'
  AddLoggerFactory, registry/DI-constructed actors, Tell, and DeathWatch. Self-terminates after its
  assertions pass instead of waiting on an external SIGINT/SIGTERM, so CI gets a deterministic exit.
  Added to Akka.slnx next to Akka.AOT.App.
- scripts/CheckAotWarnings.cs now takes --scope (comma-separated prefixes, default
  src/core/Akka/), so the same checker gates the Hosting canary's own baseline
  (src/aot/Akka.Hosting.AOT.App/aot-warnings.baseline.txt) against
  src/contrib/hosting/, src/contrib/dependencyinjection/, and src/core/Akka.Streams/ - the last
  because Hosting registers the stream-ref serializer at startup and nothing else watches that
  surface. src/core/Akka/ itself is left out; the plain-core canary's baseline already covers it.
  Three known Streams warnings (stream-ref MakeGenericType, SerializationTools.TypeFromString) are
  baselined with a pointer to #8667.
- build-system/pr-validation.yaml: new HostingAotCanary job, Linux, blocking. One unrooted publish
  serves both the run and the warning check - the three baselined Streams warnings already surface
  on that real boot, so a second rooted publish would not currently add coverage.

Verified locally: unrooted publish is clean from Hosting/DI and matches the expected 4 core + 3
Streams warnings; the app exits 0 and prints '[canary-hosting] OK'; the warning check passes
against the new baseline; a temporary Type.GetType probe added to
src/contrib/hosting/Akka.Hosting/AkkaHostingExtensions.cs (reverted) proved the check fails on a
new warning in scope.

* Hosting AOT canary: assertions that can fail, fewer lines

Review findings on the Hosting canary:
- The extension check always passed: WithExtension<T,TI>() resolves-or-creates, so marking it
  proved nothing. Require HasExtension<CanaryExtension>() before that call, which only reads.
- The health check accepted a report with zero entries. Require the 'akka.actorsystem' key
  WithActorSystemLivenessCheck() actually registers.
- Comments describing WithExtension<T>() as an akka.extensions/Type.GetType round-trip were
  stale since #8649, which moved it to ExtensionsSetup. Corrected in CanaryExtension.cs,
  Program.cs, and the README.
- WatchdogLoggerProvider now also accepts Information and waits for a dedicated marker line
  before the final ThrowIfAnyProblems, closing a race and independently proving Akka's logs
  reach Microsoft.Extensions.Logging - if the marker never arrives, the run fails.
- Dropped the CI self-test step and its fixture for this job: it only re-exercises the
  checker's "measured nothing" branch, which the core canary already covers.
- Dropped the unused RootAkka ItemGroup - this canary stays unrooted-only - and the
  now-redundant Akka/Akka.DependencyInjection ProjectReferences (both come in transitively
  through Akka.Hosting).
- Added a 300s timeout to the CI run step.

Cuts: removed NotifyActor/NotificationSink and AssertTellAsync (every Ask reply is already a
Tell), AddHocon/AssertHocon, and the redundant TrimmerSingleWarn CLI flag (set once in the
csproj). Shortened the README, YAML comments, csproj comments, and PrintFailure. Made
AssertExtensionAsync synchronous now that it has no await.

* AOT canaries: run the Hosting canary as steps in the existing AotCanary job

* Hosting AOT canary: empty baseline now that #8671 removed the stream-ref warnings
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