Skip to content

Memory leak when disposing actor system with non default ActorRefProvider #2640

@Ralf1108

Description

@Ralf1108

using Akka 1.2.0

If local actor system is created and disposed repeatedly then everything is fine.
If same is done with cluster actor system then there seems to be a memory leak after disposing.

Check tests:

  • IfLocalActorSystemIsStartedAndDisposedManyTimes_ThenThereShouldBeNoMemoryLeak
    Output:
    Got ActorIdentity: 42
    After first run - MemoryUsage: 1mb
    Iteration: 2 - MemoryUsage: 1mb
    Got ActorIdentity: 42
    Got ActorIdentity: 42
    Iteration: 4 - MemoryUsage: 1mb
    Got ActorIdentity: 42
    Got ActorIdentity: 42
    Iteration: 6 - MemoryUsage: 1mb
    ...
    Got ActorIdentity: 42
    Got ActorIdentity: 42
    Iteration: 98 - MemoryUsage: 1mb
    Got ActorIdentity: 42
    Got ActorIdentity: 42
    Iteration: 100 - MemoryUsage: 1mb
    Got ActorIdentity: 42

  • IfClusterActorSystemIsCreatedAndDisposedManyTimes_ThenThereShouldBeNoMemoryLeak
    Output:
    Got ActorIdentity: 42
    After first run - MemoryUsage: 35mb
    Iteration: 2 - MemoryUsage: 35mb
    Got ActorIdentity: 42
    Got ActorIdentity: 42
    Iteration: 4 - MemoryUsage: 102mb
    Got ActorIdentity: 42
    Got ActorIdentity: 42
    Iteration: 6 - MemoryUsage: 169mb
    System.InvalidOperationException : There seems to be a memory leak!

using System;
using Akka.Actor;
using Akka.Cluster.Tools.Client;
using Akka.Configuration;
using NUnit.Framework;

namespace StressTests
{
    [TestFixture]
    public class AkkaTests
    {
        private const string ClusterServerConfig = @"
akka {   
    actor {
        provider = ""Akka.Cluster.ClusterActorRefProvider, Akka.Cluster""
    }
    remote {
        helios.tcp {
            hostname = ""127.0.0.1""
            port = 3000
        }
    }
    cluster {
        seed-nodes = [""akka.tcp://ClusterServer@127.0.0.1:3000""]
    }  
}
";

        private const string ClusterClientConfig = @"
akka {  
    actor {
        provider = ""Akka.Cluster.ClusterActorRefProvider, Akka.Cluster""   
    }
    remote {
        helios.tcp {
            hostname = ""127.0.0.1""
            port = 3001
        }   
    }
    cluster {
        client {
            initial-contacts = [""akka.tcp://ClusterServer@127.0.0.1:3000/system/receptionist""]
        }    
    }
}
";

        [Test]
        public void IfLocalActorSystemIsCreatedAndDisposedManyTimes_ThenThereShouldBeNoMemoryLeak()
        {
            TestForMemoryLeak(RunLocalSystem);
        }

        [Test]
        public void IfClusterActorSystemIsCreatedAndDisposedManyTimes_ThenThereShouldBeNoMemoryLeak()
        {
            TestForMemoryLeak(RunClusterSystem);
        }

        private static void RunLocalSystem()
        {
            var system = ActorSystem.Create("Local");
            var actor = system.ActorOf<TestActor>();
            var result = actor.Ask<ActorIdentity>(new Identify(42)).Result;
            TestContext.Progress.WriteLine("Got ActorIdentity: " + result.MessageId);

            system.Terminate().Wait();
            system.Dispose();
        }

        private void RunClusterSystem()
        {
            var serverAkkaConfig = ConfigurationFactory.ParseString(ClusterServerConfig);
            var serverSystem = ActorSystem.Create("ClusterServer", serverAkkaConfig);
            var serverActor = serverSystem.ActorOf<TestActor>("TestActor");
            var receptionist = ClusterClientReceptionist.Get(serverSystem);
            receptionist.RegisterService(serverActor);

            var clientAkkaConfig = ConfigurationFactory.ParseString(ClusterClientConfig);
            var clientSystem = ActorSystem.Create("ClusterClient", clientAkkaConfig);

            var defaultConfig = ClusterClientReceptionist.DefaultConfig();
            clientSystem.Settings.InjectTopLevelFallback(defaultConfig);
            var clusterClientSettings = ClusterClientSettings.Create(clientSystem);
            var clientActor = clientSystem.ActorOf(ClusterClient.Props(clusterClientSettings));

            var result = clientActor.Ask<ActorIdentity>(new ClusterClient.Send("/user/TestActor",new Identify(42))).Result;
            TestContext.Progress.WriteLine("Got ActorIdentity: " + result.MessageId);

            clientSystem.Terminate().Wait();
            serverSystem.Terminate().Wait();

            clientSystem.Dispose();
            serverSystem.Dispose();
        }

        private static void TestForMemoryLeak(Action action)
        {
            const int IterationCount = 100;
            long memoryAfterFirstRun = 0;
            for (var i = 1; i <= IterationCount; i++)
            {
                if (i % 2 == 0)
                {
                    var currentMemory = GC.GetTotalMemory(true) / 1024 / 1024;
                    TestContext.Progress.WriteLine($"Iteration: {i} - MemoryUsage: {currentMemory}mb");

                    if (currentMemory > memoryAfterFirstRun + 100)
                        throw new InvalidOperationException("There seems to be a memory leak!");
                }

                action();

                if (i == 1)
                {
                    memoryAfterFirstRun = GC.GetTotalMemory(true) / 1024 / 1024;
                    TestContext.Progress.WriteLine($"After first run - MemoryUsage: {memoryAfterFirstRun}mb");
                }
            }
        }

        private class TestActor : ReceiveActor
        {
        }
    }
}

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions