Widen ActorTaskScheduler(ActorCell) ctor to protected internal - #8158
Merged
Aaronontheweb merged 1 commit intoApr 13, 2026
Merged
Conversation
The two virtual hooks OnBeforeTaskStarted / OnAfterTaskCompleted have always been protected, clearly intended as extension points for libraries that need to observe async message handler lifetimes. But the ctor was internal, blocking any external subclass from actually hooking them without reflection or runtime codegen. Widening the ctor to protected internal finishes the extension point that was already half-exposed. protected lets external libraries subclass; the internal part preserves the existing new ActorTaskScheduler(this) call site in ActorCell without requiring a derived type there. Motivation: Phobos (the Akka.NET observability plugin) needs to subclass ActorTaskScheduler to keep akka.msg.recv spans open for the full duration of ReceiveAsync / CommandAsync handlers. Today the span closes at the first await yield point, hiding all post-await work from traces. Customers running Phobos on both .NET and .NET Framework hit this, and we need a cross-runtime fix — IgnoresAccessChecksTo works only on CoreCLR, reflection can't synthesize a subclass with an inaccessible base ctor, and runtime codegen is AOT-hostile. Widening the ctor is the clean fix. No behavior change. Pure access widening, no caller breaks.
4 tasks
Aaronontheweb
added a commit
to Aaronontheweb/akka.net
that referenced
this pull request
Apr 24, 2026
… surface Apply the same API changes introduced by cherry-picks to the .NET Framework API approval files: - ActorTaskScheduler protected ctor (akkadotnet#8158) - MemoryJournal: Messages→Storage (JournalStorage), remove Update, Read param rename (akkadotnet#8184) - SharedMemoryJournal: Messages→Storage - MemorySnapshotStore: Snapshots→Storage (SnapshotStorage nested class) (akkadotnet#8184) - SnapshotEntry: sealed with readonly constructor-initialized properties (akkadotnet#8184) - StreamsDiagnostics class in Akka.Streams.Implementation (akkadotnet#8160)
Aaronontheweb
added a commit
that referenced
this pull request
Apr 24, 2026
The two virtual hooks OnBeforeTaskStarted / OnAfterTaskCompleted have always been protected, clearly intended as extension points for libraries that need to observe async message handler lifetimes. But the ctor was internal, blocking any external subclass from actually hooking them without reflection or runtime codegen. Widening the ctor to protected internal finishes the extension point that was already half-exposed. protected lets external libraries subclass; the internal part preserves the existing new ActorTaskScheduler(this) call site in ActorCell without requiring a derived type there. Motivation: Phobos (the Akka.NET observability plugin) needs to subclass ActorTaskScheduler to keep akka.msg.recv spans open for the full duration of ReceiveAsync / CommandAsync handlers. Today the span closes at the first await yield point, hiding all post-await work from traces. Customers running Phobos on both .NET and .NET Framework hit this, and we need a cross-runtime fix — IgnoresAccessChecksTo works only on CoreCLR, reflection can't synthesize a subclass with an inaccessible base ctor, and runtime codegen is AOT-hostile. Widening the ctor is the clean fix. No behavior change. Pure access widening, no caller breaks.
Aaronontheweb
added a commit
that referenced
this pull request
Apr 24, 2026
… surface Apply the same API changes introduced by cherry-picks to the .NET Framework API approval files: - ActorTaskScheduler protected ctor (#8158) - MemoryJournal: Messages→Storage (JournalStorage), remove Update, Read param rename (#8184) - SharedMemoryJournal: Messages→Storage - MemorySnapshotStore: Snapshots→Storage (SnapshotStorage nested class) (#8184) - SnapshotEntry: sealed with readonly constructor-initialized properties (#8184) - StreamsDiagnostics class in Akka.Streams.Implementation (#8160)
This was referenced Apr 27, 2026
Merged
Closed
This was referenced Jun 28, 2026
Open
Bump Akka.TestKit.Xunit2 from 1.5.38 to 1.5.70
cuteboy0323/Aaron.Akka.Streams.BackpressureMonitor#12
Open
Open
This was referenced Jul 6, 2026
This was referenced Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Widen the
ActorTaskScheduler(ActorCell)constructor frominternaltoprotected internal, so external libraries can subclassActorTaskSchedulerand override the existingOnBeforeTaskStarted/OnAfterTaskCompletedhooks without reflection or runtime codegen.No behavior change — this is a pure access widening. The
internalhalf ofprotected internalpreserves the existingnew ActorTaskScheduler(this)call site insideActorCell, so no other source inAkka.dllhad to move.Why
OnBeforeTaskStarted/OnAfterTaskCompletedhave always beenprotected virtual. They're clearly intended as an extension point — the whole reason for their existence is to let subclasses observe the async handler lifecycle insideActorTaskScheduler.RunTask. But the base constructor isinternal, so in practice no assembly outsideAkka.dllcan actually subclass and wire them up. The extension point has been half-exposed ever since it was added.Motivation (Phobos)
This blocks a real fix in Phobos (the Akka.NET observability plugin from Petabridge).
Phobos's
PhobosActorCellopens anakka.msg.recvspan at the start of each message and disposes it synchronously in afinallyblock whenbase.ReceiveMessagereturns. ForReceiveAsync<T>/CommandAsync<T>handlers,base.ReceiveMessagereturns at the firstawaityield point —ActorTaskScheduler.RunTaskhas suspended the mailbox and scheduled the continuation, but the user's Task is still running. Phobos closes the span immediately. All post-await work (Task.WhenAll, HTTP calls, external validation, nestedAsks) is invisible in traces, and downstream customer-facing spans show durations of ~1 ms for handlers that actually take 100+ ms.The correct fix is for Phobos to subclass
ActorTaskScheduler, overrideOnBeforeTaskStartedto capture the span, overrideOnAfterTaskCompletedto dispose it once the Task truly completes, and expose the custom scheduler via aPhobosActorCell.TaskScheduleroverride. Clean, zero-reflection, runs on every TFM.Today that's blocked on the
internalctor. Alternatives we looked at and rejected:IgnoresAccessChecksToGenerator— compile-time trick that widens the reference assembly's view and ships[assembly: IgnoresAccessChecksTo("Akka")]in Phobos. Works beautifully on CoreCLR, butIgnoresAccessChecksTois not honored by the .NET Framework JIT. Phobos has .NET Framework customers, and the type load would throwMethodAccessExceptionon first use — worse than the bug we're fixing.UnsafeAccessor— neither lets a derived class chain to an inaccessible base ctor. The derived class's IL needs a validcall base::.ctor(...)at compile time.Reflection.Emitsubclass — technically cross-runtime but adds codegen surface area, cold-start cost, and is AOT-hostile.Widening one keyword here is by a wide margin the cleanest option, and it's the right API shape regardless of Phobos — the hooks deserved to be genuinely usable from outside the assembly the whole time.
Backport
This needs to ship on both branches:
dev(target of this PR) → 1.6.x linev1.5— a trivial cherry-pick; Phobos 2.11.x depends on Akka 1.5.x and that's where the customer-facing fix lands. Happy to open the backport PR myself once this merges.Test plan
dotnet build src/core/Akka/Akka.csproj -c Release— cleandotnet test src/core/Akka.API.Tests/Akka.API.Tests.csproj --filter "FullyQualifiedName~CoreAPISpec"— 15/15 pass, including the updated ApproveCore snapshots.Netand.DotNetvariants (public API generator reportsprotected internalctors asprotectedin the snapshot view)