Does ClearScript have memory leak?

Sep 26, 2014 at 3:32 AM
Edited Sep 26, 2014 at 7:23 AM
I wrote a simple application, which exposed a custom type (i.e. Foo) to outside. So the user could invoke it's method like this:
var val1 = new Foo();
Console.WriteLine(val1.Method());
From the memory detecting tool, I found the memory increased a lot after each time I ran this simple script. It seems a lot of Microsoft.ClearScript.HostItem objects exists in memory, and never gone away.

So I am quite worried about this, if a lot of users would create instance of Foo, it would consume very a lot of memories, which seems terrible.

Any thoughts over this problem?

Update:

I also detected that, the ClearScript captures each script I ran, and keep it in memory. That means every script (in string format) user runs would be in memory permanently, which also can increase the memory usage while user base increasing.

Anyone can explain the memory usage mechanism for me, or the best practice something?
Coordinator
Sep 26, 2014 at 1:32 PM
Hello JerryBian!

Are you using V8? That script engine heavily favors performance over memory efficiency, and is particularly lazy when it comes to garbage collection. If you use a single long-lived V8ScriptEngine instance to run many scripts, you should call ScriptEngine.CollectGarbage() periodically.

We also recommend that you build ClearScript from the latest source code, as several memory leaks have been fixed since the most recent point release.

Good luck!
Sep 28, 2014 at 1:59 AM
Edited Oct 11, 2014 at 6:32 AM
Hi ClearScript,

Thanks for your answer, I have a quick question: should I create a new instance of V8ScriptEngine each time or use a single instance globally?

Another question: Can I just say like that, as time going, the memory of server side would increase continually. For the extreme case, the server would run out of memory finally. I have tried the ScriptEngine.CollectGarbage(true); it only can recover memory a little bit, which means the memory is increasing each time running scripts.
Coordinator
Sep 28, 2014 at 4:39 PM
Hi again,

Regarding your first question, there are several considerations. Do you need to run scripts concurrently? Are your scripts independent, or do they reuse data left over by other scripts? Are your scripts trusted and known to be well-behaved? What are your performance requirements? It's difficult to say what you should do without knowing more about your application.

As for memory leaks, we aren't aware of any in the latest ClearScript code, but there may always be bugs in ClearScript and/or the underlying script engines. Can you provide more information? Which script engine(s) are you using? What do your scripts do, roughly speaking? Does your leak detection tool provide any clues? Can you post a minimal code sample that demonstrates the issue?

Thanks!
Oct 11, 2014 at 7:50 AM
Edited Oct 11, 2014 at 7:53 AM
Hi ClearScript,

I would try to provide as more information as possible.

Users can execute some custom scripts targets to our system, in our server side, we plan to use ClearScript(V8ScriptEngine) to parse and execute the input scripts. All the scripts are independent, they can be run concurrently, so no reused data left over by others.

Speaking of memory leaks, we have some simple codes to demonstrate it.
public class TestData
{
    private readonly string _bigData;

    public TestData()
    {
        _bigData = new string('*', 1024 * 1024);
    }

    public Guid Id { get; set; }

    public DateTime Created { get; set; }
}

public class CloudService
{
    public double Calculate(double num1, double num2)
    {
        return num1 + num2;
    }

    public TestData CreateObj()
    {
        return new TestData
        {
            Created = DateTime.Now,
            Id = Guid.NewGuid()
        };
    }

    public void Output(object obj)
    {
        EngineContext.Output.WriteLine(obj);
    }
}
And we exposed CloudService like this:
ScriptEngine.AddHostObject("service", new CloudService());
Here is how we execute the scripts:
public void RunScript()
{
    const int loopNumber = 100;
    double totalTime = 0;
    var output = string.Empty;
    for (var i = 0; i < loopNumber; i++)
    {
        try
        {
            var stopwatch = Stopwatch.StartNew();
            ScriptEngine.Execute(Scripts);
            stopwatch.Stop();
            totalTime += stopwatch.Elapsed.TotalMilliseconds;
            output = EngineContext.Output.ToString();
        }
        catch (Exception ex)
        {
            output = ex.ToString();
        }

        EngineContext.Output.GetStringBuilder().Clear();
    }

    Output = string.Format("{0}{1}{4} {1}Execution Performance:{1}\rTotal: {2} ms{1}\rAverage: {3} ms.", output,
        Environment.NewLine, totalTime, totalTime / loopNumber,"=========================");
}
We executed the input scripts 100 times to evaluate the performance. The ScriptEngine is global instance, which means we reused it for every script execution.

And the test script:
var obj = service.CreateObj();
service.Output(obj.Id);
We found the memory increased 200MB after the execution, and mostly are HostItem instance(200+ count), all of them has TestData instance with 1MB big data field. Besides that, we fired a timer which call ScriptEngine.CollectGarbage(true) every 1 second, it doesn't help too much.

We use dotMemory to detect the memory issue, here are two screenshots:
Memory Clue
HostItem

Here is our question

  1. Did we use ClearScript in wrong way? You can check the full code here. Use global ScriptEngine instance or create one each time in our scenario?
  2. Can you introduce the cache mechanism succinctly? For example, if all the input scripts are same, ClearScript will cache the only one copy in memory to increase performance or cache every scripts? Do we need to cache it by ourselves?
  3. What's the next release plan, we don't want to compile source code by ourselves?
Coordinator
Oct 14, 2014 at 3:13 PM
Hi JerryBian,

We looked at your code and ran some tests; here's what we found.

  1. You're using an old ClearScript version. Several issues related to memory usage have been fixed recently.
  2. There doesn't appear to be a memory leak in the latest ClearScript code. That is, all host objects released by V8's garbage collector are eventually claimed by the CLR garbage collector.
  3. However, the current implementation of V8ScriptEngine.CollectGarbage(true) is not exhaustive. Contrary to its documentation, the underlying V8 API defers cleanup in many cases. This is a problem in general, but it's particularly devastating when the unclaimed V8 objects are actually proxies to very large host objects. This issue may account for what you're seeing in dotMemory.
We're close to releasing the next version of ClearScript, which includes a much more aggressive implementation of V8ScriptEngine.CollectGarbage(true).

Use global ScriptEngine instance or create one each time in our scenario?

Given the usage pattern you described, we recommend that you create and dispose engine instances as necessary. However, if the overhead of doing so impacts your server's scalability, you might consider a different strategy, such as engine instance pooling.

For example, if all the input scripts are same, ClearScript will cache the only one copy in memory to increase performance or cache every scripts? Do we need to cache it by ourselves?

If you're using a single V8ScriptEngine instance to execute a given script repeatedly, you could gain a significant performance boost by compiling the script via V8ScriptEngine.Compile(). Another possibility might be to invoke script functions that are already stored within the engine. The idea is to avoid the script parser if possible.

What's the next release plan, we don't want to compile source code by ourselves?

The next version should be out shortly, hopefully by the end of this week or early next week. However, we don't release binaries, so you'll have to build it or acquire binaries elsewhere.

Good luck!