-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[Python] Set memory policy to "strict" #13593
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -113,6 +113,61 @@ As part of this migration, the following build options are deprecated. From ROOT | |||||||||||||||||
|
|
||||||||||||||||||
| ROOT dropped support for Python 3.9, meaning ROOT now requires at least Python 3.10. | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Changed ownership policy for non-`const` pointer member function parameters | ||||||||||||||||||
|
|
||||||||||||||||||
| If you have a member function taking a raw pointer, like `MyClass::foo(T *obj)`, | ||||||||||||||||||
| PyROOT was so far assuming that calling this method on `my_instance` | ||||||||||||||||||
| transfers the ownership of `obj` to `my_instance`. | ||||||||||||||||||
|
|
||||||||||||||||||
| However, this resulted in many memory leaks, since many functions with such a | ||||||||||||||||||
| signature actually don't take ownership of the object. | ||||||||||||||||||
|
|
||||||||||||||||||
| To avoid such memory leaks, PyROOT now doesn't make this guess anymore as of | ||||||||||||||||||
| ROOT 6.32. Because of this change, some double-deletes or dangling references | ||||||||||||||||||
| might creep up in your scripts. These need to be fixed by properly managing | ||||||||||||||||||
| object lifetime with Python references. | ||||||||||||||||||
|
Comment on lines
+125
to
+128
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| You can fix the dangling references problem for example via: | ||||||||||||||||||
|
|
||||||||||||||||||
| 1. Assigning the object to a python variable | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| 2. Creating an owning collection that keeps the objects alive | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be further specified |
||||||||||||||||||
| 3. Writing a pythonization for the member function that does the ownership | ||||||||||||||||||
| transfer if needed | ||||||||||||||||||
|
Comment on lines
+134
to
+135
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| The double-delete problems can be fixed via: | ||||||||||||||||||
|
|
||||||||||||||||||
| 1. Drop the ownership on the Python side with `ROOT.SetOwnership(obj, False)` | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| 3. Writing a pythonization for the member function that drops the ownership on the Python side as above | ||||||||||||||||||
|
|
||||||||||||||||||
| This affects for example the `TList::Add(TObject *obj)` member function, which | ||||||||||||||||||
| will not transfer ownership from PyROOT to the TList anymore. The new policy | ||||||||||||||||||
| fixes a memory leak, but at the same time it is not possible anymore to create | ||||||||||||||||||
| the contained elements in place: | ||||||||||||||||||
|
Comment on lines
+142
to
+145
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is now invalid in light of the latest Pythonizations you added, right? |
||||||||||||||||||
|
|
||||||||||||||||||
| ```python | ||||||||||||||||||
| # A TList is by default a non-owning container | ||||||||||||||||||
| my_list = ROOT.TList() | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| # This is working, but resulted in memory leak prior to ROOT 6.32: | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| obj_1 = ROOT.TObjString("obj_1") | ||||||||||||||||||
| my_list.Add(obj_1) | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| # This is not allowed anymore, as the temporary would be | ||||||||||||||||||
| # deleted immediately leaving a dangling pointer: | ||||||||||||||||||
| my_list.Add(ROOT.TObjString("obj_2")) | ||||||||||||||||||
|
Comment on lines
+157
to
+159
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you remind me where this is enforced in code? |
||||||||||||||||||
|
|
||||||||||||||||||
| # Python reference count to contained object is now zero, | ||||||||||||||||||
| # TList contains dangling pointer! | ||||||||||||||||||
|
Comment on lines
+161
to
+162
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also this is now not true thanks to the latest changes, right? |
||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| **Note:** You can change back to the old policy by calling | ||||||||||||||||||
| `ROOT.SetMemoryPolicy(ROOT.kMemoryHeuristics)` after importing ROOT, but this | ||||||||||||||||||
| should be only used for debugging purposes and this function might be removed | ||||||||||||||||||
| in the future! | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| ## Command-line utilities | ||||||||||||||||||
|
|
||||||||||||||||||
| ## JavaScript ROOT | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -63,3 +63,26 @@ def _SetDirectory_SetOwnership(self, dir): | |||||
| import ROOT | ||||||
|
|
||||||
| ROOT.SetOwnership(self, False) | ||||||
|
|
||||||
|
|
||||||
| def declare_cpp_owned_arg(position, name, positional_args, keyword_args, condition=lambda _: True): | ||||||
| """ | ||||||
| Helper function to drop Python ownership of a specific funciton argument | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| with a given position and name, referring to the C++ signature. | ||||||
|
|
||||||
| positional_args and keyword_args should be the usual args and kwargs passed | ||||||
| to the function, and condition is an optional condition on which the Python | ||||||
| ownership is dropped. | ||||||
| """ | ||||||
| import ROOT | ||||||
|
|
||||||
| arg = None | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not required in Python, unless you expect sometimes the two |
||||||
|
|
||||||
| # has to match the C++ argument name | ||||||
| if name in keyword_args: | ||||||
| arg = keyword_args[name] | ||||||
| elif len(positional_args) > position: | ||||||
| arg = positional_args[0] | ||||||
|
|
||||||
| if condition(arg): | ||||||
| ROOT.SetOwnership(arg, False) | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # Authors: | ||
| # * Jonas Rembser 09/2023 | ||
|
|
||
| ################################################################################ | ||
| # Copyright (C) 1995-2020, Rene Brun and Fons Rademakers. # | ||
| # All rights reserved. # | ||
| # # | ||
| # For the licensing terms see $ROOTSYS/LICENSE. # | ||
| # For the list of contributors see $ROOTSYS/README/CREDITS. # | ||
| ################################################################################ | ||
|
|
||
|
|
||
| class RooPlot(object): | ||
| def addObject(self, *args, **kwargs): | ||
| from ROOT._pythonization._memory_utils import declare_cpp_owned_arg | ||
|
|
||
| # Python should transfer the ownership to the RooPlot | ||
| declare_cpp_owned_arg(0, "obj", args, kwargs) | ||
|
|
||
| return self._addObject(*args, **kwargs) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -181,6 +181,9 @@ | |
|
|
||
| import cppyy | ||
|
|
||
| from . import pythonization | ||
|
|
||
|
|
||
| def set_size(self, buf): | ||
| # Parameters: | ||
| # - self: graph object | ||
|
|
@@ -213,3 +216,26 @@ def set_size(self, buf): | |
| # Add the composite to the list of pythonizors | ||
| cppyy.py.add_pythonization(comp) | ||
|
|
||
| def _TMultiGraph_Add(self, *args, **kwargs): | ||
| """ | ||
| The TMultiGraph takes ownership of the added graphs, unless we're using the | ||
| Add(TMultiGraph*) overload, in which cases it adopts the graphs inside the | ||
| other TMultiGraph. | ||
|
Comment on lines
+221
to
+223
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From this phrasing it seems that also for the |
||
| """ | ||
| from ROOT._pythonization._memory_utils import declare_cpp_owned_arg | ||
|
|
||
| def condition(gr): | ||
| import ROOT | ||
|
|
||
| return gr and not isinstance(gr, ROOT.TMultiGraph) | ||
|
|
||
| declare_cpp_owned_arg(0, "graph", args, kwargs, condition=condition) | ||
|
|
||
| self._Add(*args, **kwargs) | ||
|
|
||
|
|
||
| @pythonization("TMultiGraph") | ||
| def pythonize_tmultigraph(klass): | ||
| # Pythonizations for TMultiGraph::Add | ||
| klass._Add = klass.Add | ||
| klass.Add = _TMultiGraph_Add | ||
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.