views:

541

answers:

2

I need some help finding a solution to a memory leak I'm having. I have a C# application (.NET v3.5) that allows a user to run IronPython scripts for testing purposes. The scripts may load different modules from the Python standard library (as included with IronPython binaries). However, when the script is completed, the memory allocated to the imported modules is not garbage collected. Looping through multiple runs of one script (done for stress testing) causes the system to run out of memory during long term use.

Here is a simplified version of what I'm doing.

Script class main function:

public void Run()
{
    // set up iron python runtime engine
    this.engine = Python.CreateEngine(pyOpts);
    this.runtime = this.engine.Runtime;
    this.scope = this.engine.CreateScope();

    // compile from file
    PythonCompilerOptions pco = (PythonCompilerOptions)this.engine.GetCompilerOptions();
    pco.Module &= ~ModuleOptions.Optimized;
    this.script = this.engine.CreateScriptSourceFromFile(this.path).Compile(pco);

    // run script
    this.script.Execute(this.scope);

    // shutdown runtime (run atexit functions that exist)
    this.runtime.Shutdown();
}

An example 'test.py' script that loads the random module (adds ~1500 KB of memory):

import random
print "Random number: %i" % random.randint(1,10)

A looping mechanism that will cause the system to run out of memory:

while(1)
{
    Script s = new Script("test.py");
    s.Run();
    s.Dispose();
}

I added the section to not optimize the compilation based on what I found in this thread, but the memory leak occurs either way. Adding the explicit call to s.Dispose() also makes no difference (as expected). I'm currently using IronPython 2.0, but I've also tried upgrading to IronPython 2.6 RC2 without any success.

How do I get the imported modules in the embedded IronPython script to be garbage collected like normal .NET objects when the scripting engine/runtime goes out of scope?

+1  A: 

Have you tried running the Iron python in its own appDomain? Python.CreateEngine allows you to pass it an AppDomain, which can then be unloaded when the script completes.

Or, based on this discussion, use LightweightScopes option, like so

Dictionary<String, Object> options = new Dictionary<string, object>();
options["LightweightScopes"] = true;
ScriptEngine engine = Python.CreateEngine(options);
ScriptRuntime runtime = engine.Runtime;
Dan McNamara
Simple and effective as a workaround, I had overlooked this. By forcing the AppDomain to unload, the memory leak is controlled (although my overall footprint has increased by using an AppDomain).
cgyDeveloper
Good answer, but I'm going to keep this open for a day or two to see if somebody has a solution that might fix the problem, as this is really just a workaround.
cgyDeveloper
LightweightScopes does not work. Based on the discussion you linked to, it likely only solves the problem of reloading a module within a script where it is already loaded. My problem is the garbage collection that does not occur when the script is completed. I wouldn't be surprised, however, if both problems have the same root cause in the DLR.
cgyDeveloper
I recreated what I think that you are doing, and my memory usage climbs to 70,000K or so, but has not gone any higher. How long is long term use?
Dan McNamara
Which IronPython version are you using? Mine can climb to 120,000 in five minutes or so, and never really levels out. Long term use could be as long as a few days or more in the case of stress testing.
cgyDeveloper
It sounds like you may be onto a legit solution, as the AppDomain solution levels out at about 64,000K, so 70,000K is certainly in the range.
cgyDeveloper
I should be more specific, I am using the LightweightScopes part. without the option enabled my memory quickly builds, with it on it builds up to a threshold and then levels out.
Dan McNamara
Could you put your code in a new answer? I'm trying the LightweightScopes option and my memory is steadily building (over 165,000K and climbing after five minutes as I'm typing this)
cgyDeveloper
Aha! pyOpts["LightweightScopes"] = ScriptingRuntimeHelpers.True works in IronPython 2.6, but pyOpts["LightweightScopes"] = true does not in IronPython 2.0 (perhaps because ScriptingRuntimeHelpers is not defined for that version of the Microsoft.Scripting assembly).
cgyDeveloper
+1  A: 

using Iron Python 2.6 RC 2, and C# 3.5

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;

namespace IPmemTest {
    class IPy {
     private string script = "import random; random.randint(1,10)";

     public IPy() {
     }

     public void run() {
      //set up script environment
      Dictionary<String, Object> options = new Dictionary<string, object>();
      options["LightweightScopes"] = true;
      ScriptEngine engine = Python.CreateEngine(options);
      ScriptRuntime runtime = engine.Runtime;
      ScriptScope scope = runtime.CreateScope();
      var source = engine.CreateScriptSourceFromString(this.script);
      var comped = source.Compile();
      comped.Execute(scope);
      runtime.Shutdown();
            }
    }
}

and my loop is

class Program {
     static void Main(string[] args) {
      while (true) {
       var ipy = new IPy();
       ipy.run();
      }
     }
    }

memory usage increases to about 70,000K, but then levels off.

Dan McNamara
Yeah, this works just fine with IronPython 2.6 RC2. But it didn't work with IronPython 2.0.1. Unfortunately, 2.6 RC2 is having trouble importing variables to the global namespace. I'm going to try 2.0.3 and post results. Thanks for the help so far :)
cgyDeveloper
2.0.3 is no good, either.
cgyDeveloper