From de5e24248223bb18206e7cad4850f2bd82a07d14 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 1 Oct 2026 15:52:18 +0000 Subject: [PATCH 1/4] Audit BREAKING_CHANGES_V1.6.md against v1.5.71 and catch up through #8698 Restate the definition (binary / source / behavioral compatibility against the last stable v1.5 release), drop rows that are not breaking under it (Artery-only, DynamicTypeLoading-off-only, fixes, dev-only comparisons), fold the TargetInvocationException and trim-annotation rows into one row each, shorten the rest, mark every row Merged with its PR, and add the breaking changes from #8694 and #8698. --- BREAKING_CHANGES_V1.6.md | 124 ++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 66 deletions(-) diff --git a/BREAKING_CHANGES_V1.6.md b/BREAKING_CHANGES_V1.6.md index de4874c8f56..84fe4836ac4 100644 --- a/BREAKING_CHANGES_V1.6.md +++ b/BREAKING_CHANGES_V1.6.md @@ -1,81 +1,73 @@ # Akka.NET v1.6 Breaking Changes -This document tracks **breaking changes** introduced into the `dev` branch during the -Akka.NET **v1.6** development cycle, ahead of a stable `v1.6.0` release. +This file lists the breaking changes on the `dev` branch during the Akka.NET **v1.6** +development cycle, ahead of a stable `v1.6.0` release. -A change is **breaking** — and therefore **must** be recorded here — if it alters any of: +## What counts as breaking -- **Behavior** — observable runtime semantics change (e.g. a message that used to do `X` - now does `Y`, or is now ignored). -- **Wire** — serialization or network-protocol compatibility with prior `1.x` releases - changes. -- **API** — a public type or member is removed, renamed, or has its signature/contract - changed (anything `Akka.API.Tests` would flag). +Compare against the **last stable v1.5 release** (`1.5.71` at the time of writing). A change +is breaking if it is not: -## Scope and lifecycle +- **Binary compatible**: an assembly compiled against v1.5 fails to load or bind (removed or + renamed public members, changed signatures, changed types). +- **Source compatible**: existing code stops compiling, or gets new errors or + warnings-as-errors the user can't avoid. New trim-analyzer warnings count only for apps + that run the trim analyzer; list them briefly. +- **Behaviorally compatible**: an existing app with its existing config sees different + runtime behavior: different results, exceptions where it used to work, different wire + output, different defaults, or a removed setting that used to have an effect. -- This ledger is maintained **only until a stable `v1.6.0` ships**. At that point its - contents are folded into the official release notes / upgrade guide and this file is - retired. -- Record the entry in the **same PR** that introduces the breaking change, so reviewers can - weigh the break alongside the code. -- A change still in flight may be listed with status `Planned` and its branch name; update - it to `Merged` with the PR link once it lands on `dev`. -- Bug fixes that merely restore documented/intended behavior are not "breaking" for this - list — but note them anyway if users may have come to depend on the old (incorrect) - behavior. +Not breaking, so not listed: behavior that only exists with `Akka.DynamicTypeLoading` off (the +switch is new in v1.6 and on by default; #8608 is listed because it turns the switch off for +you); fixes that make something work that used to fail, unless users plausibly relied on the +failure; differences from `dev` states that never shipped; internal refactors; new APIs. + +## Process and lifecycle + +- A PR that makes a breaking change says so in a `## Breaking changes` section of its + description. Maintainers batch those into this file. +- This file is kept **only until a stable `v1.6.0` ships**. Its contents then move into the + release notes / upgrade guide and the file is retired. ## Entry format -Newest first. Keep `Change` to a line or two; link the PR for detail. `Type` is one or more -of `Behavior`, `Wire`, `API` (combine with `+`). +Newest first, one row per change. Keep `Change` to a line or two and `Migration` to one; link +the PR for detail. `Type` is one or more of `Binary`, `Source`, `Behavior` (combine with `+`); +add `Wire` when the bytes on the wire change. -| Status | PR / Branch | Component | Type | Change | Migration | -|--------|-------------|-----------|------|--------|-----------| -| Planned / Merged | link or branch | `Akka.Xyz` | Behavior | one-line summary | what users must do | +| Status | PR | Component | Type | Change | Migration | +|--------|----|-----------|------|--------|-----------| +| Merged | link | `Akka.Xyz` | Behavior | one-line summary | what users must do | --- ## Changes -| Status | PR / Branch | Component | Type | Change | Migration | -|--------|-------------|-----------|------|--------|-----------| -| Planned | [#8666](https://github.com/akkadotnet/akka.net/pull/8666) | `Akka.Cluster` (serialization) | Behavior | Cluster.conf's two serializers (`ClusterMessageSerializer`, `ReliableDeliverySerializer`) are now built from an internal module table ahead of reflection, with `Akka.DynamicTypeLoading` on as well as off; the table calls each class's only constructor. With the switch on, two things differ: (1) a `serialization-settings.` block for either serializer now builds it instead of throwing `MissingMethodException`, since neither constructor takes a `Config`; (2) an exception from either constructor is no longer wrapped in `TargetInvocationException`. With the switch off, both rows -- and the binding for `Akka.Delivery.Internal.IDeliverySerializable`, a type in `Akka.dll` that Cluster's table resolves without reflection -- now resolve instead of throwing `ConfigurationException`. Same mechanism as #8658. | None. Code that caught `MissingMethodException` or `TargetInvocationException` around `ActorSystem.Create` for these serializers should catch the inner exception type instead. | -| Planned | [#8649](https://github.com/akkadotnet/akka.net/pull/8649) | `Akka.Hosting` | Behavior + API | `WithExtension()` and `WithExtensions(params Type[])` now pass extensions to the `ActorSystem` through an `ExtensionsSetup` instead of writing their type names into `akka.extensions`, and Hosting no longer reads `akka.extensions` itself. `Settings.Config.GetStringList("akka.extensions")` therefore no longer lists Hosting-registered extensions; a user-supplied `akka.extensions` reaches core unchanged, so an entry that does not resolve is now logged by core even when `WithExtension` is used (Hosting used to drop it silently), and an extension id whose constructor throws now fails startup with a `ConfigurationException` naming it, instead of being logged and skipped. `WithExtension` gains `[DynamicallyAccessedMembers(PublicParameterlessConstructor)]` on `T`; `WithExtensions(params Type[])` gains `[RequiresUnreferencedCode]`. | Code that inspects `akka.extensions` to find Hosting-registered extensions should read `Settings.Setup.Get()` or call `HasExtension()`. Trim/AOT apps should use `WithExtension()`. | -| Planned | [#8658](https://github.com/akkadotnet/akka.net/pull/8658) | `Akka.Remote` / `Akka.Streams` (serialization) | Behavior | Remote.conf's seven serializers and Akka.Streams' `StreamRefSerializer` are now built from internal module tables ahead of reflection, with `Akka.DynamicTypeLoading` on as well as off, and each table calls the class's only constructor. With the switch on, three things differ: (1) a `serialization-settings.` block for any of the six Remote serializers that take `(system)` or for `akka-stream-ref` now builds the serializer instead of throwing `MissingMethodException`; (2) an empty `serialization-settings.primitive {}` builds `PrimitiveSerializers` with `use-legacy-behavior` off instead of throwing `MissingMethodException`, and a config with no `primitive` block at all (no Remote.conf fallback) now throws `ConfigurationException("configuration is null")` instead; (3) an exception from one of these constructors is no longer wrapped in `TargetInvocationException`. With the switch off, these rows now resolve instead of throwing `ConfigurationException`. A versioned name for a different Akka.Remote/Akka.Streams version resolves both ways; reflection already accepted one (measured with `Version=0.9.0.0` and `Version=99.0.0.0`). | None. Code that caught `MissingMethodException` or `TargetInvocationException` around `ActorSystem.Create` for these serializers should catch the inner exception type instead. | -| Merged | [#8660](https://github.com/akkadotnet/akka.net/pull/8660) | `Akka.IO` | API | Removed `Tcp.ResumeWriting`, `Tcp.WritingResumed`, the `useResumeWriting` parameter and `UseResumeWriting` property on `Tcp.Register`, and the `useResumeWriting` parameter on `TcpMessage.Register` and `TcpMessage.ResumeWriting()`. This NACK-style write-suspend/resume mode was copied from JVM Akka's message shapes but never implemented in Akka.NET: `TcpConnection` never read `Register.UseResumeWriting`, never suspended writes, and never sent `WritingResumed`; `ResumeWriting` was a no-op. | Delete the `useResumeWriting` argument from `Register`/`TcpMessage.Register` calls and any `ResumeWriting` sends. Use ack-based writes (`Tcp.Write.Create(data, ack)`) for write backpressure instead. | -| Merged | [#8646](https://github.com/akkadotnet/akka.net/pull/8646) | `Akka.IO` | Behavior | A TCP `WriteAck` now means the connection's output pipe is back below its resume mark (32 KB by default, or `Inet.SO.PipeBufferSize` when set) after this write, so acks can arrive later under load; a single large write may overshoot the pause mark. While a flush is pending, later writes (ack or `NoAck`) are still copied straight into the pipe -- `write-commands-queue-max-size` still caps a single write -- and their acks wait for the next flush, which batches whatever arrived in the meantime. Closes #8617. | None for ack-driven writers. `NoAck` writers that need a memory cap should use acks. | -| Merged | [#8654](https://github.com/akkadotnet/akka.net/pull/8654) | `Akka` (core / `Akka.Event.LoggingBus`) | Behavior | Adds a first-party logger table to `LoggingBus.StartDefaultLoggers`, checked after the built-in table and before the `Type.GetType` fallback. Today it carries one row, Akka.Hosting's `LoggerFactoryLogger`, matched on type name and assembly with identity stripped (version, culture, public key token), the same as `TypeExtensions.ToBuiltInAkkaTypeName` does for built-in types (#8613). Two observable changes. (1) With `Akka.DynamicTypeLoading` on, a `LoggerFactoryLogger` name pinned to a different Akka.Hosting version -- what HOCON written against one Hosting build looks like once a newer build is loaded -- now resolves, where it previously fell through to `Type.GetType` and failed on the version mismatch. (2) With the switch off, the assembly-qualified name `AddLoggerFactory()` writes into `akka.loggers` now resolves instead of throwing, which is what the amended #8603 row below reflects. A name that matches neither table falls through exactly as before in both switch states. | None. | -| Merged | [#8613](https://github.com/akkadotnet/akka.net/pull/8613) | `Akka` (core) | Behavior | Every `BuiltIn*` type-name table (loggers, schedulers, mailboxes, message-queue semantics, routers, supervisor-strategy configurators, serializers and serialization bindings, the DNS provider) now carries one key per type instead of two, and the lookup normalizes the configured value through a new `TypeExtensions.ToBuiltInAkkaTypeName` that accepts every spelling `Type.GetType` itself accepted for an Akka.NET type -- any assembly identity, an optional space after the comma, and any casing of the `Akka` assembly name. With `Akka.DynamicTypeLoading` off, a value like `"Akka.Dispatch.BoundedMailbox,akka"` now resolves where it previously threw a `ConfigurationException`; with the switch on, behavior is unchanged. | None. | -| Merged | [#8608](https://github.com/akkadotnet/akka.net/pull/8608) | `Akka` (package MSBuild logic) | Behavior | The `Akka` package now ships `buildTransitive/Akka.targets`, which adds `` whenever the consuming project sets `PublishAot=true` or `PublishTrimmed=true`. Consumers publishing for Native AOT or trimming therefore get the switch **off** automatically, without knowing it exists: core resolves the type names its own `akka.conf` uses from the built-in tables, the trimmer removes every reflection fallback behind the switch, and any other HOCON type name throws a `ConfigurationException` naming the setting instead of failing obscurely. Ordinary untrimmed builds are untouched -- the condition does not match, no item is added, and the switch keeps its default of on. The change is observable for **any** trimmed or Native AOT publish. The affected population is applications that hand-rooted a HOCON-named type -- with `TrimmerRootAssembly`, `TrimmerRootDescriptor` or `DynamicDependency` -- and relied on `Type.GetType` then finding it; that worked under ILC as well as under ILLink (the rooted type survives and is resolvable at runtime; all you got was an `IL2057` warning at build time), and it now throws. | None for an untrimmed application. A trimmed-but-not-AOT application that relied on hand-rooting a custom logger, mailbox, dispatcher, router, serializer, scheduler or provider must either move that type to the corresponding `Setup` API, or opt back out by declaring the option itself -- `` -- which wins over the package's value. | -| Merged | [#8606](https://github.com/akkadotnet/akka.net/pull/8606) | `Akka.IO` | Behavior | UDP no longer pools byte buffers. `UdpExt` and `UdpConnectedExt` always construct `Akka.IO.Buffers.DisabledBufferPool` directly, reading only its `buffer-size` key from `akka.io.udp.disabled-buffer-pool` / `akka.io.udp-connected.disabled-buffer-pool` (each section's `class` key is gone); the `buffer-pool` key that used to point at either section and the `direct-buffer-pool` section itself are both gone from the shipped `akka.conf`, and the internal `Akka.IO.Buffers.DirectBufferPool` type is deleted along with the reflection (`Type.GetType` + `Activator.CreateInstance`) that used to build whichever pool `class` named. The public `IBufferPool`, `BufferPoolInfo` and `BufferPoolAllocationException` types are untouched, so nothing that only depends on the interface breaks. `akka.io.tcp.buffer-pool` / `direct-buffer-pool` / `disabled-buffer-pool` are also removed from `akka.conf` -- they were dead: `TcpExt` never read them (`TcpSettings.BufferPoolConfigPath` has been `[Obsolete("This property is unused")]` since before this change). | If you pointed `akka.io.udp(-connected).buffer-pool` at `direct-buffer-pool` with a larger `buffer-size` than the default 512 (or supplied your own `buffer-pool` section), move that `buffer-size` value to `disabled-buffer-pool.buffer-size` -- otherwise it silently reverts to 512 and UDP datagrams larger than that are truncated. Pooling itself cannot be re-enabled; open an issue at https://github.com/akkadotnet/akka.net/issues if you depended on it for GC pressure. Delete any `akka.io.tcp.*buffer-pool*` override from your HOCON; it already did nothing. | -| Merged | [#8606](https://github.com/akkadotnet/akka.net/pull/8606) | `Akka.IO` | Behavior | `DnsExt` resolves `akka.io.dns..provider-object` through a built-in table before falling back to reflection: the built-in `InetAddressDnsProvider` is constructed directly (as are `SimpleDnsManager` and `InetAddressDnsResolver`, its manager/resolver pair), in every spelling the other built-in tables accept. `provider-object` keeps its meaning and stays configurable -- a custom provider still loads through `Type.GetType` + `Activator.CreateInstance` while `Akka.DynamicTypeLoading` is on, exactly as before. With the switch **off**, a `provider-object` that does not name the built-in provider now throws `ConfigurationException` from `AkkaFeatures.NotBuiltIn` naming the setting, the value and the switch, instead of being resolved by reflection. | 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 leave `provider-object` unset or pointing at `Akka.IO.InetAddressDnsProvider`. | -| Merged | [#8605](https://github.com/akkadotnet/akka.net/pull/8605) | `Akka` (core / routers, supervision, dispatchers) | Behavior | Four more HOCON type-name sites gain a built-in table and/or put their `Type.GetType` fallback behind the `Akka.DynamicTypeLoading` switch: `akka.actor.router.type-mapping`, `akka.actor.guardian-supervisor-strategy` (and Akka.Persistence's per-plugin `supervisor-strategy`), a dispatcher's `type`, and a dispatcher's `executor`. Both tables use the same normalized lookup as the other built-in tables (see the #8601 row). With the switch on the same config resolves to the same type as before. With it off, a name that is not built in throws `ConfigurationException` from `AkkaFeatures.NotBuiltIn` naming the setting, the value and the switch. | None at the default -- the switch is on unless a project file explicitly turns it off. With it off, these four settings must keep the values core's `akka.conf` ships; Akka.Cluster.Metrics routers (`cluster-metrics-adaptive-pool`/`-group`) and Akka.TestKit's calling-thread dispatcher are named by type and need the switch on. | -| Merged | [#8605](https://github.com/akkadotnet/akka.net/pull/8605) | `Akka` (core / `Props.Create` expression parsing) | Behavior | `ExpressionBasedParser`'s fallback for a constructor argument it cannot read statically now compiles a typed `Expression.Lambda>` and invokes it directly instead of the non-generic overload plus `Delegate.DynamicInvoke`. The value is identical; an argument expression that throws while being evaluated now surfaces its own exception rather than a `TargetInvocationException`. | Code that read `ArgumentException.InnerException` after a failed `Props.Create(() => new MyActor(...))` should unwrap one `TargetInvocationException` less. | -| Merged | [#8604](https://github.com/akkadotnet/akka.net/pull/8604) | `Akka.Serialization` | Behavior | `SerializerIdentifierHelper.GetSerializerIdentifierFromConfig` matches `akka.actor.serialization-identifiers` keys against the name of the `Type` in hand instead of calling `Type.GetType(key, throwOnError: true)` on every key. Fixes one unloadable key in the block taking `ActorSystem.Create` down with it; narrows matching to string comparison, and widens it by ignoring the assembly identity and by letting a bare `FullName` key match a type in any assembly. | Spell keys `"Namespace.Serializer, AssemblyName"`, optionally with the `Version`/`Culture`/`PublicKeyToken` tail. Prefer assembly-qualified keys: two serializers with the same `FullName` in different assemblies would now share whichever bare key matches first, which corrupts the wire. | -| Merged | [#8604](https://github.com/akkadotnet/akka.net/pull/8604) | `Akka.Serialization` | Behavior | `Serialization..ctor` registers the `bytes`/`json` serializers and the `System.Byte[]`/`System.Object` bindings from built-in tables instead of `Type.GetType`, falling back to reflection while `Akka.DynamicTypeLoading` is on and throwing a `ConfigurationException` when it is off. With the switch off core does not register `json` at all, so a type with no binding of its own has no fallback serializer. A built-in serializer is also constructed directly now. | None at the default. An application that turns `Akka.DynamicTypeLoading` off must register a serializer for `System.Object` (or per message type) through a `SerializationSetup`; pointing the existing `"System.Object"` binding at that alias works. A `SerializationSetup` also covers the HOCON rows it takes over, such as a module's `reference.conf` rows: a serializer row for an alias the Setup registers is skipped (its `serialization-settings` block goes unused), and a binding row resolves if it names one of the Setup's `UseFor` types by full name, with its assembly half absent or equal to the type's assembly simple name (case-insensitive). Any other row core can't resolve still throws. Code catching `TargetInvocationException` around a serializer constructor should catch the inner exception type. | -| Merged | [#8603](https://github.com/akkadotnet/akka.net/pull/8603) | `Akka` (core / `Akka.Actor.Props`, `Akka.Event.LoggingBus`), `Akka.DependencyInjection` | Behavior + API | Two changes. (1) The whole `Props` actor-type flow now carries one trimming contract, `[DynamicallyAccessedMembers(PublicConstructors \| Interfaces)]`, so the trimmer keeps the constructor `Activator` calls and the `IRequiresMessageQueue<>` interfaces the mailbox requirement check reads off the actor type: the `Type`-taking `Props` constructors, `Props.Type`, `Props.Create(Type, params object[])`, all three `Props.Create` overloads, `Props.CreateBy`, `IIndirectActorProducer.ActorType`, `ActorRefFactoryExtensions.ActorOf`, `ExtendedActorSystem.SystemActorOf` (and the `ActorSystemImpl` override), plus -- in `Akka.DependencyInjection` -- the three `IDependencyResolver.Props` members and their implementations on `DependencyResolver`, `ServiceProvider` and `ServiceProviderDependencyResolver`, and the internal `ServiceProviderActorProducer`. Separately, `ActorSystemWithExtensions.WithExtension(ActorSystem, Type)` and `WithExtension` gain `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]` -- the narrower contract, because those take an `IExtensionId`, not an actor type, and call the parameterless `Activator.CreateInstance`. No runtime behavior changes; the attributes are visible only to trim analysis and change no signature a compiler binds to. (2) `LoggingBus.StartDefaultLoggers` resolves `akka.loggers` from new built-in name tables (all three loggers Akka.dll ships -- `Akka.Event.DefaultLogger`, `Akka.Event.StandardOutLogger` and `Akka.Event.TraceLogger` -- each under three spellings: the bare name core's own `akka.conf` ships, the short assembly-qualified name that `TraceLogger`'s own documentation tells users to write, and the full versioned assembly-qualified name Akka.Hosting writes) before falling back to `Type.GetType`, and that fallback only runs while dynamic type loading is on. With the switch on, which is what every existing application gets, the same config resolves to the same logger as before, for every input. With the switch off, only the names in the table resolve: any other logger throws a `ConfigurationException` naming `akka.loggers`, the value and the switch, where it previously threw `ConfigurationException` "Logger specified in config cannot be found". | 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 leave `akka.loggers` at the built-in loggers or Akka.Hosting's `LoggerFactoryLogger`. The new attributes need no source change, and nothing warns unless a project sets `EnableTrimAnalyzer` / `IsTrimmable` / `IsAotCompatible` -- no project in this repository does yet. Once one does: passing an unannotated `Type` into any of these members reports `IL2072` when it came from a method return or a field, and `IL2067` when it came from one of the caller's own parameters; an unannotated generic argument reports `IL2091`. Fix either by annotating the `Type` or type parameter it flows from with `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors \| DynamicallyAccessedMemberTypes.Interfaces)]`, or by passing a `typeof(...)`. Because the attribute is part of the contract, an *implementor* has to repeat it verbatim rather than flow it: a class implementing `IIndirectActorProducer.ActorType` or any of the three `IDependencyResolver.Props` members reports `IL2092` / `IL2093` (parameter and return annotation mismatch) or `IL2095` (type-parameter mismatch) until it carries the identical attribute, and so does an override of `ExtendedActorSystem.SystemActorOf`. | -| Merged | [#8602](https://github.com/akkadotnet/akka.net/pull/8602) | `Akka` (core / `Akka.Dispatch.Mailboxes`) | Behavior + API | `Mailboxes` resolves the mailbox names core's own `akka.conf` ships from two built-in tables (`BuiltInMailboxTypes`, `BuiltInMessageQueueSemantics`) instead of `Type.GetType`, so the trimmer and the Native AOT compiler can see which type is loaded. Two observable changes. (1) A built-in `mailbox-type` is constructed directly, so when its constructor rejects its config the `ArgumentException` from `Mailboxes.Lookup` carries that constructor's own exception as `InnerException` instead of the `TargetInvocationException` `Activator.CreateInstance` wrapped it in, and names the configured spelling (e.g. `Akka.Dispatch.BoundedMailbox, Akka`) where it previously named `Type.ToString()`; relatedly, the non-zero-push-timeout warning now runs outside that `try`, so an exception from a custom `IProducesPushTimeoutSemanticsMailbox.PushTimeout` getter propagates as itself rather than wrapped. (2) With the `Akka.DynamicTypeLoading` switch **off**, a key under `akka.actor.mailbox.requirements` that is neither built in nor resolvable now throws a `ConfigurationException` out of `ActorSystem.Create`, where the switch-on path still logs a warning and skips that one binding; an unknown `mailbox-type` or `mailbox-requirement` likewise throws a `ConfigurationException` naming the setting, the value and the switch, in place of the previous `ConfigurationException` / `TypeLoadException`. With the switch on - the default, and what every existing application gets - every other input resolves to the same mailbox as before. Separately, `MailboxType` and the `Type` parameters of `Mailboxes.HasRequiredType`, `Mailboxes.ProducesMessageQueue` and `Mailboxes.GetRequiredType` gain `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]` so that trimming keeps `IProducesMessageQueue<>` / `IRequiresMessageQueue<>` on subclasses. | None at the default -- the switch is on unless a project file explicitly turns it off. Code that caught `TargetInvocationException` around a rejected `mailbox-capacity` / `mailbox-push-timeout-time` should catch the inner exception type instead, and code asserting on the exact `Cannot instantiate MailboxType ...` text should expect the configured spelling for the five built-in types. A trimmed or Native AOT application that turns the switch off must keep `mailbox-type`, `mailbox-requirement` and every key under `akka.actor.mailbox.requirements` at built-in values. The new attributes are visible only to trim analysis -- they change no signature a compiler binds to -- but a caller that has trim analysis enabled and passes an unannotated `Type` into those three methods now gets `IL2072` at its own call site: annotate the `Type` it flows from with `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]`, or pass a `typeof(...)`. | -| Merged | [#8601](https://github.com/akkadotnet/akka.net/pull/8601) | `Akka` (core / actor system bootstrap) | Behavior | Adds the internal trimming / Native AOT feature switch `Akka.DynamicTypeLoading` (default **on**) and puts built-in lookup tables in front of the first three HOCON type-name sites: `akka.stdout-logger-class` and `akka.logger-formatter` in `Settings`, and `akka.scheduler.implementation` in `ActorSystemImpl.ConfigureScheduler`. Each site now checks a `BuiltIn*` table before falling back to `Type.GetType`. Each table originally carried two spellings of every name it knows -- the bare `Ns.T` and the `Ns.T, Akka` form, which is what core's own `akka.conf` uses (the #8613 row collapses this to one key per type) -- and the lookup first runs the configured value through the new internal `Akka.Util.TypeExtensions.StripAssemblyIdentity`, which removes the `Version=`, `Culture=`, `PublicKeyToken=`, `ProcessorArchitecture=`, `Retargetable=` and `ContentType=` components. With the switch on, which is what every existing application gets, the same config resolves to the same type as before, with two narrow differences: (1) a built-in name is constructed directly, so a constructor that rejects its config (e.g. `akka.scheduler.tick-duration` below 10 ms, which `HashedWheelTimerScheduler` refuses) now surfaces its own `ArgumentOutOfRangeException` / `ConfigurationException` instead of the `TargetInvocationException` that `Activator.CreateInstance` wrapped it in; (2) a built-in type name written with a full assembly-qualified name of a different Akka version (what `Akka.Hosting` writes into HOCON, carried across an upgrade) now resolves where it previously threw. `Type.GetType` rejects the version mismatch, so on `dev` such a value fails in both switch states -- measured: `ArgumentException("Could not load type of ...")` for `akka.logger-formatter`/`akka.stdout-logger-class`, and `FileNotFoundException` for `akka.scheduler.implementation`, whose `Type.GetType` call passes `throwOnError: true`. Custom (non-built-in) names are unchanged, since the reflection fallback still receives the raw value. An application that turns the switch off with `` gets only the names in the tables: anything else throws a `ConfigurationException` naming the setting, the value and the switch, where it previously threw `TypeLoadException` (scheduler) or `ArgumentException` (stdout logger, log formatter). | None at the default -- the switch is on unless a project file explicitly turns it off. Code that caught `TargetInvocationException` around `ActorSystem.Create` to read a rejected `akka.scheduler.*` value should catch the inner exception type instead. A trimmed or Native AOT application that turns the switch off must leave these three settings at their built-in values. | -| Planned | `fix/aot-provider-constant-type-names` | `Akka` (core / actor system bootstrap) | Behavior | `ActorSystemImpl` now resolves the three built-in actor ref providers from the compile-time constant type names 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. Two observable consequences: (1) `ProviderSelection.GetProvider` now maps the local provider's type name -- both the bare `"Akka.Actor.LocalActorRefProvider"` that ships in `akka.conf` and the assembly-qualified `"Akka.Actor.LocalActorRefProvider, Akka"` -- onto `ProviderSelection.Local` instead of letting it fall through to `Custom`, so `Settings.ProviderClass` on a default boot now reports `"Akka.Actor.LocalActorRefProvider, Akka"` where it previously reported the bare `"Akka.Actor.LocalActorRefProvider"`. (2) `Settings` only validates `ProviderClass` through `Type.GetType` when the selection is `ProviderSelection.Custom`; the built-in three are validated where they are constructed, and a missing `Akka.Remote` / `Akka.Cluster` reference now throws `ConfigurationException("akka.actor.provider = cluster, but Akka.Cluster is not referenced by this application.")` from `ActorSystemImpl` rather than the generic "not a valid type name" message from `Settings`. Which provider gets constructed for any given config is unchanged. | None for normal use. Code that compares `Settings.ProviderClass` against the bare string `"Akka.Actor.LocalActorRefProvider"` should compare against `ProviderSelection.Local.Instance.Fqn`, or test `Settings.ProviderSelectionType is ProviderSelection.Local`. Code that asserted on the exact text of the misconfiguration exception for `provider = remote` / `provider = cluster` should expect the new message. | -| Planned | `feature/hosting-import-dev` (forward-port of [#8591](https://github.com/akkadotnet/akka.net/pull/8591)) | `Akka.Hosting`, `Akka.Remote.Hosting`, `Akka.Cluster.Hosting`, `Akka.Persistence.Hosting`, `Akka.Hosting.TestKit`, `Akka.Hosting.TestKit.Xunit2` | API | The Akka.Hosting packages now ship from this repository at the Akka.NET version. On 1.6 they target `net10.0` only (previously `netstandard2.0;net6.0`), matching the rest of Akka.NET 1.6. `Microsoft.Extensions.*` floor moves to 10.0.0 for `Akka`, `Akka.DependencyInjection` and the Hosting packages. | Reference `Akka.Hosting.*` at the same version as `Akka.*`. Applications must target `net10.0` or later, as for every other Akka.NET 1.6 package. | -| Planned | `fix/testkit-xunit-async-dispose-chain` | `Akka.TestKit.Xunit` | API + Behavior | `Akka.TestKit.Xunit.TestKit` now also implements `Xunit.IAsyncLifetime`, adding `virtual ValueTask InitializeAsync()` and `virtual ValueTask DisposeAsync()`. This closes #8191: xUnit v3 tears a test class instance down via `IAsyncDisposable.DisposeAsync()` in preference to `IDisposable.Dispose()` whenever a type implements both, so a derived spec that declared its own `IAsyncLifetime.DisposeAsync()` (to run its own async teardown) short-circuited `TestKit.Dispose(bool)` entirely and silently leaked its `ActorSystem` -- DotNetty threads, a bound socket, and a `PeriodicTimer`-driven scheduler included. `TestKit.DisposeAsync()` now runs the synchronous dispose chain (`AfterAll()` etc.) and then shuts the system down with the non-blocking `ShutdownAsync()` instead of the blocking `Shutdown()`. | A derived class that already declares its own `InitializeAsync`/`DisposeAsync` (whether via `IAsyncLifetime` directly or by re-declaring the method) now hides a virtual member of the same name and gets `CS0114`, which is an error under this repo's own `-warnaserror` build. Add `override` to each and chain to the base (`await base.InitializeAsync();` / `await base.DisposeAsync();`), and drop the redundant `IAsyncLifetime` interface declaration and any no-op `DisposeAsync` that only returned a completed `ValueTask`. | -| Planned | `fix/8508-clusterclient-reconnect-timer` | `Akka.Cluster.Tools` | API + Behavior | `ClusterClient` now implements `Akka.Actor.IWithTimers` and manages its reconnect, heartbeat and refresh-contacts timers through the keyed timer scheduler. The reconnect deadline is armed once per entry into the establishing phase (`PreStart` and re-establish) instead of on every message handled while establishing, which is the fix for #8508: the old code abandoned one one-shot timer per message and cancelled only the last, so a leftover firing after the client returned to establishing stopped a healthy client mid-reconnect, long before its own deadline was due. API surface: the class gains the `IWithTimers` interface and a public `Timers` property; the `PostStop` override is gone because the timer scheduler cancels timers on stop and restart. `reconnect-timeout = off` (the default) remains a true no-op and arms nothing. Secondary behavior change: because the timers are keyed, a `RefreshContactsTick` still queued from the previous cadence is discarded when the interval changes on a state transition, rather than being delivered against the new cadence. | None. `ClusterClient` is `sealed`, so the added interface and property cannot break a subclass, and the removed `PostStop` override was `protected` on that sealed class and therefore unreachable from outside. Deployments that had enabled `akka.cluster.client.reconnect-timeout` should simply stop seeing clients stopped early during receptionist handover. | -| Planned | [#8324](https://github.com/akkadotnet/akka.net/pull/8324) | `Akka.Routing` | Behavior | `ConsistentHash` no longer retains the `SortedDictionary` passed to its public constructor — the ring is snapshotted into internal sorted arrays. Mutating that dictionary after construction no longer affects the instance (previously the aliasing was inconsistent: `IsEmpty` and `operator +`/`-` read it live, while `NodeFor` froze it after the first lookup). A `null` dictionary now throws `ArgumentNullException` from the constructor instead of surfacing later as a `NullReferenceException`. No public API removed; the ring built by `ConsistentHash.Create` is byte-identical. (#8293) | None for normal use — `ConsistentHash.Create` already builds the dictionary fully before constructing, so routers/receptionists are unaffected. If you call the `ConsistentHash(SortedDictionary, int)` constructor directly, populate the dictionary before passing it and don't rely on post-construction mutation being visible. | -| Planned | `fix/gossip-removal-tombstones` | `Akka.Cluster` | Wire + Behavior | Cluster gossip now carries a removal tombstone for every member the leader removes (new `repeated Tombstone tombstones = 7` on the `Gossip` proto message, previously the one vacant field number). A member the leader removed can no longer be put back into the gossip by a peer that has not caught up, which previously blocked convergence permanently and needed a full cluster restart to clear. Tombstones expire after the new `akka.cluster.prune-gossip-tombstones-after` setting (default `24h`), pruned by the leader on a converged tick and reclaimed for good: gossip reception keeps the tombstones of whichever gossip wins the causal comparison, so a peer that has not pruned yet cannot hand a pruned tombstone back. Tombstones are only unioned across the two gossips when neither descends from the other -- equal clocks, or concurrent clocks, where each side may hold a removal the other has not heard about. The field is additive and proto3 ignores unknown fields in both directions, so rolling upgrades are safe; an older node parses the gossip fine but drops the tombstones when it re-emits it, and if that stripped gossip is causally newer than an upgraded node's, the upgraded node adopts it and loses those tombstones too -- so the protection only holds between upgraded nodes and the fix is not fully in effect until every node is upgraded. | No action required. Raise `akka.cluster.prune-gossip-tombstones-after` if the cluster must survive partitions longer than 24 hours, or lower it if a cluster with heavy join/leave churn needs the gossip message kept small -- each tombstone adds a full address plus a timestamp to every gossip message until it expires. | -| Planned | `feature/default-bounded-shard-rebalancing` | `Akka.Cluster.Sharding` | Behavior | The default `rebalance-absolute-limit` is now `20`, selecting the bounded shard allocation strategy instead of the legacy threshold-based strategy. | To retain the legacy strategy temporarily, explicitly set `akka.cluster.sharding.least-shard-allocation-strategy.rebalance-absolute-limit = 0`. Review `rebalance-threshold` and `max-simultaneous-rebalance`, which do not apply while the bounded strategy is active. | -| Planned | `fix/artery-shutdown-flush` | `Akka.Remote` (Artery) | Behavior | Graceful transport shutdown now flushes. `ArteryRemoting.Shutdown()` completes each association's outbound channels, waits up to the new `akka.remote.artery.advanced.flush-wait-on-shutdown` (default `2 s`, mirroring classic remoting's `akka.remote.flush-wait-on-shutdown`) for the outbound streams to finish writing what they had already accepted, and only then drains the remainder to `Dropped`. Previously it completed and drained in the same breath, so a message accepted microseconds before shutdown -- an ack, a graceful notice, a handshake reply -- was published as `Dropped` instead of reaching the socket. Two consequences: graceful shutdown may now take up to `flush-wait-on-shutdown` longer per transport (only when a backlog cannot reach its peer; an association with nothing left to write completes at once), and messages that previously surfaced as `Dropped` may now be delivered. Abrupt termination (materializer death without a graceful `Shutdown()`) is unchanged. **Takes effect starting with `fix/artery-control-backoff-and-held-element` (#8554)**, which hosts the transport's materializer under `/system` (`ArteryRemoting.CreateSystemMaterializer`) so its `StreamSupervisor` survives `/user` guardian teardown instead of being killed by it; before #8554 the outbound streams were already dead by the time this flush ran (killed out from under a live graph interpreter during ordinary `/user` teardown, well before `Shutdown()` itself executed), so this row's two consequences were never actually observable on `dev`. That same `/system` move adds two further consequences not previously listed here: the `AbruptStageTerminationException` warning that used to fire on every graceful `ActorSystem.Terminate()` (because the stream was killed live) no longer fires, since the stream now completes gracefully instead; and a terminating node's INBOUND connections also survive `/user` teardown, so traffic arriving late during shutdown is now drained and dead-lettered rather than met with a connection reset. | No action required. Set `akka.remote.artery.advanced.flush-wait-on-shutdown = 0` to restore the previous drain-immediately behavior, or lower it to cap how long shutdown may wait. Monitoring that counts shutdown-time `Dropped` events will see fewer of them. Log-scraping or alerting keyed on the `AbruptStageTerminationException`/"outbound stream has ended" warning firing during ordinary graceful shutdown will go quiet -- that is expected, not a regression. Peers that inferred an in-flight terminate from a reset connection will instead observe a clean, drained FIN. | -| Planned | `fix/artery-inbound-quarantine-check` | `Akka.Remote` (Artery) | Behavior | Quarantine is now enforced on the INBOUND path too. Previously Artery only gated outbound sends -- an envelope arriving FROM a uid this system has quarantined was still delivered, and the quarantined peer was only notified once, proactively, at the moment `Quarantine()` was called. A new `InboundQuarantineCheckStage`, woven into the inbound pipeline right after handshake, now drops every inbound envelope (ordinary or control, including system messages) whose origin uid is quarantined, and reactively re-sends a `Quarantined` control notice to the origin for each drop (except for a heartbeat or the peer's own `Quarantined` notice, to avoid a reply storm). The existing one-shot proactive notice in `Quarantine()` is unchanged. Additionally, an ordinary/large outbound stream that terminates while its association is quarantined no longer wedges permanently: the materialize-once gate is released (timer-driven auto-reconnect stays suppressed), so a quarantine-piercing `ActorSelection` send -- or any send after a new incarnation's handshake lifts the quarantine -- re-materializes the stream on demand and can reach a restarted peer at the same address. | No action required -- this is a bug fix restoring the documented "no further communication" guarantee of quarantine and the documented new-incarnation piercing behavior; code that (incorrectly) depended on a quarantined peer's replies still arriving is unsupported. | -| Planned | `fix/artery-daemonmsgcreate-control-stream` | `Akka.Remote` (Artery) | Behavior | Remote deployment's `DaemonMsgCreate` now travels over Artery's CONTROL stream (as a plain envelope, no delivery/ack sequencing) instead of the ordinary stream, ordering it ahead of the `Watch` that remote deployment sends immediately afterwards. Previously the two rode independent, unordered TCP connections and `Watch` systematically arrived first, so the receiver replied `DeathWatchNotification(existenceConfirmed: false)` for a not-yet-created actor and the deployer reaped the freshly-deployed routee before its `Supervise` registration landed, emptying cluster router pools. Additionally (Pekko parity), inbound ordinary messages addressed to a remote-deployed recipient that has not been created yet are no longer dead-lettered immediately: the resolve is retried on a bounded schedule (20 attempts x 50ms, buffered per recipient in FIFO order) so first messages that arrive ahead of the in-flight `DaemonMsgCreate` are delivered once the actor exists; paths that never resolve are banned and dead-letter as before. | No action required -- this is a bug fix restoring correct create-before-watch ordering; code that (incorrectly) depended on the old race is unsupported. | -| Planned | `feature/artery-test-mode` | `Akka.Remote` | Behavior | System UID generation (`AddressUidExtension` / `AddressUid`) now uses a cryptographic RNG instead of the `Environment.TickCount`-seeded `ThreadLocalRandom`. Previously, multiple processes started within the same millisecond tick (e.g. every node of a multi-node test spawning at once) could draw IDENTICAL system UIDs, silently corrupting Artery's uid-keyed identity (handshakes, quarantine, association reverse index). UIDs remain nonzero and in the legacy `[1, int.MaxValue]` range at the default (`use-64bit-system-uids = off`). | Nothing required -- UIDs were always documented as random; only code depending on the (buggy) time-seeded determinism could observe a difference. | -| Planned | `feature/artery-test-mode` | `Akka.Remote` (Artery) / `Akka.Remote.TestKit` | Behavior | New `akka.remote.artery.advanced.test-mode` setting (default `off`, Pekko key parity) enables failure-injection test stages in Artery's stream pipelines, and `ArteryRemoting.ManagementCommand` now honors `SetThrottle` with `Blackhole`/`Unthrottled` (returning `true`; previously always `false`) so TestConductor `Blackhole`/`PassThrough` work over Artery. Rate throttles (`TokenBucket`) and `ForceDisassociate` remain unsupported on Artery (still `false`, matching Pekko). `MultiNodeConfig.TestTransport = true` now also sets Artery test-mode on, and the TestKit `Player` now treats a `false` `ManagementCommand` result as a failure (previously reported success for a silently ignored command). | Nothing at the default (`test-mode = off` composes byte-identical pipelines). Test code that relied on `ManagementCommand` always returning `false` on Artery, or on the Player treating `false` as success, must account for the new semantics. | -| Planned | `fix/artery-outbound-lanes-robustness` | `Akka.Remote` (Artery) | Behavior | On transport shutdown, messages still buffered in an association's outbound queues (ordinary lanes, control, large) at drain time are now DRAINED and published as `Dropped` events to the `EventStream`, one per message. Previously they were silently stranded in the completed channels -- never delivered, never surfaced anywhere. Scope: only queue-resident messages are accounted; the bounded handful already pulled into the stream stages when shutdown lands may still be lost without a `Dropped` (ordinary delivery is at-most-once by contract). | Nothing required. Monitoring that subscribes to `Dropped` (or watches the `DeadLetterListener` log summaries) will now see shutdown-time drops that were previously invisible; treat them as accounting of pre-existing loss, not new loss. | -| Planned | `feature/artery-outbound-lanes` | `Akka.Remote` (Artery) | Behavior | `akka.remote.artery.advanced.outbound-lanes` is now actually implemented (previously parsed but ignored -- every value behaved as 1). Each association fans ordinary-stream sends across N bounded lanes, keyed by a hash of the recipient's UID, only when this value is explicitly raised above `1`; all lanes still merge into the association's single TCP connection. Per-recipient ordering is preserved. `outbound-message-queue-size` now applies PER LANE (matching Pekko), so raising `outbound-lanes` multiplies an association's total ordinary-outbound buffering. `ActorSelection` messages and recipients with an unresolved UID always ride lane 0. | No action required at the default. If you had previously set `outbound-lanes` > 1 in your own config expecting it to do something, it now genuinely enables parallel outbound encoding -- revert to `1` if you relied on its prior no-op behavior, and account for the per-lane queue sizing. | -| Planned | `feature/artery-inbound-lanes` | `Akka.Remote` (Artery) | Behavior | `akka.remote.artery.advanced.inbound-lanes` is now actually implemented (previously parsed but ignored -- every value behaved as 1). Its shipping default is changed from `4` to `1`, so out-of-the-box behavior is unaffected. An accepted Ordinary-stream connection now fans ordinary-message deserialization/dispatch out across N per-lane bounded queues + consumer loops, keyed by a stable hash of the recipient path (or, for `ActorSelectionMessage`, the selection's own target path elements), only when this value is explicitly raised above `1`; a new `akka.remote.artery.advanced.inbound-lane-buffer-size` (default `4096`) sizes each lane's queue. Per-recipient ordering is preserved. | No action required at the default. If you had previously set `inbound-lanes` > 1 in your own config expecting it to do something, it now genuinely enables parallel inbound processing -- revert to `1` if you relied on its prior no-op behavior. | -| Planned | `fix/cluster-nonblocking-startup` | `Akka.Cluster` | Behavior | Cluster extension startup no longer blocks on an internal 20s ask bounded by `akka.actor.creation-timeout`; `Cluster.Get()` returns immediately and core initialization completes asynchronously. The "Failed to startup Cluster" ask-timeout failure mode (which could terminate a healthy node under thread starvation) is removed; genuine core-startup failures still shut the node down via supervision. User-issued cluster commands (`Subscribe`, `Join`, `Leave`, `Down`, ...) route through `/system/cluster` for the entire lifetime of the extension, rather than switching to a direct reference to the resolved core daemon once one becomes available, so that commands issued back-to-back by the same caller keep their relative order (two extra local mailbox hops on a low-traffic control plane; peer gossip, heartbeats, and `ExitingConfirmed` address the daemon actors directly and are unaffected). | None for typical code — all public APIs are message-based or awaitable. Code that used `Cluster.Get()` as a "core is started" synchronization point should await `JoinAsync`/`RegisterOnMemberUp` or observe cluster state. | -| Planned | `fix/artery-pipe-watermarks` | `Akka.Remote` (Artery) | Behavior | Artery TCP connections now raise the pause/resume watermarks of their internal read pipe to 1 MiB (pause 2 MiB), via a new opt-in `Akka.IO.Inet.SO.PipeBufferSize` socket option consulted by `TcpIncomingConnection`/`TcpOutgoingConnection`, configurable via `akka.remote.artery.advanced.tcp.pipe-buffer-size` (default `1m`). Akka.IO's own default (~16 KiB, derived from `akka.io.tcp.receive-buffer-size`) is unchanged for every other TCP connection. | No action required. Artery connections use somewhat more memory per connection and see higher throughput under high-in-flight/one-way traffic; tune via `akka.remote.artery.advanced.tcp.pipe-buffer-size` if needed. | -| Merged | [#8317](https://github.com/akkadotnet/akka.net/pull/8317) | `Akka.Remote` / `Akka.Cluster` | API | System/address UID widened `int` → `long` across `AddressUid.Uid`, `AddressUidExtension.Uid()`, `UniqueAddress(Address, int)`/`.Uid`, `QuarantinedEvent(Address, int)`/`.Uid`, `RemoteWatcher.HeartbeatRsp(int)`/`.AddressUid`, and all `Quarantine(Address, int? uid)` members (`IRemoteActorRefProvider` / `RemoteTransport` / `RemoteWatcher`). Hard re-type (v1.6). Prerequisite for Artery. | Recompile against the `long` members; replace `int` uid locals with `long`. Default UID generation stays in 32-bit range; full 64-bit generation is opt-in via `akka.remote.use-64bit-system-uids = on`. | -| Merged | [#8317](https://github.com/akkadotnet/akka.net/pull/8317) | `Akka.Cluster` | Wire | Cluster gossip `UniqueAddress.uid` widened `uint32` → `uint64` (`ClusterMessages.proto`). Same varint wire type → binary-compatible for uids ≤ `uint32.MaxValue`. | Rolling upgrade safe provided uid generation stays in 32-bit range (the default); enable `akka.remote.use-64bit-system-uids` only after the whole cluster + all remote peers are on v1.6. | -| Planned | `fix/artery-queue-and-shutdown-robustness` | `Akka.Remote` (Artery) | Behavior | Artery's control-queue default capacity raised from 256 to 20000 (matches Pekko's `outbound-control-queue-size`) to stop a mass-`Unwatch` termination burst from spuriously quarantining a healthy peer. Local-only (per-association channel size); no wire-format impact. | Configurable via `akka.remote.artery.advanced.outbound-control-queue-size` if a different capacity is needed. | -| Planned | `fix/artery-queue-and-shutdown-robustness` | `Akka.Remote` (Artery) | Behavior | Artery's ordinary-outbound-queue overflow now publishes a `Dropped` event directly to the `EventStream` instead of routing through `DeadLetters.Tell` (which double-wrapped it as a `DeadLetter`). | Subscribers watching for dropped Artery sends should subscribe to `Dropped`, not `DeadLetter`; the default `DeadLetterListener` already logs both. | -| Planned | `feature/deprecate-actorpublisher` | `Akka.Streams` | Behavior | `Source.ActorRef` is re-implemented as a stream-native `GraphStage` (off the legacy `ActorPublisher`). Its public signature is unchanged, but the materialized `IActorRef` no longer treats `PoisonPill` / `Kill` as stream completion — those messages are now ignored. (`Status.Success` draining and `Status.Failure` completion are unchanged.) | Complete the stream explicitly: send `new Status.Success(...)` to drain buffered elements and complete, or `new Status.Failure(ex)` to fail. Do not rely on `PoisonPill` / `Kill`. | +| Status | PR | Component | Type | Change | Migration | +|--------|----|-----------|------|--------|-----------| +| Merged | [#8698](https://github.com/akkadotnet/akka.net/pull/8698) | `Akka` (serialization) | Behavior + Wire | A user `serializers` row that names a built-in serializer type under a second alias now gets the shared built-in instance, and that alias's own `serialization-settings` block is ignored. For `PrimitiveSerializers` this can change the manifests it writes. | Put the settings on the built-in alias's block (e.g. `serialization-settings.primitive`) instead of a second alias. | +| Merged | [#8697](https://github.com/akkadotnet/akka.net/pull/8697), [#8655](https://github.com/akkadotnet/akka.net/pull/8655), [#8649](https://github.com/akkadotnet/akka.net/pull/8649), [#8603](https://github.com/akkadotnet/akka.net/pull/8603), [#8602](https://github.com/akkadotnet/akka.net/pull/8602) | `Akka`, `Akka.DependencyInjection`, `Akka.Hosting` | Source (trim analyzer only) | New trimming attributes on public APIs (`Props` and `ActorOf`/`SystemActorOf`, `IIndirectActorProducer.ActorType`, `IDependencyResolver.Props`, `MailboxType`/`Mailboxes`, `TypeCache.GetType`, Hosting's `WithExtension`/`WithExtensions`). Apps that run the trim analyzer may get new `IL2026`/`IL2067`/`IL2072`/`IL2091` warnings at call sites, and `IL2092`-`IL2095` on implementations and overrides. | Annotate the `Type` or type parameter the value flows from (or pass `typeof(...)`); implementations repeat the attribute. | +| Merged | [#8694](https://github.com/akkadotnet/akka.net/pull/8694) | `Akka` and modules (serialization) | Behavior + Wire | Built-in serializers now declare their ids in code, so an `akka.actor.serialization-identifiers` override for a built-in serializer type no longer has any effect. Subclasses still read their id from HOCON. | Remove such overrides. A cluster where every node carried the same override must upgrade all nodes together. | +| Merged | [#8695](https://github.com/akkadotnet/akka.net/pull/8695), [#8669](https://github.com/akkadotnet/akka.net/pull/8669), [#8666](https://github.com/akkadotnet/akka.net/pull/8666), [#8658](https://github.com/akkadotnet/akka.net/pull/8658), [#8605](https://github.com/akkadotnet/akka.net/pull/8605), [#8604](https://github.com/akkadotnet/akka.net/pull/8604), [#8602](https://github.com/akkadotnet/akka.net/pull/8602), [#8601](https://github.com/akkadotnet/akka.net/pull/8601) | `Akka`, `Akka.Remote`, `Akka.Streams`, `Akka.Cluster`, cluster contrib, `Akka.Persistence` | Behavior | Akka now constructs its own scheduler, mailboxes and serializers directly instead of through `Activator.CreateInstance`, and `Props.Create(() => new A(...))` evaluates arguments without `DynamicInvoke`. An exception thrown there is no longer wrapped in `TargetInvocationException`, and a rejected built-in mailbox's error names the configured type string. | Catch the inner exception type. | +| Merged | [#8649](https://github.com/akkadotnet/akka.net/pull/8649) | `Akka.Hosting` | Behavior | `WithExtension()`/`WithExtensions(...)` pass extensions through `ExtensionsSetup`, so `akka.extensions` no longer lists them. An extension id whose constructor throws now fails startup with `ConfigurationException`; v1.5 logged and skipped it. | Use `Settings.Setup.Get()` or `HasExtension()` instead of reading `akka.extensions`. | +| Merged | [#8660](https://github.com/akkadotnet/akka.net/pull/8660) | `Akka.IO` | Binary + Source | Removed `Tcp.ResumeWriting`, `Tcp.WritingResumed`, `Tcp.Register.UseResumeWriting`, `TcpMessage.ResumeWriting()` and the `useResumeWriting` parameter of `Tcp.Register`/`TcpMessage.Register`. They never did anything. | Drop the `useResumeWriting` argument and any `ResumeWriting` sends; use ack-based writes for backpressure. | +| Merged | [#8646](https://github.com/akkadotnet/akka.net/pull/8646) (TCP rewrite in [#8132](https://github.com/akkadotnet/akka.net/pull/8132)) | `Akka.IO` | Behavior | `akka.io.tcp.write-commands-queue-max-size` now caps a single write, not the total bytes queued on a connection, so writes that push the backlog past it are no longer rejected with `CommandFailed`. Backpressure comes from `WriteAck`, which waits until the output buffer drains below its resume mark. | Use ack-based writes if you relied on the setting to bound memory. | +| Merged | [#8608](https://github.com/akkadotnet/akka.net/pull/8608) | `Akka` (package build logic) | Behavior | Projects that set `PublishAot` or `PublishTrimmed` now get `Akka.DynamicTypeLoading` turned off automatically. There, a HOCON type name outside Akka's built-in tables (custom logger, mailbox, dispatcher, router, serializer, scheduler, extension, provider, DNS provider) throws `ConfigurationException` instead of loading by reflection, and receiving a stream ref fails. Untrimmed builds are unaffected. | Register custom types through `Setup` APIs (`SerializationSetup`, `ExtensionsSetup`, `LoggerSetup`, ...), or opt back in with ``. | +| Merged | [#8606](https://github.com/akkadotnet/akka.net/pull/8606) | `Akka.IO` | Behavior | UDP no longer pools buffers. The `akka.io.udp(-connected).buffer-pool` and `direct-buffer-pool` settings and the pool sections' `class` keys are gone; UDP always reads `disabled-buffer-pool.buffer-size`. | If you pointed `buffer-pool` at `direct-buffer-pool` or a custom pool, move its `buffer-size` to `disabled-buffer-pool.buffer-size`, or datagrams over 512 bytes are truncated. | +| Merged | [#8599](https://github.com/akkadotnet/akka.net/pull/8599), [#8670](https://github.com/akkadotnet/akka.net/pull/8670) | `Akka` (`Settings`) | Behavior | `Settings.ProviderSelectionType`/`ProviderClass` now report the built-in provider when `akka.actor.provider` names one by type, including the default config: v1.5 gave `Custom`/`"Akka.Actor.LocalActorRefProvider"`, v1.6 gives `Local`/`"Akka.Actor.LocalActorRefProvider, Akka"`. | Test `ProviderSelectionType is ProviderSelection.Local` instead of comparing `ProviderClass` strings. | +| Merged | [#8594](https://github.com/akkadotnet/akka.net/pull/8594) | `Akka.Hosting` family, `Akka`, `Akka.DependencyInjection` | Binary + Source | The Akka.Hosting packages now ship from this repository at the Akka.NET version and target `net10.0` only (Akka.Hosting 1.5: `netstandard2.0;net6.0`). The `Microsoft.Extensions.*` minimum moves to 10.0.0 for `Akka`, `Akka.DependencyInjection` and the Hosting packages. | Target `net10.0` or later and reference `Akka.Hosting.*` at the same version as `Akka.*`. | +| Merged | [#8545](https://github.com/akkadotnet/akka.net/pull/8545) | `Akka.TestKit.Xunit` | Source | `TestKit` now implements `IAsyncLifetime` with `virtual` `InitializeAsync()`/`DisposeAsync()`, so a derived spec that declares its own gets `CS0114` (an error under warnings-as-errors). | Mark them `override` and call `base.InitializeAsync()`/`base.DisposeAsync()`. | +| Merged | [#8445](https://github.com/akkadotnet/akka.net/pull/8445) | `Akka.Cluster.Sharding` | Behavior | The default `least-shard-allocation-strategy.rebalance-absolute-limit` is now `20` (v1.5: `0`), which selects the bounded rebalancing strategy; `rebalance-threshold` and `max-simultaneous-rebalance` no longer apply by default. | Set `rebalance-absolute-limit = 0` to keep the legacy strategy for now. | +| Merged | [#8378](https://github.com/akkadotnet/akka.net/pull/8378) | `Akka.Remote.TestKit` | Behavior | The multi-node `Player` now fails a throttle or blackhole request that the transport did not apply; v1.5 reported success for the no-op. | Set `TestTransport = true` in the `MultiNodeConfig` of specs that throttle or blackhole. | +| Merged | [#8359](https://github.com/akkadotnet/akka.net/pull/8359) | `Akka.Cluster` | Behavior | `Cluster.Get()` no longer blocks until the cluster core has started, and no longer fails with "Failed to startup Cluster" after `akka.actor.creation-timeout`; core startup finishes in the background. | Don't treat `Cluster.Get()` returning as "cluster started"; use `JoinAsync`, `RegisterOnMemberUp` or cluster events. | +| Merged | [#8317](https://github.com/akkadotnet/akka.net/pull/8317) | `Akka.Remote`, `Akka.Cluster` | Binary + Source | System UIDs are now `long` instead of `int`: `AddressUid.Uid`, `AddressUidExtension.Uid()`, `UniqueAddress`'s constructor and `Uid`, `QuarantinedEvent`, `RemoteWatcher.HeartbeatRsp`, and every `Quarantine(Address, int?)` member. The wire format is unchanged while UIDs stay in 32-bit range (the default). | Recompile and use `long` for UIDs. Turn on `akka.remote.use-64bit-system-uids` only once every node runs v1.6. | +| Merged | [#8263](https://github.com/akkadotnet/akka.net/pull/8263) | `Akka.Streams` | Behavior | The actor materialized by `Source.ActorRef` now ignores `PoisonPill` and `Kill` instead of completing the stream. | Send `Status.Success` to complete the stream or `Status.Failure` to fail it. | + +### Not breaking, for the record + +- **Artery** is new in v1.6 (v1.5.71 has no Artery), so changes to its behavior and settings + are not listed. +- **#8317 wire**: cluster gossip's `UniqueAddress.uid` went from `uint32` to `uint64`. Both use + the same varint encoding, so v1.5 and v1.6 nodes interoperate while UIDs stay 32-bit. +- **#8484 gossip tombstones**: gossip gains an additive `tombstones` field that v1.5 nodes + ignore, plus a new `akka.cluster.prune-gossip-tombstones-after` setting (default `24h`). From 300138f754f327d8b978610a5ed97bca792984cc Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 1 Oct 2026 16:01:44 +0000 Subject: [PATCH 2/4] Add pre-ledger breaking changes to BREAKING_CHANGES_V1.6.md Cover changes on dev since the v1.5 line diverged that landed before the ledger existed: net10.0-only targeting and ByteString removal (#8132), App.config HOCON loading removal (#7456), and the AddOrSet removal (#7622). --- BREAKING_CHANGES_V1.6.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BREAKING_CHANGES_V1.6.md b/BREAKING_CHANGES_V1.6.md index 84fe4836ac4..b71932a0bdf 100644 --- a/BREAKING_CHANGES_V1.6.md +++ b/BREAKING_CHANGES_V1.6.md @@ -62,6 +62,10 @@ add `Wire` when the bytes on the wire change. | Merged | [#8359](https://github.com/akkadotnet/akka.net/pull/8359) | `Akka.Cluster` | Behavior | `Cluster.Get()` no longer blocks until the cluster core has started, and no longer fails with "Failed to startup Cluster" after `akka.actor.creation-timeout`; core startup finishes in the background. | Don't treat `Cluster.Get()` returning as "cluster started"; use `JoinAsync`, `RegisterOnMemberUp` or cluster events. | | Merged | [#8317](https://github.com/akkadotnet/akka.net/pull/8317) | `Akka.Remote`, `Akka.Cluster` | Binary + Source | System UIDs are now `long` instead of `int`: `AddressUid.Uid`, `AddressUidExtension.Uid()`, `UniqueAddress`'s constructor and `Uid`, `QuarantinedEvent`, `RemoteWatcher.HeartbeatRsp`, and every `Quarantine(Address, int?)` member. The wire format is unchanged while UIDs stay in 32-bit range (the default). | Recompile and use `long` for UIDs. Turn on `akka.remote.use-64bit-system-uids` only once every node runs v1.6. | | Merged | [#8263](https://github.com/akkadotnet/akka.net/pull/8263) | `Akka.Streams` | Behavior | The actor materialized by `Source.ActorRef` now ignores `PoisonPill` and `Kill` instead of completing the stream. | Send `Status.Success` to complete the stream or `Status.Failure` to fail it. | +| Merged | [#8132](https://github.com/akkadotnet/akka.net/pull/8132) | All packages | Binary + Source | Every package now targets `net10.0` only (v1.5: `netstandard2.0` + `net6.0`), so .NET Framework, .NET 6 and .NET 8 apps can't reference v1.6. Dependency minimums rise too, e.g. Google.Protobuf 3.36.1 and FSharp.Core 10.1.302 (Akka.FSharp). | Move the app to .NET 10 or later. | +| Merged | [#8132](https://github.com/akkadotnet/akka.net/pull/8132) | `Akka.IO`, `Akka.Streams` | Binary + Source | `Akka.IO.ByteString` is removed. TCP messages (`Tcp.Write`, `Tcp.Received`, `TcpMessage.Write`) and the Streams byte APIs (`Framing`, `JsonFraming`, `FileIO`, `StreamConverters`, `Tcp` / `IncomingConnection`) now use `ReadOnlySequence`; UDP messages (`Udp.Send`/`Received`, `UdpConnected.Send`/`Received`) use `ReadOnlyMemory`. `Akka.IO.ByteOrder` moved to `Akka.Util.ByteOrder`. | Replace `ByteString` with `ReadOnlySequence` / `ReadOnlyMemory` (e.g. `Encoding.UTF8.GetBytes(s)` for `ByteString.FromString(s)`, `Encoding.UTF8.GetString(data)` for `.ToString()`); import `ByteOrder` from `Akka.Util`. | +| Merged | [#7622](https://github.com/akkadotnet/akka.net/pull/7622) | `Akka` | Binary + Source | Removed the obsolete `Akka.Util.Internal.Extensions.AddOrSet` extension method. | Use the dictionary indexer: `dict[key] = value`. | +| Merged | [#7456](https://github.com/akkadotnet/akka.net/pull/7456) (landed via [#7761](https://github.com/akkadotnet/akka.net/pull/7761)) | `Akka`, `Akka.FSharp` | Binary + Source | Loading HOCON from `App.config`/`Web.config` is gone: `ConfigurationFactory.Load()`, `AkkaConfigurationSection`, `HoconConfigurationElement`, `CDataConfigurationElement` and Akka.FSharp's `Configuration.load` are removed. | Keep the HOCON in a string or file and load it with `ConfigurationFactory.ParseString`, or configure through Akka.Hosting. | ### Not breaking, for the record From a9981c9531dbb9b46e4f3c1d2879e388b1456c42 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 1 Oct 2026 16:17:19 +0000 Subject: [PATCH 3/4] Apply review feedback to BREAKING_CHANGES_V1.6.md Add rows for #8222 (Serializer overloads, ByteArraySerializer manifest), #8465 (explicit TLS hostname check), #8132 (TCP connects bypass akka.io.dns) and #7557 (object handler without predicate blocks later Receive calls). State net10.0-only once, folding in #8594 and the dependency bumps; fix the ByteString row's write types; narrow the #8698 row to module serializers; drop the .Internal AddOrSet row and say why in the preamble; shorten rows; use asterisk list markers. --- BREAKING_CHANGES_V1.6.md | 47 ++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/BREAKING_CHANGES_V1.6.md b/BREAKING_CHANGES_V1.6.md index b71932a0bdf..7fe247c53a1 100644 --- a/BREAKING_CHANGES_V1.6.md +++ b/BREAKING_CHANGES_V1.6.md @@ -8,12 +8,12 @@ development cycle, ahead of a stable `v1.6.0` release. Compare against the **last stable v1.5 release** (`1.5.71` at the time of writing). A change is breaking if it is not: -- **Binary compatible**: an assembly compiled against v1.5 fails to load or bind (removed or +* **Binary compatible**: an assembly compiled against v1.5 fails to load or bind (removed or renamed public members, changed signatures, changed types). -- **Source compatible**: existing code stops compiling, or gets new errors or +* **Source compatible**: existing code stops compiling, or gets new errors or warnings-as-errors the user can't avoid. New trim-analyzer warnings count only for apps that run the trim analyzer; list them briefly. -- **Behaviorally compatible**: an existing app with its existing config sees different +* **Behaviorally compatible**: an existing app with its existing config sees different runtime behavior: different results, exceptions where it used to work, different wire output, different defaults, or a removed setting that used to have an effect. @@ -21,12 +21,15 @@ Not breaking, so not listed: behavior that only exists with `Akka.DynamicTypeLoa switch is new in v1.6 and on by default; #8608 is listed because it turns the switch off for you); fixes that make something work that used to fail, unless users plausibly relied on the failure; differences from `dev` states that never shipped; internal refactors; new APIs. +Types in `.Internal` namespaces or marked `[InternalApi]` are not public API (see +[API compatibility](docs/community/contributing/api-changes-compatibility.md)), so changes to +them aren't listed either. ## Process and lifecycle -- A PR that makes a breaking change says so in a `## Breaking changes` section of its +* A PR that makes a breaking change says so in a `## Breaking changes` section of its description. Maintainers batch those into this file. -- This file is kept **only until a stable `v1.6.0` ships**. Its contents then move into the +* This file is kept **only until a stable `v1.6.0` ships**. Its contents then move into the release notes / upgrade guide and the file is retired. ## Entry format @@ -45,33 +48,39 @@ add `Wire` when the bytes on the wire change. | Status | PR | Component | Type | Change | Migration | |--------|----|-----------|------|--------|-----------| -| Merged | [#8698](https://github.com/akkadotnet/akka.net/pull/8698) | `Akka` (serialization) | Behavior + Wire | A user `serializers` row that names a built-in serializer type under a second alias now gets the shared built-in instance, and that alias's own `serialization-settings` block is ignored. For `PrimitiveSerializers` this can change the manifests it writes. | Put the settings on the built-in alias's block (e.g. `serialization-settings.primitive`) instead of a second alias. | -| Merged | [#8697](https://github.com/akkadotnet/akka.net/pull/8697), [#8655](https://github.com/akkadotnet/akka.net/pull/8655), [#8649](https://github.com/akkadotnet/akka.net/pull/8649), [#8603](https://github.com/akkadotnet/akka.net/pull/8603), [#8602](https://github.com/akkadotnet/akka.net/pull/8602) | `Akka`, `Akka.DependencyInjection`, `Akka.Hosting` | Source (trim analyzer only) | New trimming attributes on public APIs (`Props` and `ActorOf`/`SystemActorOf`, `IIndirectActorProducer.ActorType`, `IDependencyResolver.Props`, `MailboxType`/`Mailboxes`, `TypeCache.GetType`, Hosting's `WithExtension`/`WithExtensions`). Apps that run the trim analyzer may get new `IL2026`/`IL2067`/`IL2072`/`IL2091` warnings at call sites, and `IL2092`-`IL2095` on implementations and overrides. | Annotate the `Type` or type parameter the value flows from (or pass `typeof(...)`); implementations repeat the attribute. | -| Merged | [#8694](https://github.com/akkadotnet/akka.net/pull/8694) | `Akka` and modules (serialization) | Behavior + Wire | Built-in serializers now declare their ids in code, so an `akka.actor.serialization-identifiers` override for a built-in serializer type no longer has any effect. Subclasses still read their id from HOCON. | Remove such overrides. A cluster where every node carried the same override must upgrade all nodes together. | +| Merged | [#8698](https://github.com/akkadotnet/akka.net/pull/8698) | `Akka.Remote`, `Akka.Cluster` and other modules (serialization) | Behavior + Wire | A user `serializers` row that names a built-in Akka.Remote/Akka.Cluster/... serializer type under a second alias now gets the shared built-in instance, and that alias's own `serialization-settings` block is ignored. For `PrimitiveSerializers` this can change the manifests it writes. | Put the settings on the built-in alias's block (e.g. `serialization-settings.primitive`) instead of a second alias. | +| Merged | [#8697](https://github.com/akkadotnet/akka.net/pull/8697), [#8655](https://github.com/akkadotnet/akka.net/pull/8655), [#8649](https://github.com/akkadotnet/akka.net/pull/8649), [#8603](https://github.com/akkadotnet/akka.net/pull/8603), [#8602](https://github.com/akkadotnet/akka.net/pull/8602) | `Akka`, `Akka.DependencyInjection`, `Akka.Hosting` | Source (trim analyzer only) | New trimming attributes on public APIs: `Props` and `ActorOf`/`SystemActorOf`, `IIndirectActorProducer.ActorType`, `IDependencyResolver.Props`, `MailboxType`/`Mailboxes`, `TypeCache.GetType`, and Hosting's `AddAkka`, `WithHealthCheck`, `WithDefaultLogMessageFormatter`, `WithExtension`/`WithExtensions` and others. Apps that run the trim analyzer may get new `IL2026`/`IL2067`/`IL2072`/`IL2091` warnings at call sites, and `IL2092`-`IL2095` on implementations and overrides. | Annotate the `Type` or type parameter the value flows from (or pass `typeof(...)`); implementations repeat the attribute. | +| Merged | [#8694](https://github.com/akkadotnet/akka.net/pull/8694) | `Akka` and modules (serialization) | Behavior + Wire | Built-in serializers now declare their ids in code, so an `akka.actor.serialization-identifiers` override for a built-in serializer type no longer has any effect. Subclasses still read their id from HOCON. | Remove such overrides, upgrading all nodes together if every node carried the same override. | | Merged | [#8695](https://github.com/akkadotnet/akka.net/pull/8695), [#8669](https://github.com/akkadotnet/akka.net/pull/8669), [#8666](https://github.com/akkadotnet/akka.net/pull/8666), [#8658](https://github.com/akkadotnet/akka.net/pull/8658), [#8605](https://github.com/akkadotnet/akka.net/pull/8605), [#8604](https://github.com/akkadotnet/akka.net/pull/8604), [#8602](https://github.com/akkadotnet/akka.net/pull/8602), [#8601](https://github.com/akkadotnet/akka.net/pull/8601) | `Akka`, `Akka.Remote`, `Akka.Streams`, `Akka.Cluster`, cluster contrib, `Akka.Persistence` | Behavior | Akka now constructs its own scheduler, mailboxes and serializers directly instead of through `Activator.CreateInstance`, and `Props.Create(() => new A(...))` evaluates arguments without `DynamicInvoke`. An exception thrown there is no longer wrapped in `TargetInvocationException`, and a rejected built-in mailbox's error names the configured type string. | Catch the inner exception type. | | Merged | [#8649](https://github.com/akkadotnet/akka.net/pull/8649) | `Akka.Hosting` | Behavior | `WithExtension()`/`WithExtensions(...)` pass extensions through `ExtensionsSetup`, so `akka.extensions` no longer lists them. An extension id whose constructor throws now fails startup with `ConfigurationException`; v1.5 logged and skipped it. | Use `Settings.Setup.Get()` or `HasExtension()` instead of reading `akka.extensions`. | | Merged | [#8660](https://github.com/akkadotnet/akka.net/pull/8660) | `Akka.IO` | Binary + Source | Removed `Tcp.ResumeWriting`, `Tcp.WritingResumed`, `Tcp.Register.UseResumeWriting`, `TcpMessage.ResumeWriting()` and the `useResumeWriting` parameter of `Tcp.Register`/`TcpMessage.Register`. They never did anything. | Drop the `useResumeWriting` argument and any `ResumeWriting` sends; use ack-based writes for backpressure. | -| Merged | [#8646](https://github.com/akkadotnet/akka.net/pull/8646) (TCP rewrite in [#8132](https://github.com/akkadotnet/akka.net/pull/8132)) | `Akka.IO` | Behavior | `akka.io.tcp.write-commands-queue-max-size` now caps a single write, not the total bytes queued on a connection, so writes that push the backlog past it are no longer rejected with `CommandFailed`. Backpressure comes from `WriteAck`, which waits until the output buffer drains below its resume mark. | Use ack-based writes if you relied on the setting to bound memory. | -| Merged | [#8608](https://github.com/akkadotnet/akka.net/pull/8608) | `Akka` (package build logic) | Behavior | Projects that set `PublishAot` or `PublishTrimmed` now get `Akka.DynamicTypeLoading` turned off automatically. There, a HOCON type name outside Akka's built-in tables (custom logger, mailbox, dispatcher, router, serializer, scheduler, extension, provider, DNS provider) throws `ConfigurationException` instead of loading by reflection, and receiving a stream ref fails. Untrimmed builds are unaffected. | Register custom types through `Setup` APIs (`SerializationSetup`, `ExtensionsSetup`, `LoggerSetup`, ...), or opt back in with ``. | +| Merged | [#8646](https://github.com/akkadotnet/akka.net/pull/8646) (TCP rewrite in [#8132](https://github.com/akkadotnet/akka.net/pull/8132)) | `Akka.IO` | Behavior | `akka.io.tcp.write-commands-queue-max-size` now caps a single write rather than a registered connection's total backlog (writes buffered before `Register` are still capped in total), so a growing backlog no longer gets `CommandFailed`. Backpressure comes from `WriteAck`, which waits until the output buffer drains below its resume mark. | Use ack-based writes if you relied on the setting to bound memory. | +| Merged | [#8608](https://github.com/akkadotnet/akka.net/pull/8608) | `Akka` (package build logic) | Behavior | Projects that set `PublishAot` or `PublishTrimmed` now get `Akka.DynamicTypeLoading` turned off automatically, so a HOCON type name outside Akka's built-in tables (custom logger, mailbox, dispatcher, router, serializer, scheduler, extension, provider, DNS provider) throws `ConfigurationException` instead of loading by reflection. Receiving a stream ref also fails there; untrimmed builds are unaffected. | Register custom types through `Setup` APIs (`SerializationSetup`, `ExtensionsSetup`, `LoggerSetup`, ...), or opt back in with ``. | | Merged | [#8606](https://github.com/akkadotnet/akka.net/pull/8606) | `Akka.IO` | Behavior | UDP no longer pools buffers. The `akka.io.udp(-connected).buffer-pool` and `direct-buffer-pool` settings and the pool sections' `class` keys are gone; UDP always reads `disabled-buffer-pool.buffer-size`. | If you pointed `buffer-pool` at `direct-buffer-pool` or a custom pool, move its `buffer-size` to `disabled-buffer-pool.buffer-size`, or datagrams over 512 bytes are truncated. | | Merged | [#8599](https://github.com/akkadotnet/akka.net/pull/8599), [#8670](https://github.com/akkadotnet/akka.net/pull/8670) | `Akka` (`Settings`) | Behavior | `Settings.ProviderSelectionType`/`ProviderClass` now report the built-in provider when `akka.actor.provider` names one by type, including the default config: v1.5 gave `Custom`/`"Akka.Actor.LocalActorRefProvider"`, v1.6 gives `Local`/`"Akka.Actor.LocalActorRefProvider, Akka"`. | Test `ProviderSelectionType is ProviderSelection.Local` instead of comparing `ProviderClass` strings. | -| Merged | [#8594](https://github.com/akkadotnet/akka.net/pull/8594) | `Akka.Hosting` family, `Akka`, `Akka.DependencyInjection` | Binary + Source | The Akka.Hosting packages now ship from this repository at the Akka.NET version and target `net10.0` only (Akka.Hosting 1.5: `netstandard2.0;net6.0`). The `Microsoft.Extensions.*` minimum moves to 10.0.0 for `Akka`, `Akka.DependencyInjection` and the Hosting packages. | Target `net10.0` or later and reference `Akka.Hosting.*` at the same version as `Akka.*`. | | Merged | [#8545](https://github.com/akkadotnet/akka.net/pull/8545) | `Akka.TestKit.Xunit` | Source | `TestKit` now implements `IAsyncLifetime` with `virtual` `InitializeAsync()`/`DisposeAsync()`, so a derived spec that declares its own gets `CS0114` (an error under warnings-as-errors). | Mark them `override` and call `base.InitializeAsync()`/`base.DisposeAsync()`. | +| Merged | [#8465](https://github.com/akkadotnet/akka.net/pull/8465) | `Akka.Remote` (DotNetty TLS) | Behavior | `CertificateValidation.ValidateHostname(expectedHostname)` now checks the certificate against `expectedHostname`; v1.5 used that name only in its log message and relied on `SslStream`'s name-mismatch result. It can reject connections v1.5 accepted, such as a wrong or placeholder name, or inbound mutual-TLS client certificates. | Pass the name the certificate is issued for, or call `ValidateHostname()` with no argument to keep the v1.5 check. | | Merged | [#8445](https://github.com/akkadotnet/akka.net/pull/8445) | `Akka.Cluster.Sharding` | Behavior | The default `least-shard-allocation-strategy.rebalance-absolute-limit` is now `20` (v1.5: `0`), which selects the bounded rebalancing strategy; `rebalance-threshold` and `max-simultaneous-rebalance` no longer apply by default. | Set `rebalance-absolute-limit = 0` to keep the legacy strategy for now. | | Merged | [#8378](https://github.com/akkadotnet/akka.net/pull/8378) | `Akka.Remote.TestKit` | Behavior | The multi-node `Player` now fails a throttle or blackhole request that the transport did not apply; v1.5 reported success for the no-op. | Set `TestTransport = true` in the `MultiNodeConfig` of specs that throttle or blackhole. | | Merged | [#8359](https://github.com/akkadotnet/akka.net/pull/8359) | `Akka.Cluster` | Behavior | `Cluster.Get()` no longer blocks until the cluster core has started, and no longer fails with "Failed to startup Cluster" after `akka.actor.creation-timeout`; core startup finishes in the background. | Don't treat `Cluster.Get()` returning as "cluster started"; use `JoinAsync`, `RegisterOnMemberUp` or cluster events. | -| Merged | [#8317](https://github.com/akkadotnet/akka.net/pull/8317) | `Akka.Remote`, `Akka.Cluster` | Binary + Source | System UIDs are now `long` instead of `int`: `AddressUid.Uid`, `AddressUidExtension.Uid()`, `UniqueAddress`'s constructor and `Uid`, `QuarantinedEvent`, `RemoteWatcher.HeartbeatRsp`, and every `Quarantine(Address, int?)` member. The wire format is unchanged while UIDs stay in 32-bit range (the default). | Recompile and use `long` for UIDs. Turn on `akka.remote.use-64bit-system-uids` only once every node runs v1.6. | +| Merged | [#8317](https://github.com/akkadotnet/akka.net/pull/8317) | `Akka.Remote`, `Akka.Cluster` | Binary + Source | System UIDs are now `long` instead of `int`: `AddressUid.Uid`, `AddressUidExtension.Uid()`, `UniqueAddress`'s constructor and `Uid`, `QuarantinedEvent`, `RemoteWatcher.HeartbeatRsp`, and every `Quarantine(Address, int?)` member. The wire format is unchanged while UIDs stay in 32-bit range (the default). | Recompile with `long` UIDs, and turn on `akka.remote.use-64bit-system-uids` only once every node runs v1.6. | | Merged | [#8263](https://github.com/akkadotnet/akka.net/pull/8263) | `Akka.Streams` | Behavior | The actor materialized by `Source.ActorRef` now ignores `PoisonPill` and `Kill` instead of completing the stream. | Send `Status.Success` to complete the stream or `Status.Failure` to fail it. | -| Merged | [#8132](https://github.com/akkadotnet/akka.net/pull/8132) | All packages | Binary + Source | Every package now targets `net10.0` only (v1.5: `netstandard2.0` + `net6.0`), so .NET Framework, .NET 6 and .NET 8 apps can't reference v1.6. Dependency minimums rise too, e.g. Google.Protobuf 3.36.1 and FSharp.Core 10.1.302 (Akka.FSharp). | Move the app to .NET 10 or later. | -| Merged | [#8132](https://github.com/akkadotnet/akka.net/pull/8132) | `Akka.IO`, `Akka.Streams` | Binary + Source | `Akka.IO.ByteString` is removed. TCP messages (`Tcp.Write`, `Tcp.Received`, `TcpMessage.Write`) and the Streams byte APIs (`Framing`, `JsonFraming`, `FileIO`, `StreamConverters`, `Tcp` / `IncomingConnection`) now use `ReadOnlySequence`; UDP messages (`Udp.Send`/`Received`, `UdpConnected.Send`/`Received`) use `ReadOnlyMemory`. `Akka.IO.ByteOrder` moved to `Akka.Util.ByteOrder`. | Replace `ByteString` with `ReadOnlySequence` / `ReadOnlyMemory` (e.g. `Encoding.UTF8.GetBytes(s)` for `ByteString.FromString(s)`, `Encoding.UTF8.GetString(data)` for `.ToString()`); import `ByteOrder` from `Akka.Util`. | -| Merged | [#7622](https://github.com/akkadotnet/akka.net/pull/7622) | `Akka` | Binary + Source | Removed the obsolete `Akka.Util.Internal.Extensions.AddOrSet` extension method. | Use the dictionary indexer: `dict[key] = value`. | +| Merged | [#8222](https://github.com/akkadotnet/akka.net/pull/8222) | `Akka` (serialization) | Source + Behavior | `Serializer` gains virtual `FromBinary(byte[], string)` and `Manifest(object)`, so `FromBinary(bytes, null)` on a `Serializer` is now ambiguous (`CS0121`) and a subclass's own method with either signature hides the base one (`CS0114`). `ByteArraySerializer` now derives from `SerializerV2`, whose `IncludeManifest` is a sealed `true` (v1.5: `false`), so a subclass overriding it no longer compiles and code that reads `IncludeManifest` (e.g. a v1.5-style custom journal) now stores a type-name manifest for `byte[]` (Akka's own wire output is unchanged, since `Manifest()` returns `""`). | Cast the null (`(Type)null`) or pass a manifest, mark matching subclass methods `override`, and stop overriding `ByteArraySerializer.IncludeManifest`. | +| Merged | [#8132](https://github.com/akkadotnet/akka.net/pull/8132), [#8594](https://github.com/akkadotnet/akka.net/pull/8594); dependency bumps [#8228](https://github.com/akkadotnet/akka.net/pull/8228)/[#8386](https://github.com/akkadotnet/akka.net/pull/8386), [#8229](https://github.com/akkadotnet/akka.net/pull/8229)/[#8577](https://github.com/akkadotnet/akka.net/pull/8577), [#8236](https://github.com/akkadotnet/akka.net/pull/8236)/[#8338](https://github.com/akkadotnet/akka.net/pull/8338) | All packages, including the Akka.Hosting family | Binary + Source | Every package targets `net10.0` only (v1.5 and Akka.Hosting 1.5: `netstandard2.0` + `net6.0`), and the Akka.Hosting packages now ship from this repository at the Akka.NET version, so .NET Framework, .NET 6 and .NET 8 apps can't reference v1.6. Dependency minimums rise: `Microsoft.Extensions.*` 10.0 (`Akka`, `Akka.DependencyInjection`, Hosting), Google.Protobuf 3.36.1, FSharp.Core 10.1.302 (Akka.FSharp) and LightningDB 0.22.0 (Akka.DistributedData.LightningDB). | Move the app to .NET 10 or later and reference `Akka.Hosting.*` at the same version as `Akka.*`. | +| Merged | [#8132](https://github.com/akkadotnet/akka.net/pull/8132) | `Akka.IO`, `Akka.Streams` | Binary + Source | `Akka.IO.ByteString` is removed, and `Akka.IO.ByteOrder` moved to `Akka.Util.ByteOrder`. TCP data (`Tcp.Received`, `Tcp.Write`) and the Streams byte APIs (`Framing`, `JsonFraming`, `FileIO`, `StreamConverters`, `Tcp`) use `ReadOnlySequence`, the TCP write helpers (`Tcp.Write.Create`, `TcpMessage.Write`) also take `ReadOnlyMemory`/`byte[]`, and UDP messages use `ReadOnlyMemory`. | Replace `ByteString` with `byte[]`/`ReadOnlyMemory`/`ReadOnlySequence` (e.g. `Encoding.UTF8.GetBytes`/`GetString` in place of `ByteString.FromString`/`ToString`) and import `ByteOrder` from `Akka.Util`. | +| Merged | [#8132](https://github.com/akkadotnet/akka.net/pull/8132) | `Akka.IO` | Behavior | TCP connects to a `DnsEndPoint` now go straight to `Socket.ConnectAsync` and the OS resolver, so `akka.io.dns` (custom `provider-object`, cache TTLs, `use-ipv6`) no longer applies to TCP. UDP still resolves through `akka.io.dns`. | Resolve the host yourself and connect to an `IPEndPoint`, or rely on the OS resolver. | +| Merged | [#7557](https://github.com/akkadotnet/akka.net/pull/7557) (landed via [#7761](https://github.com/akkadotnet/akka.net/pull/7761)) | `Akka` (`ReceiveActor`) | Behavior | `Receive(typeof(object), Func)` without a predicate now makes any later `Receive*` registration throw `InvalidOperationException`. v1.5 allowed later handlers there, because that handler can return `false`. | Register that handler last. | | Merged | [#7456](https://github.com/akkadotnet/akka.net/pull/7456) (landed via [#7761](https://github.com/akkadotnet/akka.net/pull/7761)) | `Akka`, `Akka.FSharp` | Binary + Source | Loading HOCON from `App.config`/`Web.config` is gone: `ConfigurationFactory.Load()`, `AkkaConfigurationSection`, `HoconConfigurationElement`, `CDataConfigurationElement` and Akka.FSharp's `Configuration.load` are removed. | Keep the HOCON in a string or file and load it with `ConfigurationFactory.ParseString`, or configure through Akka.Hosting. | ### Not breaking, for the record -- **Artery** is new in v1.6 (v1.5.71 has no Artery), so changes to its behavior and settings - are not listed. -- **#8317 wire**: cluster gossip's `UniqueAddress.uid` went from `uint32` to `uint64`. Both use +* **Artery** is new in v1.6 (v1.5.71 has no Artery), so changes to its behavior and settings + are not listed. Remote.conf always registers its `artery-control` serializer (id 23, inside + Akka.NET's reserved 1-100 range), even with Artery off. +* **#8317 wire**: cluster gossip's `UniqueAddress.uid` went from `uint32` to `uint64`. Both use the same varint encoding, so v1.5 and v1.6 nodes interoperate while UIDs stay 32-bit. -- **#8484 gossip tombstones**: gossip gains an additive `tombstones` field that v1.5 nodes +* **#8484 gossip tombstones**: gossip gains an additive `tombstones` field that v1.5 nodes ignore, plus a new `akka.cluster.prune-gossip-tombstones-after` setting (default `24h`). +* **#8561 TestKit**: TestKit's reference config now really turns off DotNetty batching + (`akka.remote.dot-netty.tcp.batching.enabled = false`), so TestKit-based remoting tests run + without batching. Production systems are unaffected. From 7835116c3cb9c91aa50987a33a7f97b0bc701029 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 1 Oct 2026 18:01:48 +0000 Subject: [PATCH 4/4] Drop the #7557 ReceiveActor row from BREAKING_CHANGES_V1.6.md The Receive(typeof(object), Func) regression is being fixed in a separate PR instead of documented. --- BREAKING_CHANGES_V1.6.md | 1 - 1 file changed, 1 deletion(-) diff --git a/BREAKING_CHANGES_V1.6.md b/BREAKING_CHANGES_V1.6.md index 7fe247c53a1..262508b7c09 100644 --- a/BREAKING_CHANGES_V1.6.md +++ b/BREAKING_CHANGES_V1.6.md @@ -69,7 +69,6 @@ add `Wire` when the bytes on the wire change. | Merged | [#8132](https://github.com/akkadotnet/akka.net/pull/8132), [#8594](https://github.com/akkadotnet/akka.net/pull/8594); dependency bumps [#8228](https://github.com/akkadotnet/akka.net/pull/8228)/[#8386](https://github.com/akkadotnet/akka.net/pull/8386), [#8229](https://github.com/akkadotnet/akka.net/pull/8229)/[#8577](https://github.com/akkadotnet/akka.net/pull/8577), [#8236](https://github.com/akkadotnet/akka.net/pull/8236)/[#8338](https://github.com/akkadotnet/akka.net/pull/8338) | All packages, including the Akka.Hosting family | Binary + Source | Every package targets `net10.0` only (v1.5 and Akka.Hosting 1.5: `netstandard2.0` + `net6.0`), and the Akka.Hosting packages now ship from this repository at the Akka.NET version, so .NET Framework, .NET 6 and .NET 8 apps can't reference v1.6. Dependency minimums rise: `Microsoft.Extensions.*` 10.0 (`Akka`, `Akka.DependencyInjection`, Hosting), Google.Protobuf 3.36.1, FSharp.Core 10.1.302 (Akka.FSharp) and LightningDB 0.22.0 (Akka.DistributedData.LightningDB). | Move the app to .NET 10 or later and reference `Akka.Hosting.*` at the same version as `Akka.*`. | | Merged | [#8132](https://github.com/akkadotnet/akka.net/pull/8132) | `Akka.IO`, `Akka.Streams` | Binary + Source | `Akka.IO.ByteString` is removed, and `Akka.IO.ByteOrder` moved to `Akka.Util.ByteOrder`. TCP data (`Tcp.Received`, `Tcp.Write`) and the Streams byte APIs (`Framing`, `JsonFraming`, `FileIO`, `StreamConverters`, `Tcp`) use `ReadOnlySequence`, the TCP write helpers (`Tcp.Write.Create`, `TcpMessage.Write`) also take `ReadOnlyMemory`/`byte[]`, and UDP messages use `ReadOnlyMemory`. | Replace `ByteString` with `byte[]`/`ReadOnlyMemory`/`ReadOnlySequence` (e.g. `Encoding.UTF8.GetBytes`/`GetString` in place of `ByteString.FromString`/`ToString`) and import `ByteOrder` from `Akka.Util`. | | Merged | [#8132](https://github.com/akkadotnet/akka.net/pull/8132) | `Akka.IO` | Behavior | TCP connects to a `DnsEndPoint` now go straight to `Socket.ConnectAsync` and the OS resolver, so `akka.io.dns` (custom `provider-object`, cache TTLs, `use-ipv6`) no longer applies to TCP. UDP still resolves through `akka.io.dns`. | Resolve the host yourself and connect to an `IPEndPoint`, or rely on the OS resolver. | -| Merged | [#7557](https://github.com/akkadotnet/akka.net/pull/7557) (landed via [#7761](https://github.com/akkadotnet/akka.net/pull/7761)) | `Akka` (`ReceiveActor`) | Behavior | `Receive(typeof(object), Func)` without a predicate now makes any later `Receive*` registration throw `InvalidOperationException`. v1.5 allowed later handlers there, because that handler can return `false`. | Register that handler last. | | Merged | [#7456](https://github.com/akkadotnet/akka.net/pull/7456) (landed via [#7761](https://github.com/akkadotnet/akka.net/pull/7761)) | `Akka`, `Akka.FSharp` | Binary + Source | Loading HOCON from `App.config`/`Web.config` is gone: `ConfigurationFactory.Load()`, `AkkaConfigurationSection`, `HoconConfigurationElement`, `CDataConfigurationElement` and Akka.FSharp's `Configuration.load` are removed. | Keep the HOCON in a string or file and load it with `ConfigurationFactory.ParseString`, or configure through Akka.Hosting. | ### Not breaking, for the record