-
Notifications
You must be signed in to change notification settings - Fork 747
PyObject finalizer #692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PyObject finalizer #692
Conversation
Codecov Report
@@ Coverage Diff @@
## master #692 +/- ##
==========================================
- Coverage 76.9% 76.69% -0.22%
==========================================
Files 63 64 +1
Lines 5798 5925 +127
Branches 950 977 +27
==========================================
+ Hits 4459 4544 +85
- Misses 1025 1052 +27
- Partials 314 329 +15
Continue to review full report at Codecov.
|
This is duplicate for the #532. There are several PR that are exists more than half of a year, and still not approved. |
@amos402 please review #532 and let me know your feedback. @dmitriyse that code in your PR is really hard to review, it is beyond my understanding of Python/CLR internals. I still give preference to @dmitriyse PR but @amos402 if there is anything extra beyond #532 in your PR, please let us know. |
@denfromufa, ok. I will update those PRs in this week. |
@denfromufa @dmitriyse I reviewed the #532 roughly, it create a thread for decreasing the reference of python objects, it would slow down Python if you get GIL oftenly. Note from python document
It is not safe to call this function when it is unknown which thread (if any) currently has the global interpreter lock. This function is not available when thread support is disabled at compile time. |
I agree with @amos402 Py_AddPendingCall can give some benefits and my approach can be improved for Python 3.1+. We are looking for different directions: single and multi threaded environment. Dedicated thread was the only approach that succesfully solve memory leak in the my multithreaded application. @amos402 Please check this #541 bug on your branch, ensure that your finalizers approach also elliminates memory leak. |
@dmitriyse I try two cases, it seems no problems except
|
src/runtime/finalizer.cs
Outdated
private ConcurrentQueue<IDisposable> _objQueue = new ConcurrentQueue<IDisposable>(); | ||
|
||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] | ||
private delegate int PedingCall(IntPtr arg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PendingCall
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The calling type for Py_AddPendingCall
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know, there's just a typo in the name ;)
src/runtime/finalizer.cs
Outdated
|
||
public event EventHandler<CollectArgs> CollectOnce; | ||
|
||
private ConcurrentQueue<IDisposable> _objQueue = new ConcurrentQueue<IDisposable>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have a few places that fail in presence of multiple appdomains, this would too, I guess. Can you add a comment on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What failures will have occurred, can you give me some examples?
src/runtime/finalizer.cs
Outdated
private bool _pending = false; | ||
private readonly object _collectingLock = new object(); | ||
public int Threshold { get; set; } | ||
public bool Enable { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these public?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make users can adjust the finalizer's configuration.
src/runtime/finalizer.cs
Outdated
} | ||
Instance.DisposeAll(); | ||
Instance.CallPendingFinalizers(); | ||
Runtime.PyErr_Clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you calling this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure no garbage left and clear the pending calls.
Pending calls wouldn't clear after calling Py_Finalize, if reset the .net environment, the function pointers would point to unknown memory and cause crash on calling them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you calling PyErr_Clear
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
emmm...It seems I should expose the exceptions in DisposeAll after each obj.Dispose()
|
||
private void AddPendingCollect() | ||
{ | ||
lock (_collectingLock) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lock should probably encompass the whole function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it needs to be lock whole function, the flag already set.
I can't image any situations that Py_AddPendingCall will be called in multiple threads on same time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't follow, either you expect multiple concurrent executions, then the lock needs to surround the whole function, or you don't, then you don't need any locking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, yes, thank you for you reminder, I neglect that there is another store at line 114.
src/runtime/runtime.cs
Outdated
@@ -198,6 +199,8 @@ public class Runtime | |||
internal static bool IsPython2 = pyversionnumber < 30; | |||
internal static bool IsPython3 = pyversionnumber >= 30; | |||
|
|||
public static int MainManagedThreadId { get; internal set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this rather a private set
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it should be. I will modify it later.
src/runtime/runtime.cs
Outdated
@@ -350,6 +354,7 @@ internal static void Initialize() | |||
|
|||
internal static void Shutdown() | |||
{ | |||
Finalizer.Shutdown(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a particular reason for doing the Finalizer
shutdown first? I would have expected it to come last.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems there is no particular reason for that. I can't remember why I put it on first, maybe just put it casually....
Synchronized fixed
if (_pending) | ||
{ | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this optimisation really needed? If not, I'd rather like the code to be obviously safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a simple double-check, make it check without lock at first.
@amos402 some git conflicts with master branch now. |
This PR causes appveyor CI to get stuck for one hour on each build. |
d9a4c8e
to
a4bb82d
Compare
😥I still can't figure out why it just crash on 3.7 on Linux |
Already fixed it. It crash on 3.7 because I changed PYTHONMALLOC in test case. |
I added ref count check on debug mode, it should be helpful for finding the bugs like this PyString s1 = new PyString("test_string");
// s2 steal a reference from s1
PyString s2 = new PyString(s1.Handle); |
What does this implement/fix? Explain your changes.
Does this close any currently open issues?
...
Any other comments?
...
Checklist
Check all those that are applicable and complete.
AUTHORS
CHANGELOG