Skip to content

Commit adec698

Browse files
h-g-sCopilot
andcommitted
fix: guard __del__ against partially-constructed objects (PyPy safe)
On PyPy, the GC may call __del__ on an object whose __init__ raised before all attributes were set (e.g. SolverGurobi.__init__ raises early when Gurobi is not installed, before _ownsModel/_venv_loaded are set). CPython avoids this because reference-counting drops the object immediately; PyPy's tracing GC processes it later. Replace bare attribute access in __del__ with getattr(..., default) in: - SolverGurobi.__del__: guard _ownsModel and _venv_loaded - SolverHighs.__del__: guard _model (avoid calling Highs_destroy(NULL)) - SolverCbc.__del__: guard _model (avoid calling Cbc_deleteModel(NULL)) - SolverOsi.__del__: guard owns_solver Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 10854ab commit adec698

3 files changed

Lines changed: 9 additions & 6 deletions

File tree

mip/cbc.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,7 +1645,8 @@ def remove_vars(self, varsList: List[int]):
16451645
cbclib.Cbc_deleteCols(self._model, len(varsList), idx)
16461646

16471647
def __del__(self):
1648-
cbclib.Cbc_deleteModel(self._model)
1648+
if getattr(self, "_model", None) is not None:
1649+
cbclib.Cbc_deleteModel(self._model)
16491650

16501651
def get_problem_name(self) -> str:
16511652
namep = self.__name_space
@@ -1776,7 +1777,7 @@ def __clear_sol(self: "SolverOsi"):
17761777
self.__obj_val = None
17771778

17781779
def __del__(self):
1779-
if self.owns_solver:
1780+
if getattr(self, "owns_solver", False):
17801781
cbclib.Osi_deleteSolver(self.osi)
17811782

17821783
def add_var(

mip/gurobi.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,11 +428,12 @@ def __clear_sol(self):
428428
self.__obj_val = None
429429

430430
def __del__(self):
431-
# freeing Gurobi model and environment
432-
if self._ownsModel:
431+
# Guard against partially-constructed objects (e.g. PyPy GC may call
432+
# __del__ even if __init__ raised before setting these attributes).
433+
if getattr(self, "_ownsModel", False):
433434
if self._model:
434435
GRBfreemodel(self._model)
435-
if self._env and self._venv_loaded:
436+
if self._env and getattr(self, "_venv_loaded", False):
436437
GRBfreeenv(self._env)
437438

438439
def add_var(

mip/highs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,8 @@ def __init__(self, model: mip.Model, name: str, sense: str):
800800

801801
def __del__(self):
802802
self._name_buffer = None
803-
self._lib.Highs_destroy(self._model)
803+
if getattr(self, "_model", None) is not None:
804+
self._lib.Highs_destroy(self._model)
804805

805806
# ── Cache grow / flush helpers ─────────────────────────────────────────
806807

0 commit comments

Comments
 (0)