fix(gurobi): dispose the solver model before the env on close#826
Merged
Conversation
Register the gurobipy model on the env ExitStack (as Xpress and Mosek already do), so Solver.close() disposes it before the env — the ordering Gurobi requires for the license to be released. Previously the model was only dereferenced, so any user-held model.solver_model reference silently kept the license acquired after close(). to_gurobipy() now detaches the built model from the throwaway solver so the caller keeps ownership. Document the release points in Model.solve(). Closes #459 follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merging this PR will not alter performance
Comparing Footnotes
|
Also note in Model.solve() that the attached solver enables persistent re-solves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mypy rejected Gurobi._detach_solver_model on the Solver-typed return. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The autosummary method tables on the subclass pages show only the first docstring line, so it must carry the license-release fact itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FabianHofmann
approved these changes
Jul 15, 2026
FabianHofmann
left a comment
Collaborator
There was a problem hiding this comment.
thanks for covering this remaining bit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I treid to verify that the gurobi license issue #459 is fully solved. As far as i can see its not perfect yet. I also took the opportunity to make the .close() more discoverable!
Note
The following content was generated by AI (Claude Code).
Follow-up to #459. The env has been context-managed since the license-leak audit, but
Solver.close()only dereferenced thegurobipy.Model— it disposed the env first and relied on refcount GC to free the model. Since Gurobi keeps the underlying environment (and with it the license) alive until every model created from it is freed, any user-heldmodel.solver_modelreference silently kept the license acquired afterclose()/model.solver = None.What changed
Gurobinow registers thegurobipy.Modelon the envExitStack(as Xpress and Mosek already do).close()unwinds LIFO, disposing the model before the env — the ordering the Gurobi docs require — and explicitdispose()releases the license even while user code still holds a reference.to_gurobipy()detaches the built model from its throwaway solver so the caller keeps ownership (its teardown would otherwise now dispose the returned model).Model.solve()docstring documents when the license is released:model.solver.close(),model.solver = None, the nextsolve()call, or model GC.Compatibility with older gurobipy
The fix relies on
gurobipy.Modelsupporting the context-manager protocol. Env and Model have been context managers since Gurobi 9.0, and the oldest gurobipy with wheels for Python 3.11 (linopy's floor) is 10.0 — so every installable combination is covered. Verified identical behavior (context protocol +Model has already been freedon disposed access) on gurobipy 12.0.1 and 13.0.2.Proof it was a footgun
The new test
test_gurobi_close_disposes_held_solver_modelholds asolver_modelreference acrossclose()and asserts the model is disposed. Onmasterit fails — the held model is still fully queryable, i.e. the license is still acquired.Empirical demonstration (gurobipy 13.0.2)
New test against
master(pre-fix):With the fix:
19 passed(-k "close or gurobi"), and the full gurobi/persistent sweep passes (523 passed, 11 skipped).🤖 Generated with Claude Code