You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm getting a memory leak with simple calls from .NET to Python function. The issue is reproducible on all pythonnet versions that I tested. When I test Python calls to .NET, then the memory leak is less obvious, since all resources are released when calling the CPython gc.collect(). Calling the same function from .NET does not release any resources, but calling the CLR GC.WaitForPendingFinalizers() blocks the execution.
usingSystem;usingPython.Runtime;namespacememoryleakpykw{classProgram{staticvoidMain(string[]args){PyObjectpymodule;PyObjectgc;PyObjecttestmethod;PythonEngine.Initialize();IntPtrpylock=PythonEngine.AcquireLock();{PyObjectsys=PythonEngine.ImportModule("sys");// Py.Import("sys");gc=PythonEngine.ImportModule("gc");// Py.Import("sys");sys.GetAttr("path").InvokeMethod("append",newPyString(@"C:\Users\denis.akhiyarov\source\repos\memoryleakpykw\pyinclr"));pymodule=PythonEngine.ImportModule("pyinclr");testmethod=pymodule.GetAttr("testmethod");}PythonEngine.ReleaseLock(pylock);Console.WriteLine("starting test for memory leak, press any key");Console.ReadKey();pylock=PythonEngine.AcquireLock();{for(inti=1;i<=1000000;i++){testmethod.Invoke();}}gc.InvokeMethod("collect");PythonEngine.ReleaseLock(pylock);GC.Collect();//GC.WaitForPendingFinalizers(); // this blocks executionGC.Collect();Console.WriteLine("finished test for memory leak, press any key");Console.ReadKey();}}}
usingSystem;usingPython.Runtime;namespacememoryleakpykw{classProgram{staticvoidMain(string[]args){PyObjectpymodule;PyObjectgc;PyObjecttestmethod;PythonEngine.Initialize();varts=PythonEngine.BeginAllowThread();IntPtrpylock=PythonEngine.AcquireLock();{PyObjectsys=PythonEngine.ImportModule("sys");// Py.Import("sys");gc=PythonEngine.ImportModule("gc");// Py.Import("sys");sys.GetAttr("path").InvokeMethod("append",newPyString(@"C:\Users\denis.akhiyarov\source\repos\memoryleakpykw\pyinclr"));pymodule=PythonEngine.ImportModule("pyinclr");testmethod=pymodule.GetAttr("testmethod");}PythonEngine.ReleaseLock(pylock);Console.WriteLine("starting test for memory leak, press any key");Console.ReadKey();pylock=PythonEngine.AcquireLock();{for(inti=1;i<=1000000;i++){testmethod.Invoke();}}gc.InvokeMethod("collect");PythonEngine.ReleaseLock(pylock);GC.Collect();GC.WaitForPendingFinalizers();// should no blockGC.Collect();GC.WaitForPendingFinalizers();// should no blockPythonEngine.EndAllowThread(ts);Console.WriteLine("finished test for memory leak, press any key");Console.ReadKey();}}}
@dmitriyse I'm not sure how is that supposed to help with the memory leak, but the leak is still there and not released from both GC. WaitForPendingFinalizers still blocks.
I tested this code in the branch #532.
And no any memory leaks occurred.
This image was captured after some minutes after start. So memory consumption is totally leak-free.
When I debugged this branch I faced with unpredictable C# GC behavior, after some amount of valid "Dispose" calls, GC does not generate enough finalizer calls. Looks like some unmanaged actions are not allowed inside GC thread.
@dmitriyse here is another leak, even with your valid finalizer. This is when raising exception in Python and catching it in C#:
deftestmethod(number=3, astring='abc'):
raiseValueError("testing for memory leak")
returnastring*int(number)
usingSystem;usingSystem.ComponentModel;usingPython.Runtime;namespacememoryleakpykw{classProgram{staticvoidMain(string[]args){dynamicpymodule;PyObjectgc;dynamictestmethod;PythonEngine.Initialize();varts=PythonEngine.BeginAllowThreads();IntPtrpylock=PythonEngine.AcquireLock();{PyObjectsys=PythonEngine.ImportModule("sys");// Py.Import("sys");gc=PythonEngine.ImportModule("gc");// Py.Import("sys");sys.GetAttr("path").InvokeMethod("append",newPyString(@"C:\Users\denis.akhiyarov\source\repos\memoryleakpykw\pyinclr"));pymodule=PythonEngine.ImportModule("pyinclr");testmethod=pymodule.testmethod;//.GetAttr("testmethod");}PythonEngine.ReleaseLock(pylock);Console.WriteLine("starting test for memory leak, press any key");Console.ReadKey();doublefloatarg1=5.1f;dynamicres=null;{for(inti=1;i<=1000000;i++){using(Py.GIL()){try{res=testmethod(Py.kw("number",floatarg1),Py.kw("astring","bbc"));}catch(Exceptione){if(i%1000==0){Console.WriteLine(e.Message);}}}}}Console.WriteLine(res);PythonEngine.EndAllowThreads(ts);Console.WriteLine("finished test for memory leak, press any key");Console.ReadKey();}}}
Memory leak example with an exception is not so good. I tried the same example but without exception raising and it still produces memory leak (iterations count should be increased).
Activity
den-run-ai commentedon Sep 11, 2017
Actually
GC.WaitForPendingFinalizers();is blocking, notgc.collect!dmitriyse commentedon Sep 11, 2017
Please try this version of your code:
den-run-ai commentedon Sep 11, 2017
@dmitriyse I'm not sure how is that supposed to help with the memory leak, but the leak is still there and not released from both GC.
WaitForPendingFinalizersstill blocks.den-run-ai commentedon Sep 11, 2017
I got rid of this memory leak by using the following inconvenient disposal code:
Not sure why
rescannot be disposed by either GC?!den-run-ai commentedon Sep 12, 2017
I still don't understand why managed GC from CLR is not calling the finalizer of
PyObjectfor the results of calls into pythonnet:What is holding the reference to these objects or why GC is not activated?
Thanks for the edit @dmitriyse, now when
EndAllowThreadsis afterWaitForPendingFinalizers, then some of resources are released, but not all:I read these Q&A and nothing looks wrong in PyObject:
https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern
https://stackoverflow.com/questions/3038571/whats-the-purpose-of-gc-suppressfinalizethis-in-dispose-method
https://stackoverflow.com/questions/45036/will-the-garbage-collector-call-idisposable-dispose-for-me
https://stackoverflow.com/questions/3038571/whats-the-purpose-of-gc-suppressfinalizethis-in-dispose-method
https://stackoverflow.com/questions/538060/proper-use-of-the-idisposable-interface
https://stackoverflow.com/questions/898828/finalize-dispose-pattern-in-c-sharp
dmitriyse commentedon Sep 12, 2017
I tested this code in the branch #532.

And no any memory leaks occurred.
This image was captured after some minutes after start. So memory consumption is totally leak-free.
When I debugged this branch I faced with unpredictable C# GC behavior, after some amount of valid "Dispose" calls, GC does not generate enough finalizer calls. Looks like some unmanaged actions are not allowed inside GC thread.
den-run-ai commentedon Sep 13, 2017
@dmitriyse I tested your branch for finalizer with .NET Framework 4.6.1 and it looks great!
den-run-ai commentedon Sep 13, 2017
Interestingly .NET 4.0 garbage collector behaves much nicer:
den-run-ai commentedon Sep 14, 2017
Here is the testing for .NET Core 2.0, also looks very solid!
den-run-ai commentedon Sep 15, 2017
@dmitriyse here is another leak, even with your valid finalizer. This is when raising exception in Python and catching it in C#:
dmitriyse commentedon Dec 12, 2017
Memory leak example with an exception is not so good. I tried the same example but without exception raising and it still produces memory leak (iterations count should be increased).
dmitriyse commentedon Dec 12, 2017
I fixed exception related memory leak. See latest update to PR #532
dmitriyse commentedon Dec 12, 2017
We needs to close this issue after PR #532 merge to master
Zamgunner commentedon Nov 14, 2018
guys, any update on merging this fix to mainline?