Skip to content

Commit

Permalink
Add missing PyObject.Dispose calls
Browse files Browse the repository at this point in the history
- Adding _some_ of the missing PyObject.Dispose calls. In the cases
where C# is calling the Python side.
   - Note that Python calls to C# code is correctly handling the
   disposure of resources.
  • Loading branch information
Martin-Molinero committed Apr 10, 2019
1 parent 082906e commit fa122fa
Show file tree
Hide file tree
Showing 17 changed files with 155 additions and 133 deletions.
12 changes: 7 additions & 5 deletions Algorithm/Alphas/AlphaModelPythonWrapper.cs
Expand Up @@ -40,11 +40,11 @@ public override string Name
// if the model defines a Name property then use that
if (_model.HasAttr("Name"))
{
return _model.Name;
return (_model.Name as PyObject).GetAndDispose<string>();
}

// if the model does not define a name property, use the python type name
return _model.__class__.__name__;
return (_model.__class__.__name__ as PyObject).GetAndDispose<string>();
}
}
}
Expand Down Expand Up @@ -80,11 +80,13 @@ public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
using (Py.GIL())
{
var insights = _model.Update(algorithm, data) as PyObject;
foreach (PyObject insight in insights)
var iterator = insights.GetIterator();
foreach (PyObject insight in iterator)
{
yield return insight.AsManagedObject(typeof(Insight)) as Insight;
yield return insight.GetAndDispose<Insight>();
}
insights.Destroy();
iterator.Dispose();
insights.Dispose();
}
}

Expand Down
Expand Up @@ -58,11 +58,13 @@ public override IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorith
using (Py.GIL())
{
var targets = _model.CreateTargets(algorithm, insights) as PyObject;
foreach (PyObject target in targets)
var iterator = targets.GetIterator();
foreach (PyObject target in iterator)
{
yield return target.AsManagedObject(typeof(IPortfolioTarget)) as IPortfolioTarget;
yield return target.GetAndDispose<IPortfolioTarget>();
}
targets.Destroy();
iterator.Dispose();
targets.Dispose();
}
}

Expand Down
6 changes: 4 additions & 2 deletions Algorithm/QCAlgorithm.Python.cs
Expand Up @@ -383,7 +383,9 @@ public void RegisterIndicator(Symbol symbol, PyObject indicator, IDataConsolidat
{
using (Py.GIL())
{
indicator.InvokeMethod("Update", new[] { consolidated.ToPython() });
var data = consolidated.ToPython();
indicator.InvokeMethod("Update", new[] { data });
data.Dispose();
}
};
}
Expand All @@ -400,7 +402,7 @@ public void Plot(string series, PyObject pyObject)
{
try
{
decimal value = ((dynamic)pyObject).Current.Value;
var value = (((dynamic)pyObject).Current.Value as PyObject).GetAndDispose<decimal>();
Plot(series, value);
}
catch
Expand Down
8 changes: 5 additions & 3 deletions Algorithm/Risk/RiskManagementModelPythonWrapper.cs
Expand Up @@ -48,11 +48,13 @@ public override IEnumerable<IPortfolioTarget> ManageRisk(QCAlgorithm algorithm,
using (Py.GIL())
{
var riskTargetOverrides = _model.ManageRisk(algorithm, targets) as PyObject;
foreach (PyObject target in riskTargetOverrides)
var iterator = riskTargetOverrides.GetIterator();
foreach (PyObject target in iterator)
{
yield return target.AsManagedObject(typeof(IPortfolioTarget)) as IPortfolioTarget;
yield return target.GetAndDispose<IPortfolioTarget>();
}
riskTargetOverrides.Destroy();
iterator.Dispose();
riskTargetOverrides.Dispose();
}
}

Expand Down
10 changes: 6 additions & 4 deletions Algorithm/Selection/UniverseSelectionModelPythonWrapper.cs
Expand Up @@ -41,7 +41,7 @@ public override DateTime GetNextRefreshTimeUtc()

using (Py.GIL())
{
return _model.GetNextRefreshTimeUtc();
return (_model.GetNextRefreshTimeUtc() as PyObject).GetAndDispose<DateTime>();
}
}

Expand Down Expand Up @@ -76,11 +76,13 @@ public override IEnumerable<Universe> CreateUniverses(QCAlgorithm algorithm)
using (Py.GIL())
{
var universes = _model.CreateUniverses(algorithm) as PyObject;
foreach (PyObject universe in universes)
var iterator = universes.GetIterator();
foreach (PyObject universe in iterator)
{
yield return universe.AsManagedObject(typeof(Universe)) as Universe;
yield return universe.GetAndDispose<Universe>();
}
universes.Destroy();
iterator.Dispose();
universes.Dispose();
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs
Expand Up @@ -61,8 +61,8 @@ public AlgorithmPythonWrapper(string moduleName)
Logging.Log.Trace($"AlgorithmPythonWrapper(): Python version {PythonEngine.Version}: Importing python module {moduleName}");

var module = Py.Import(moduleName);

foreach (var name in module.Dir())
var pyList = module.Dir();
foreach (var name in pyList)
{
Type type;
var attr = module.GetAttr(name.ToString());
Expand Down Expand Up @@ -90,8 +90,10 @@ public AlgorithmPythonWrapper(string moduleName)

_onOrderEvent = (_algorithm as PyObject).GetAttr("OnOrderEvent");
}
attr.Dispose();
}

module.Dispose();
pyList.Dispose();
// If _algorithm could not be set, throw exception
if (_algorithm == null)
{
Expand Down
100 changes: 51 additions & 49 deletions Common/Extensions.cs
Expand Up @@ -39,6 +39,26 @@ namespace QuantConnect
/// </summary>
public static class Extensions
{
/// <summary>
/// Helper method that will cast the provided <see cref="PyObject"/>
/// to a T type and dispose of it.
/// </summary>
/// <typeparam name="T">The target type</typeparam>
/// <param name="instance">The <see cref="PyObject"/> instance to cast and dispose</param>
/// <returns>The instance of type T. Will return default value if
/// provided instance is null</returns>
public static T GetAndDispose<T>(this PyObject instance)
{
if (instance == null)
{
return default(T);
}
var returnInstance = instance.As<T>();
// will reduce ref count
instance.Dispose();
return returnInstance;
}

/// <summary>
/// Extension to move one element from list from A to position B.
/// </summary>
Expand Down Expand Up @@ -1038,25 +1058,31 @@ public static string ToSafeString(this PyObject pyObject)
{
using (Py.GIL())
{
var value = "";
// PyObject objects that have the to_string method, like some pandas objects,
// can use this method to convert them into string objects
if (pyObject.HasAttr("to_string"))
{
return Environment.NewLine + pyObject.InvokeMethod("to_string").ToString();
var pyValue = pyObject.InvokeMethod("to_string");
value = Environment.NewLine + pyValue;
pyValue.Dispose();
}

var value = pyObject.ToString();
if (string.IsNullOrWhiteSpace(value))
else
{
var pythonType = pyObject.GetPythonType();
if (pythonType.GetType() == typeof(PyObject))
{
value = pythonType.ToString();
}
else
value = pyObject.ToString();
if (string.IsNullOrWhiteSpace(value))
{
var type = pythonType.As<Type>();
value = pyObject.AsManagedObject(type).ToString();
var pythonType = pyObject.GetPythonType();
if (pythonType.GetType() == typeof(PyObject))
{
value = pythonType.ToString();
}
else
{
var type = pythonType.As<Type>();
value = pyObject.AsManagedObject(type).ToString();
}
pythonType.Dispose();
}
}
return value;
Expand Down Expand Up @@ -1103,6 +1129,7 @@ public static bool TryConvert<T>(this PyObject pyObject, out T result)

if (!type.IsAssignableFrom(csharpType))
{
pythonType.Dispose();
return false;
}

Expand All @@ -1111,7 +1138,8 @@ public static bool TryConvert<T>(this PyObject pyObject, out T result)
// If the PyObject type and the managed object names are the same,
// pyObject is a C# object wrapped in PyObject, in this case return true
// Otherwise, pyObject is a python object that subclass a C# class.
string name = ((dynamic) pythonType).__name__;
var name = (((dynamic) pythonType).__name__ as PyObject).GetAndDispose<string>();
pythonType.Dispose();
return name == result.GetType().Name;
}
catch
Expand Down Expand Up @@ -1148,12 +1176,12 @@ public static bool TryConvertToDelegate<T>(this PyObject pyObject, out T result)
}

var code = string.Empty;
var locals = new PyDict();
var types = type.GetGenericArguments();

try
using (Py.GIL())
{
using (Py.GIL())
var locals = new PyDict();
try
{
for (var i = 0; i < types.Length; i++)
{
Expand All @@ -1168,14 +1196,15 @@ public static bool TryConvertToDelegate<T>(this PyObject pyObject, out T result)

PythonEngine.Exec(code, null, locals.Handle);
result = (T)locals.GetItem("delegate").AsManagedObject(typeof(T));

locals.Dispose();
return true;
}
}
catch
{
// Do not throw or log the exception.
// Return false as an exception means that the conversion could not be made.
catch
{
// Do not throw or log the exception.
// Return false as an exception means that the conversion could not be made.
}
locals.Dispose();
}
return false;
}
Expand Down Expand Up @@ -1221,33 +1250,6 @@ public static string GetEnumString(this int value, PyObject pyObject)
}
}

/// <summary>
/// Destroys a PyObject
/// https://docs.python.org/2/reference/datamodel.html#object.__del__
/// </summary>
/// <param name="pyObject">PyObject to be destroyed</param>
public static void Destroy(this PyObject pyObject)
{
try
{
if (pyObject.HasAttr("__del__"))
{
pyObject.InvokeMethod("__del__");
}
}
catch (PythonException e)
{
if (string.IsNullOrWhiteSpace(e.StackTrace))
{
throw new Exception($"{(pyObject as dynamic).__qualname__} returned a result with an undefined error set.");
}
else
{
throw e;
}
}
}

/// <summary>
/// Performs on-line batching of the specified enumerator, emitting chunks of the requested batch size
/// </summary>
Expand Down

0 comments on commit fa122fa

Please sign in to comment.