【问题标题】:IronPython 2.7.7 C# Integration Memory LeakIronPython 2.7.7 C# 集成内存泄漏
【发布时间】:2018-01-31 20:28:48
【问题描述】:

我正在集成 IronPython 脚本以在 C# 引擎下运行。 C# 引擎构建“ScriptValue”对象的字典并将其传递给 IronPython 脚本,然后使用这些对象进行计算。 'ScriptValue' 对象在一个单独的类库中并实现了 'MarshalByRefObject' 并且是一个简单的 .net 对象(仅存储 double 和 bool 值)。脚本运行频繁。

第一次尝试: 我实例化了 IronPython 引擎并运行了脚本。随着运行的进行,我可以看到内存使用量正在快速增加。最终在一天或运行后,应用程序因内存不足异常而崩溃。我尝试保持 IronPythonEngine 的一个实例处于活动状态并在每次运行时重新启动一个新实例。我还尝试关闭 IronPython 引擎,但内存会持续增加。

第二次尝试: 在对此进行了大量研究之后,提出了尝试在单独的 AppDomain 中运行引擎并在完成运行脚本后卸载 AppDomain 的建议。然后我实现了这个并创建了一个新的 AppDomain 并在运行完成后将其卸载。这似乎在一定程度上有所帮助,但内存泄漏仍然存在,尽管它以较慢的速度蔓延。

我进行了各种内存分析,似乎 IronPython 或 DLR 土地上的某个地方的非托管内存没有被释放,这会加班加点。随着 AppDomain 的卸载,托管内存似乎正在被清除。

C# 引擎本身相当复杂,并与 MS SQL、IronPython、Data Historian 和 Asset Database 交互。我不会详细介绍此问题,因为我已经能够通过将所有其他组件取出到一个简单的 Windows 窗体应用程序中来重现该问题。

我现在在计时器下运行的代码是:

private void RunEngine()
{

    ScriptEngine pythonEngine = null;
    AppDomain sandbox = null;
    ScriptSource source = null;
    ScriptScope scope = null;
    dynamic subClass = null;
    ObjectOperations ops = null;
    dynamic instance = null;
    dynamic result = null;

    Dictionary<string, ScriptValue> scriptInputValues = GetIronPythonScriptInputAttributeValues();
    Dictionary<string, ScriptValue> scriptOutputValues = GetIronPythonScriptOutputAttributes();

    // Setup PythonEngine options
    Dictionary<string, object> options = new Dictionary<string, object>();
    //options["Debug"] = ScriptingRuntimeHelpers.True;
    options["ExceptionDetail"] = ScriptingRuntimeHelpers.True;
    options["ShowClrExceptions"] = ScriptingRuntimeHelpers.True;

    // Create a sandbox to run the IronPython scripts in
    sandbox = AppDomain.CreateDomain("IronPythonSandbox",
                                                      AppDomain.CurrentDomain.Evidence,
                                                    new AppDomainSetup() { ApplicationBase = AppDomain.CurrentDomain.BaseDirectory, ApplicationName = "IronPythonSandbox" },
                                                    new PermissionSet(PermissionState.Unrestricted));

    // Create the python engine
    pythonEngine = Python.CreateEngine(sandbox, options);
    source = pythonEngine.CreateScriptSourceFromFile(@"\\server2\Projects\Customer\Development\Scripts\calculation.py");
    var compiled = source.Compile();
    scope = pythonEngine.CreateScope();
    //source.Execute(scope);
    compiled.Execute(scope);
    subClass = scope.GetVariableHandle("Calculate");
    ops = pythonEngine.Operations;
    instance = ops.Invoke(subClass, scriptInputValues, scriptOutputValues);
    result = instance.Unwrap();

    if (scriptInputValues?.Count > 0) { scriptInputValues.Clear(); scriptInputValues = null; }
    if (scriptOutputValues?.Count > 0) { scriptOutputValues.Clear(); scriptOutputValues = null; }

    result = null;
    instance = null;
    ops = null;
    subClass = null;
    scope = null;
    source = null;
    pythonEngine?.Runtime?.Shutdown();
    pythonEngine = null;
    if (sandbox != null) { AppDomain.Unload(sandbox); }
    sandbox = null;

}

我现在已经将脚本剥离成裸露的内容来测试内存问题,它是这样的,并没有进行任何实际的计算。

import clr
import sys

# Import integration library to allow for access to the required .Net object types
sys.path.append(r"C:\Program Files\Company\RTCM Worker") # Include the path to the .Net Library
clr.AddReference('RTCM.Worker.IPy.Integration.Library.dll')
import RTCM.Worker.IPy.Integration.Library

import System
from System.Collections.Generic import Dictionary

sys.path.append(r"\\server2\Projects\Customer\Development\Scripts") # Include the path to the module
from constants import *
from sharedfunctions import *

import math


def Calculate(scriptInputValues, scriptOutputValues):

    returnValue = True

    try:

        # Parameter validations

        if returnValue: # Only proceed with the calculation if all inputs are valid

            ## Script logging related objects
            #ENABLE_SCRIPTLOGGING = scriptOutputValues[C_EnableScriptLogging].Value
            #SCRIPT_LOG = scriptOutputValues[C_ScriptLog].Value

            # Get all the required input parameter values
            AMB_TEMP = scriptInputValues[C_AmbientTemperature].Value
            GND_AIR = scriptInputValues[C_GroundAir].Value
            MAX_DESIGN_TEMP = scriptInputValues[C_MaximumDesignTemperature].Value
            g = scriptInputValues[C_RatingCalculationConstants_g].Value
            CONDUCTOR_DIA = scriptInputValues[C_ConductorDIA].Value
            WIND_SPEED = scriptInputValues[C_WindSpeed].Value # From lookup table and no conversion needed as this is in m/s
            DEFAULT_WIND_ANGLE = scriptInputValues[C_WindBearing].Value
            SIGMA = scriptInputValues[C_Rating_Calculation_Constants_SIGMA].Value
            CONDUCTOR_EMISSIVITY = scriptInputValues[C_ConductorEmissivity].Value
            SOLAR_ABSORPTION = scriptInputValues[C_SolarAbsorption].Value
            SOLAR_DIRECT = scriptInputValues[C_SolarDirect].Value
            GROUND_REFLECTIVITY = scriptInputValues[C_GroundReflectivity].Value
            SOLAR_DIFFUSE = scriptInputValues[C_SolarDiffuse].Value
            CONDUCTOR_SKIN_EFFECT = scriptInputValues[C_ConductorSkinEffect].Value
            CONDUCTOR_MAG_EFFECT = scriptInputValues[C_ConductorMAGEffect].Value
            CONDUCTOR_DC_RESISTANCE = scriptInputValues[C_ConductorDCResistance].Value
            CONDUCTOR_ALPHA = scriptInputValues[C_ConductorAlpha].Value


            # Destroy all referenced objects
            del AMB_TEMP
            del GND_AIR
            del MAX_DESIGN_TEMP
            del g
            del CONDUCTOR_DIA
            del WIND_SPEED
            del DEFAULT_WIND_ANGLE
            del SIGMA
            del CONDUCTOR_EMISSIVITY
            del SOLAR_ABSORPTION
            del SOLAR_DIRECT
            del GROUND_REFLECTIVITY
            del SOLAR_DIFFUSE
            del CONDUCTOR_SKIN_EFFECT
            del CONDUCTOR_MAG_EFFECT
            del CONDUCTOR_DC_RESISTANCE
            del CONDUCTOR_ALPHA
            del scriptInputValues
            del scriptOutputValues

            returnValue = True

    except System.Exception as ex:
        returnValue = False

    return returnValue;

一些随着时间推移内存逐渐增加的屏幕截图,您会注意到非托管内存正在逐渐增加:

Start of run

some time later

some time later

我现在没有选择了。对于尝试的事情还有其他建议吗?

我尝试过的其他一些事情:

  1. 将 LightweightScopes 设置为 true 并没有帮助。
  2. 使用 del 关键字删除 IronPython 脚本中引用的对象,但没有帮助。

如果您想了解有关我的设置的任何其他详细信息,请告诉我。

【问题讨论】:

  • 当我在这里发布代码时,我从代码中注释掉了一行,用于允许在代码中调试 Python 脚本。选项[“调试”] = ScriptingRuntimeHelpers.True;出于好奇,我从应用程序中取出了它,让它在最后一个小时内运行,没有像以前那样出现内存泄漏。我将在接下来的几天内对此进行监控,看看这是否真的是它的原因并报告回来。我已经介绍的另一件事是这个,我现在将遍历代码以查看哪些有帮助 options["LightweightScopes"] = ScriptingRuntimeHelpers.True;

标签: c# memory-leaks ironpython


【解决方案1】:

每次在 C# 引擎中执行 IronPython 2.7.5 脚本时,我遇到了完全相同的内存泄漏问题。

您应该在每个脚本执行结束时手动断开与 MarshalByRef 对象的连接,否则您可能会一直持有对象。如果在 MarshalByRef 对象中,您已覆盖 InitializeLifetimeService 以防止远程处理异常,则必须手动断开连接,如下所示:

System.Runtime.Remoting.RemotingServices.Disconnect(MarshalByRefObj obj)

希望您已经成功地从引擎中删除了调试选项,我想知道这是否对您有用。

【讨论】:

  • 嗨,亚历克斯,谢谢。不,它不适用于删除调试选项。我一直在关注其他事情,但会重新审视这个。您能否详细说明断开 MarshalByRef 对象的连接。我现在没有做任何 MarshalByRef 。请参阅我在原文中发布的代码。您是否有任何代码示例与您围绕编组所做的具体操作有关?内存使用率不断上升。
  • 另外,在我的情况下,强制 GC 收集将是过度的,但我将在下一个版本中将其包含到客户端环境中,以查看它对内存使用的影响。脚本运行非常频繁,我需要优化 GC 收集的频率,但作为测试,我会让它在每次运行时运行。
  • 嗨,Alex,当您说断开 MarshalByRefObj 并实施它时,我明白了您的意思,但结果与以前相同。内存泄漏仍然存在。我正在深入研究 .Net Remoting,看看是否还有其他可以尝试的方法,但在现阶段它看起来并不乐观。
  • 嗯。另一个潜在的调查途径是您是否在不必要地编译脚本?当我想到我看过的东西时,我会继续补充。
  • 感谢亚历克斯,非常感谢您的回复。是的,我正在编译脚本,并将其作为我尝试的各种事情的一部分。我将删除编译并重新测试,看看它是否有任何不同。
猜你喜欢
  • 1970-01-01
  • 2010-12-12
  • 2016-01-27
  • 2010-11-11
  • 2017-02-18
  • 1970-01-01
  • 2012-10-12
  • 1970-01-01
  • 2015-11-19
相关资源
最近更新 更多