-
Notifications
You must be signed in to change notification settings - Fork 280
Replace weakref.proxy with strong references for plugin self.model (#… #1194
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
Changes from 2 commits
552f4ee
e2b643f
fa5eef4
e101b0d
71fa380
57a7c21
fae41e8
5366af9
d65ce51
f849433
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 | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,5 @@ | ||||||||||||||||||||||||
| ##@file scip.pxi | ||||||||||||||||||||||||
| #@brief holding functions in python that reference the SCIP public functions included in scip.pxd | ||||||||||||||||||||||||
| import weakref | ||||||||||||||||||||||||
| from os.path import abspath | ||||||||||||||||||||||||
| from os.path import splitext | ||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||
|
|
@@ -2821,6 +2820,7 @@ cdef class Model: | |||||||||||||||||||||||
| self._modelconss = {} | ||||||||||||||||||||||||
| self._generated_event_handlers_count = 0 | ||||||||||||||||||||||||
| self._benders_subproblems = [] # Keep references to Benders subproblem Models | ||||||||||||||||||||||||
| self._plugins = [] # Keep references to plugins to break cycles in __dealloc__ | ||||||||||||||||||||||||
| self._iis = NULL | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if not createscip: | ||||||||||||||||||||||||
|
|
@@ -2892,15 +2892,30 @@ cdef class Model: | |||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| self.includeEventhdlr(event_handler, name, description) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def __del__(self): | ||||||||||||||||||||||||
| """Free SCIP before the cyclic GC clears Python references. | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| __del__ (tp_finalize) runs before tp_clear, so _plugins and other | ||||||||||||||||||||||||
| Python attributes are still intact. This lets SCIPfree call plugin | ||||||||||||||||||||||||
| callbacks (consfree, eventexit, etc.) safely. The GC handles breaking | ||||||||||||||||||||||||
| the circular references afterwards via tp_clear. | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
| self._free_scip_instance() | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def __dealloc__(self): | ||||||||||||||||||||||||
| # Declare all C variables at the beginning for Cython compatibility | ||||||||||||||||||||||||
| # Safety net: if __del__ didn't run (e.g. interpreter shutdown), | ||||||||||||||||||||||||
| # free SCIP here. Plugin callbacks may not work at this point since | ||||||||||||||||||||||||
| # tp_clear may have already cleared _plugins. | ||||||||||||||||||||||||
| if self._scip is not NULL and self._freescip and PY_SCIP_CALL: | ||||||||||||||||||||||||
| PY_SCIP_CALL( SCIPfree(&self._scip) ) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| if self._scip is not NULL and self._freescip and PY_SCIP_CALL: | |
| PY_SCIP_CALL( SCIPfree(&self._scip) ) | |
| if self._scip is not NULL and self._freescip: | |
| SCIPfree(&self._scip) | |
| self._scip = NULL | |
| self._freescip = False |
Copilot
AI
Mar 3, 2026
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.
_free_scip_instance() calls SCIPfree(&self._scip) without checking the return code. If SCIPfree fails, the code still sets self._scip = NULL and self._freescip = False, which can silently leak the SCIP instance and leave the object in an inconsistent state. Please wrap SCIPfree with PY_SCIP_CALL (or explicitly check the returned SCIP_RETCODE) and only NULL/reset fields after a successful free; if this is being avoided to prevent exceptions in finalizers, consider catching/suppressing the exception in __del__ while still surfacing it in free().
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.
So why do we still need to clear the pointers if it does no longer matter when to do it?
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 is for the case where users are holding PySCIPOpt variables after the corresponding SCIP variables were freed.
Copilot
AI
Mar 3, 2026
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.
free() frees the SCIP instance but leaves large Python-side caches/references (_modelvars, _modelconss, _benders_subproblems, possibly _bestSol) intact. Since the docstring says the Model should not be used after free(), it would be safer to also clear these containers to allow Python memory to be reclaimed immediately and to avoid accidental post-free access to stale wrappers.
| # Clear Python-side caches/references that may hold onto SCIP-related objects. | |
| if hasattr(self, "_modelvars") and self._modelvars is not None: | |
| self._modelvars.clear() | |
| if hasattr(self, "_modelconss") and self._modelconss is not None: | |
| self._modelconss.clear() | |
| if hasattr(self, "_benders_subproblems") and self._benders_subproblems is not None: | |
| self._benders_subproblems.clear() | |
| if hasattr(self, "_bestSol"): | |
| self._bestSol = None |
Copilot
AI
Mar 3, 2026
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.
free() assumes every entry in self._plugins has a .model attribute. This is not true for all plugin base classes (e.g., IISfinder in iisfinder.pxi does not define model), so plugin.model = None can raise AttributeError and prevent cleanup. Please either (a) only store plugins that participate in the Model↔plugin reference cycle, (b) add a model attribute to all plugin base classes that can be included, or (c) guard with hasattr(plugin, "model") / try/except AttributeError so cleanup always completes.
| plugin.model = None | |
| try: | |
| plugin.model = None | |
| except AttributeError: | |
| # Plugin does not expose or allow setting a 'model' attribute. | |
| # In that case, there is no Model↔plugin reference to break. | |
| pass |
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.
Should this not be "to the plugins from
self.model"?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.
changed