Skip to content

EventStream subscription does not get automatically removed on actor death causing memory leak #5717

@ghost

Description

Akka.Net v1.4.34 (was discovered in v1.4.17 as that's what we were using but verified to still exist in latest)

Running on .Net Core 3.1

If an Actor subscribes to a message Type on the EventStream and doesn't unsubscribe before dying the actor is garbage collected but the subscription remains and it keeps the ActorCell alive forever along with the Props used to create it.

In our app we have a set of Job actors that are loaded on demand and stay in memory until idle for a time at which point they kill themselves. This works great except the Props (ours are unique per instance due to a GUID in the parameters) and the ActorCell stay in memory forever due to the EventStream subscription never getting freed up. The EventStream subscription dictionaries continue to grow in size as well. We have 1000s of Job actors churning 24/7 so in a 12 hour period, I measured on average 2000 live Job actors at any given moment and 68,000 dead and gone at the end of the period. However 70,000 ActorCells and Props were still alive resulting in about 30MB/24 hours memory leakage at the current churn rate. The total memory leakage depends on the ActorCell overhead and the size of the Props as the contents of the Props are kept alive too - we had 7000 extra GUIDs alive after 12 hours too due to this as they are in the Props.

To repro this I wrote a test program that slowly creates and then slowly kills 100 actors (slow so I can see what's going on and snapshot it easily) and can be configured to either not subscribe at all, subscribe in PreStart and unsubscribe in PostStop or to only subscribe and not unsubscribe. I monitored the memory with SciTech Memory Profiler snapshotting before, during and after for each case.

If set to either not subscribe at all or sub and unsub all is well. All Props and ActorCells are gone except for a few ones used by Akka internally and my Manager Actor that creates my 100 instances.

If instances subscribe and are killed without unsubscribing (like our app), all ActorCells and Props stay around forever although the Actors are gone. It's the EventStream subscriptions holding the reference to the IActorRef (ActorCell) that's doing it. As you can see below I've got 107 ActorCells left. There's just the expected 7 when I unsub manually or don't sub at all.

I ran my test app linked directly with the Akka.Net source code so I could debug into the Akka code and the message to Register to unsubscribe is never received by the EventBusUnsubscriber so it never Context.Watch the actors therefore it doesn't care if they die and are still subscribed. I verified the EventBusUnsubscriber is running it just never does anything. I am unable to figure out why the Register message is never received but I think it never gets sent due to some issue with the code in EventStream.cs RegisterWithUnsubscriber. The IF never seems to evaluate to TRUE that I've seen. I can't figure out what this code is trying to avoid doing but it seems to avoid it all the time...it's too clever for me :-)

Explicitly unsubscribing Self in the PostStop of the Actor 100% fixes the issue.

Here is the code I believe the problem lies in.
Thanks
Tom

*edit - added Repro

        private void RegisterWithUnsubscriber(IActorRef subscriber)
        {
            _initiallySubscribedOrUnsubscriber.Match().With<Left<IImmutableSet<IActorRef>>>(v =>
            {
                if (_initiallySubscribedOrUnsubscriber.CompareAndSet(v,
                    Either.Left<IImmutableSet<IActorRef>>(v.Value.Add(subscriber))))
                {
                    RegisterWithUnsubscriber(subscriber);  // <---- this line never gets called
                }

            }).With<Right<IActorRef>>(unsubscriber =>
            {
                unsubscriber.Value.Tell( new EventStreamUnsubscriber.Register(subscriber)); // <---- neither does this
            });
        }

akkaeventstreams

AkkaMemoryLeakRepro.zip

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions