Skip to content

fix(signalr): scope stream materializer to per-session actor to prevent actor leak#192

Merged
Aaronontheweb merged 1 commit into
devfrom
fix/signalr-actor-materializer-leak
Mar 9, 2026
Merged

fix(signalr): scope stream materializer to per-session actor to prevent actor leak#192
Aaronontheweb merged 1 commit into
devfrom
fix/signalr-actor-materializer-leak

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Collaborator

Summary

  • Root cause: SessionRegistry.MaterializeAndBindSessionAsync() called .Run(_system) for all stream materializations, creating stream stage actors under the global StreamSupervisor-0. These accumulated over the daemon's lifetime and were only torn down at full system shutdown — visible as a large burst of Flow-*-actorRefSource* actors in shutdown logs.
  • Fix: Introduces SignalRSessionActor, a per-session binding actor mirroring the SlackThreadBindingActor pattern. Each actor creates Context.Materializer(namePrefix:"signalr") scoped to its own context, making all stream actors its children. When the session ends, the actor stops → materializer disposes → all stream children are automatically cleaned up.
  • New actor hierarchy: /netclaw/user/signalr-gateway/{sessionId} (owned, lifecycle-bound streams) replaces orphaned actors under StreamSupervisor-0.

Changes

  • SignalRSessionActor — per-session binding actor with Initializing/Active state machine, scoped materializer, pipeline reinitialization on stream termination, and PostStop cleanup
  • SignalRGatewayHostingExtensions — registers signalr-gateway (GenericChildPerEntityParent) and SignalRGatewayActorKey
  • SessionRegistry — rewritten to delegate pipeline lifecycle to the actor via Tell; now tracks session existence only (no stream state)
  • Program.cs — wires .WithSignalRGateway() into the Akka builder
  • SessionRegistryTests — rewritten to cover registry coordination behavior using a StubRequiredActor

Test plan

  • All existing tests pass (dotnet test)
  • dotnet slopwatch analyze — no new violations
  • Start daemon, run sessions, shut down — shutdown logs should show no Flow-*-actorRefSource* burst under StreamSupervisor-0
  • Session actors appear under /netclaw/user/signalr-gateway/ and stop when sessions end

…nt actor leak

SignalR sessions were materializing Akka.Streams pipelines using the system-level
materializer (.Run(_system)), which created all stream stage actors as children of
the global StreamSupervisor-0. These actors accumulated over the daemon's lifetime
and were only torn down at full system shutdown, producing the large burst of
Flow-*-actorRefSource* actors visible in shutdown logs.

Introduces SignalRSessionActor, a per-session binding actor that mirrors the
SlackThreadBindingActor pattern: it creates Context.Materializer(namePrefix:"signalr")
scoped to its own context so all stream actors become its children and are
automatically stopped when the session ends.

- Add SignalRSessionActor with Initializing/Active state machine, scoped materializer,
  pipeline reinitialization on stream termination, and clean PostStop disposal
- Add SignalRGatewayHostingExtensions to register the signalr-gateway parent actor
  (GenericChildPerEntityParent) and SignalRGatewayActorKey for DI lookup
- Rewrite SessionRegistry to delegate pipeline lifecycle to the actor via Tell;
  registry now tracks session existence only (no stream state)
- Wire WithSignalRGateway() into Program.cs Akka builder
- Rewrite SessionRegistryTests to cover registry coordination behavior using a
  StubRequiredActor that returns ActorRefs.Nobody

After this change: /netclaw/user/signalr-gateway/{sessionId} actors own their
streams; stopping the actor cleans up all stream children immediately.
@Aaronontheweb Aaronontheweb force-pushed the fix/signalr-actor-materializer-leak branch from 62c192c to 56c6523 Compare March 9, 2026 02:24
@Aaronontheweb Aaronontheweb merged commit 56b4d01 into dev Mar 9, 2026
4 checks passed
@Aaronontheweb Aaronontheweb deleted the fix/signalr-actor-materializer-leak branch March 9, 2026 02:34
@Aaronontheweb Aaronontheweb mentioned this pull request Mar 13, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant