Skip to content

AOT: remove the Akka.Streams stream-ref reflection warnings - #8671

Merged
Aaronontheweb merged 3 commits into
akkadotnet:devfrom
Aaronontheweb:fix/aot-stream-ref-warnings
Sep 30, 2026
Merged

Aaronontheweb merged 3 commits into
akkadotnet:devfrom
Aaronontheweb:fix/aot-stream-ref-warnings

Conversation

@Aaronontheweb

@Aaronontheweb Aaronontheweb commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Summary

A Native AOT publish of any Akka.Hosting app hits three warnings from Akka.Streams, because Hosting references Akka.Streams and StreamRefSerializer (id 30) is always registered through the StreamsSerializers module table added in #8658 - regardless of whether the app ever exchanges a stream ref:

  • IL3050 in SinkRefImpl.Create (MakeGenericType)
  • IL3050 in SourceRefImpl.Create (MakeGenericType)
  • IL2057 in SerializationTools.TypeFromString (Type.GetType)

The element type genuinely drives the closed generic - Sink<T, NotUsed> / Source<T, NotUsed> and the (T)payload cast inside the stage logic both depend on it - so materializing as object would change behavior and was not an option. Instead this guards all three sites behind the existing Akka.DynamicTypeLoading feature switch, using the same if (AkkaFeatures.IsDynamicTypeLoadingSupported) { ... } else { throw ... } shape used elsewhere in core, with one dedicated message instead of AkkaFeatures.NotBuiltIn (that helper is worded for a HOCON setting with a built-in fallback table; there's no HOCON setting or built-in table here, so it read oddly):

Cannot deserialize a stream ref with element type [X]: stream refs need Akka.DynamicTypeLoading enabled at publish time (see #8667).

  • With the switch on (the default), nothing changes: same Type.GetType / MakeGenericType path, same wire format.
  • With it off, only receiving a stream ref (deserializing a SinkRef/SourceRef, alone or enclosed in a message) throws SerializationException. Sending one, and using one in-process, are unaffected - ToBinary only needs typeof(T), which the trimmer can always see.

Closes #8667.

Records the switch-off behavior change in the Breaking changes section below (there was no guard at all before this, so switch-off previously behaved the same as switch-on for stream refs; the shared BREAKING_CHANGES_V1.6.md ledger is updated separately in a batch PR), and files #8673 to track an opt-in element-type registry for applications that need to both disable the switch and exchange stream refs remotely.

Note on #8668: that PR is still open. Its Hosting canary baseline currently lists only these same 3 warnings, so once this PR merges and those warnings stop firing, #8668's baseline goes stale and CheckAotWarnings hits its "measured nothing" branch (exit 1) against an unrebased baseline. The plan is for this PR to merge first, then #8668 rebases on top of it and ships with a header-only baseline. This PR does not touch any #8668 files.

Test plan

  • dotnet build src/core/Akka.Streams -c Release - clean build
  • dotnet build src/core/Akka.Streams.Tests -c Release -warnaserror - clean, zero warnings
  • dotnet test src/core/Akka.Streams.Tests -c Release -f net10.0 --filter "FullyQualifiedName~StreamRef|FullyQualifiedName~StreamsSerializersSpec" - 24 passed, 1 pre-existing skip (unrelated), 0 failed. Includes one new end-to-end test: StreamRefSerializer.ToBinary on a SourceRefImpl<int> succeeds with the switch off, and FromBinary on the resulting bytes throws SerializationException.
  • dotnet format --verify-no-changes on the touched files - clean (the pre-existing whitespace issues in SourceRefImpl.cs predate this change and are untouched by it)
  • Verified the warnings with a throwaway console app (ProjectReference to Akka.Streams, TrimmerRootAssembly=Akka.Streams, Akka.DynamicTypeLoading=false, dotnet publish -r linux-x64, not committed): 18 warnings before this change, 15 after, with exactly these three gone and nothing else shifted

Breaking changes

Component Type Change Migration
Akka.Streams (serialization / stream refs) Behavior Guards the stream-ref reflection (SinkRefImpl.Create, SourceRefImpl.Create, SerializationTools.TypeFromString) behind Akka.DynamicTypeLoading, removing three IL2057/IL3050 warnings every Akka.Hosting app hit under Native AOT. Switch on (default): unchanged. Switch off -- including a JIT app using it as a strict mode -- only receiving a stream ref now throws SerializationException; sending one, and in-process use, still work. None at the default. An app that disables the switch and exchanges stream refs remotely needs #8673 once it ships, or must keep the switch on.

Akka.Streams' StreamRefSerializer is always registered through the akkadotnet#8658
module table, so a Native AOT publish of any Akka.Hosting app hit three
warnings regardless of whether the app ever exchanges a stream ref:
IL2057 in SerializationTools.TypeFromString, and IL3050 in
SinkRefImpl.Create and SourceRefImpl.Create.

Guards all three sites behind the existing Akka.DynamicTypeLoading
switch, matching the if (AkkaFeatures.IsDynamicTypeLoadingSupported) /
NotBuiltIn pattern used elsewhere in core. With the switch on (the
default), nothing changes - same Type.GetType/MakeGenericType path,
same wire format. With it off, deserializing a stream ref now throws a
clear SerializationException naming the element type and the switch,
instead of leaving trimmed-but-still-reachable reflection behind.

The element type genuinely drives Sink<T, NotUsed>/Source<T, NotUsed>
and the (T)payload cast in the stage logic, so materializing as object
was not an option - the guard is the honest fix here, not a rewrite.

Adds tests for the switch-off failure and confirms the existing
StreamRefsSpec suite still passes. Verified with a throwaway AOT
publish (Akka.Streams rooted via TrimmerRootAssembly): 18 warnings
before, 15 after, with exactly these three gone and nothing else
shifted.
- Replace AkkaFeatures.NotBuiltIn with a dedicated message for the
  stream-ref guard: that helper is worded for a HOCON setting with a
  built-in fallback table, and reads oddly here since there is neither.
  SerializationTools.StreamRefTypeNotSupported is the single source of
  truth for the three call sites.
- Consolidate the three switch-off unit tests into one end-to-end test
  through the real StreamRefSerializer: ToBinary on a SourceRefImpl<int>
  succeeds with the switch off (sending needs only typeof(T)), and the
  resulting FromBinary throws SerializationException (receiving needs
  Type.GetType). Drops the now-unused WithDynamicTypeLoadingOff helper.
- Halve the BREAKING_CHANGES_V1.6.md row, link PR akkadotnet#8671 in the PR
  column like the other rows, note that only receiving a stream ref
  fails (sending and in-process use still work), that a JIT app using
  the switch as a strict mode loses this too, and drop the inline
  "Closes akkadotnet#8667" - that belongs in the PR description, not the ledger.
- File akkadotnet#8673 (opt-in stream-ref element-type registry) as the tracked
  follow-up for an application that needs both the switch off and
  stream refs over the wire, and link it from the ledger migration
  text.
# 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

if (AkkaFeatures.IsDynamicTypeLoadingSupported)
return CreateGeneric(eventType, initialPartnerRef);

throw new SerializationException(SerializationTools.StreamRefTypeNotSupported(eventType.FullName ?? eventType.Name));

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.

reflection-based serialization won't work under AOT

}

[Fact(DisplayName = "StreamRefSerializer should serialize a SourceRef but fail to deserialize it When dynamic type loading is off")]
public void Should_serialize_but_not_deserialize_a_SourceRef_When_dynamic_type_loading_is_disabled()

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 1ec9e5e into akkadotnet:dev Sep 30, 2026
16 checks passed
@Aaronontheweb
Aaronontheweb deleted the fix/aot-stream-ref-warnings branch September 30, 2026 19:13
Aaronontheweb added a commit to Aaronontheweb/akka.net that referenced this pull request Sep 30, 2026
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

akka-streams AOT Ahead-of-Time (AOT) Compilation serialization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native AOT: 3 IL2xxx/IL3xxx warnings from the stream-ref serializer (Akka.Streams)

1 participant