microsoft / ClearScript

A library for adding scripting to .NET applications. Supports V8 (Windows, Linux, macOS) and JScript/VBScript (Windows).
https://microsoft.github.io/ClearScript/
MIT License
1.77k stars 148 forks source link

V8 PropertyBag memory leak? #417

Closed logicaloud closed 2 years ago

logicaloud commented 2 years ago

The following code shows a linear increase in Private Bytes, Working Set and Working Set Peak in Performance Monitor running on Windows 10. Handle Count is stable. Memory usage grows to a GB within an hour or so.

The code creates the runtime once with the engine being created and disposed repeatedly. Garbage collection is forced every 15 seconds. It does not seem to matter whether the property bags are created once outside the loop or new for every engine. Compiling vs not compiling the script makes no difference.

Tested with ClearScript v7.3.2 and NET 6 on 64-bit Windows 10.

Is there anything that I should do differently?

var engineThread = new Thread(delegate ()
{
    RunScriptEngine();
});
engineThread.Start();
Console.ReadLine();

void RunScriptEngine()
{
    // Script to copy input values to output values
    const string Script = @"for (let [key, value] of Object.entries(inputValues)) { outputValues[key] = inputValues[key]; }";
    const int NumTestValues = 100;

    var runtime = new Microsoft.ClearScript.V8.V8Runtime();
    var compiledScript = runtime.Compile(Script);

    var lastGCtime = DateTime.UtcNow;

    // loop forever

    while (true)
    {
        using (var engine = runtime.CreateScriptEngine())
        {
            // prepare input / output values

            var inputValues = new Microsoft.ClearScript.PropertyBag();
            var outputValues = new Microsoft.ClearScript.PropertyBag();

            for (var i = 0; i < NumTestValues; ++i)
            {
                var valueName = "value" + i;
                inputValues.Add(valueName, i);
                outputValues.Add(valueName, i);
            }

            engine.AddHostObject("inputValues", inputValues);
            engine.AddHostObject("outputValues", outputValues);

            engine.Execute(compiledScript);

            Thread.Sleep(10);
        }

        // Force garbage collection every 15 seconds

        if ((DateTime.UtcNow - lastGCtime).TotalSeconds > 15)
        {
            runtime.CollectGarbage(true);
            lastGCtime = DateTime.UtcNow;
            Console.Write(".");
        }
    }
}
ClearScriptLib commented 2 years ago

Hi @logicaloud,

Thanks for reporting this!

There appears to be a leak within the .NET runtime's COM infrastructure. ClearScript triggers it when the host passes certain kinds of objects to the script engine, including PropertyBag instances and other so-called "expando" objects.

The good news is that engaging the COM infrastructure is completely unnecessary when dealing with V8. That gives us an easy way to work around the bug.

Our next release, hopefully out later this week, will include the workaround.

Thanks again!

logicaloud commented 2 years ago

That's great, thank you. I'll check it out as soon as it is available.

ClearScriptLib commented 2 years ago

Version 7.3.3 added a workaround for the leak.