I am using JuMP with Gurobi.jl to solve a MIP.
In Julia, pressing Ctrl+C usually kills the entire Julia process instead of just stopping Gurobi.
My goal is:
Stop the optimization at any arbitrary time (not using MIPGap, TimeLimit, or NodeLimit).
Still retrieve the best feasible solution found so far.
I’ve seen that Gurobi supports callbacks via JuMP/MOI. Is it possible to have something like a user interrupt (e.g., pressing a key) that triggers a callback to terminate the solver gracefully ?
If yes, could someone show me the minimal working example of how to set this up in Julia?
Thanks!
Please press once .
e.g.
import JuMP, Gurobi, SparseArrays
N = 50 # 754 seconds
Q = 1. * SparseArrays.dropzeros(
SparseArrays.spdiagm(
[i => rand(-9:9, N - i) for i in 0:div(N, 2)]...
)
)
m = JuMP.Model();
JuMP.@variable(m, rand(-7:-3) <= y[1:N] <= rand(3:7));
JuMP.@variable(m, rand(-9:-5) <= x[1:N] <= rand(5:9));
JuMP.@objective(m, Min, (x'Q)y);
JuMP.set_optimizer(m, Gurobi.Optimizer)
JuMP.optimize!(m); JuMP.solution_summary(m)
test:
julia> JuMP.optimize!(m); JuMP.solution_summary(m)
Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - "Debian GNU/Linux 12 (bookworm)")
CPU model: AMD EPYC 7763 64-Core Processor, instruction set [SSE2|AVX|AVX2]
Thread count: 128 physical cores, 256 logical processors, using up to 32 threads
Optimize a model with 0 rows, 100 columns and 0 nonzeros
Model fingerprint: 0x8f14f73d
Model has 927 quadratic objective terms
Coefficient statistics:
Matrix range [0e+00, 0e+00]
Objective range [0e+00, 0e+00]
QObjective range [2e+00, 2e+01]
Bounds range [3e+00, 9e+00]
RHS range [0e+00, 0e+00]
Continuous model is non-convex -- solving as a MIP
Found heuristic solution: objective 1972.0000000
Found heuristic solution: objective -90.0000000
Presolve removed 0 rows and 5 columns
Presolve time: 0.01s
Presolved: 921 rows, 1016 columns, 2763 nonzeros
Found heuristic solution: objective -1365.000000
Variable types: 0 continuous, 1016 integer (1016 binary)
Root relaxation: objective -1.668040e+05, 396 iterations, 0.00 seconds (0.00 work units)
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
0 0 -166804.00 0 95 -1365.0000 -166804.00 - - 0s
H 0 0 -31392.00000 -166804.00 431% - 0s
H 0 0 -42554.00000 -166804.00 292% - 0s
H 0 0 -44102.00000 -166804.00 278% - 0s
H 0 0 -45200.00000 -166804.00 269% - 0s
0 0 -154413.00 0 127 -45200.000 -154413.00 242% - 0s
0 0 -148261.00 0 145 -45200.000 -148261.00 228% - 0s
0 0 -148189.50 0 145 -45200.000 -148189.50 228% - 0s
0 2 -148189.50 0 145 -45200.000 -148189.50 228% - 0s
H 31 64 -46492.00000 -134496.00 189% 132 0s
H 50 64 -47016.00000 -133618.25 184% 121 0s
H 51 64 -47256.00000 -133215.88 182% 121 0s
H 511 542 -48548.00000 -129601.50 167% 82.7 0s
H 545 689 -48697.00000 -129601.50 166% 82.5 0s
H 572 689 -49412.00000 -129601.50 162% 82.1 0s
* 1639 1572 40 -50381.00000 -128661.00 155% 69.6 0s
* 1640 1572 40 -50573.00000 -128661.00 154% 69.6 0s
* 1642 1572 38 -54946.00000 -128661.00 134% 69.5 0s
H 1794 1693 -57200.00000 -128596.63 125% 69.3 0s
H 1990 1823 -57215.00000 -128479.25 125% 69.1 0s
H 1994 1823 -57981.00000 -128479.25 122% 69.1 0s
H 2020 1823 -58562.00000 -128479.25 119% 69.0 0s
H 2083 1823 -59088.00000 -128479.25 117% 69.1 0s
H 2160 1985 -60250.00000 -128479.25 113% 69.2 0s
H 2340 2000 -60908.00000 -127924.75 110% 69.3 0s
H 2755 2220 -62933.00000 -127388.67 102% 68.3 0s
H 3141 2478 -63149.00000 -126541.00 100% 67.1 0s
58996 26304 -84114.500 28 813 -63149.000 -94544.362 49.7% 42.3 5s
^C
Cutting planes:
Gomory: 42
Flow cover: 340
Zero half: 366
RLT: 7
BQP: 4
Explored 59003 nodes (2510577 simplex iterations) in 7.97 seconds (10.92 work units)
Thread count was 32 (of 256 available processors)
Solution count 10: -63149 -62933 -60908 ... -54946
Solve interrupted
Best objective -6.314900000000e+04, best bound -8.731000000000e+04, gap 38.2603%
User-callback calls 119146, time in user-callback 0.02 sec
solution_summary(; result = 1, verbose = false)
├ solver_name : Gurobi
├ Termination
│ ├ termination_status : INTERRUPTED
│ ├ result_count : 10
│ ├ raw_status : Optimization was terminated by the user.
│ └ objective_bound : -8.73100e+04
├ Solution (result = 1)
│ ├ primal_status : FEASIBLE_POINT
│ ├ dual_status : NO_SOLUTION
│ ├ objective_value : -6.31490e+04
│ └ relative_gap : 3.82603e-01
└ Work counters
├ solve_time (sec) : 7.99659e+00
├ simplex_iterations : 2510577
├ barrier_iterations : 0
└ node_count : 59003
julia> JuMP.value.(x)
50-element Vector{Float64}:
This is not correct either, you can refer to The Julia REPL · The Julia Language
no, it is not working. and i want to add one more thing, i’m using server base gurobi license, not on local machine.
I think the requirement is somewhat unusual.
But you can use GRBterminate(backend(model)) within a callback, as written in GitHub - jump-dev/Gurobi.jl: A Julia interface to the Gurobi Optimizer .
As for achieving your “arbitrary” goal…
Since I have no idea what a cloud solve is like… But in julia maybe you can try some asynchronous programming thing:
import JuMP, Gurobi, SparseArrays
N = 50 # 754 seconds
Q = 1. * SparseArrays.dropzeros(
SparseArrays.spdiagm(
[i => rand(-9:9, N - i) for i in 0:div(N, 2)]...
)
);
m = JuMP.direct_model(Gurobi.Optimizer())
JuMP.@variable(m, rand(-7:-3) <= y[1:N] <= rand(3:7));
JuMP.@variable(m, rand(-9:-5) <= x[1:N] <= rand(5:9));
JuMP.@objective(m, Min, (x'Q)y);
# Run the solve async-ly
Threads.@spawn JuMP.optimize!(m)
# when you want to interrupt, execute this command in the REPL
Gurobi.GRBterminate(JuMP.backend(m))
# you can check the current status
JuMP.solution_summary(m)
JuMP.value.(x)
odow
October 4, 2025, 12:02am
5
Hi @amit_97 , welcome to the forum
i’m using server base gurobi license, not on local machine.
Do you mean the Gurobi instant cloud? Or just the WLS license?
If you’re using the Gurobi instant cloud, the Gurobi documentation sounds like it should support termination, so if CTRL+C isn’t working, then that is a bug we should fix: Callbacks - Gurobi Remote Services Guide
Do you have a reproducible example of your model? What options did you set? (You shouldn’t are any sensitive API keys, etc.)
odow
October 4, 2025, 1:10am
7
Hello @odow , thank you for your response. However, I’m experiencing an issue specifically with VS Code and its integrated terminal. When I run the code through the VS Code terminal, it doesn’t accept any input or respond until Gurobi finishes solving. In contrast, when I execute the same code via PowerShell, the “Ctrl + C” interrupt command works as expected.
property of VS Code terminal:
Command line: C:\Users\xyz\AppData\Local\Programs\Julia-1.10.9\bin\julia.exe
-i --banner=no --project=C:\Users\xyz.julia\environments\v1.10 c:\Users\xyz.cursor\extensions\julialang.language-julia-1.149.2-universal\scripts\terminalserver\terminalserver.jl
.\pipe\vsc-jl-repl-e177509b-df29-4d52-9441-08472c4f840a
.\pipe\vsc-jl-repldbg-033bc92e-4781-4c7e-b229-9f8308e2fccb
.\pipe\vsc-jl-cr-d830131c-4d65-4ce2-b12f-a2372fab5eac USE_REVISE=true
USE_PLOTPANE=true USE_PROGRESS=true
ENABLE_SHELL_INTEGRATION=true
DEBUG_MODE=false
property of PowerShell terminal:
Command line: C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe
-noexit -command ‘try { . “c:\Users\xyz\AppData\Local\Programs\cursor\resources\app\out\vs\workbench\contrib\terminal\common\scripts\shellIntegration.ps1” } catch {}’
odow
October 6, 2025, 7:08am
10
It seems like there are a number of issues with CTRL+C in vs code: GitHub · Where software is built
If you want to intercept, I’d just use powershell.
VSCode in windows works fine, really. It is one of the greatest softwares in my mind.
(BTW, windows users probably use the powershell integrated in VSCode—it is powershell after all)
I find the API GRBterminate exceptionally useful, particularly working in conjunction with julia’s async programming.
e.g. terminate a vector of models and retrieve the available best solutions.
import JuMP, Gurobi, SparseArrays
const GRB_ENV = Gurobi.Env();
const Ns = 20:60;
const J = length(Ns);
const inn = Vector{JuMP.Model}(undef, J);
const TLIM = 4;
function opt_and_assert(j)
m = inn[j]
JuMP.optimize!(m)
t, p = JuMP.termination_status(m), JuMP.primal_status(m)
p == JuMP.FEASIBLE_POINT || error("primal: $p")
t in [JuMP.OPTIMAL, JuMP.INTERRUPTED] || error("termination: $t")
print("\r j = $j is done")
end
interrupt_opt(j) = Gurobi.GRBterminate(JuMP.backend(inn[j]))
function build_model(j, N)
Q = 1. * SparseArrays.dropzeros(
SparseArrays.spdiagm(
[i => rand(-9:9, N - i) for i in 0:div(N, 2)]...
)
)
m = inn[j] = JuMP.direct_model(Gurobi.Optimizer(GRB_ENV))
JuMP.set_silent(m)
JuMP.@variable(m, rand(-7:-3) <= y[1:N] <= rand(3:7));
JuMP.@variable(m, rand(-9:-5) <= x[1:N] <= rand(5:9));
JuMP.@objective(m, Min, (x'Q)y)
print("\r built j = $j, N = $N")
end;
# build_model
wvec = map((j, N) -> Threads.@spawn(build_model(j, N)), 1:J, Ns);
foreach(wait, wvec)
# optimize model within a TimeLimit
cvec = map(j -> Threads.@spawn(opt_and_assert(j)), 1:J);
sleep(TLIM)
# interrupt manually
foreach(interrupt_opt, 1:J)
# check the best available results
foreach(wait, cvec)
map(JuMP.termination_status, inn)
map(JuMP.objective_value, inn)
odow
October 20, 2025, 1:47am
13
Your code is not safe. It uses GRB_ENV across multiple threads.
A better way to write this code would be:
using JuMP
import Gurobi
import SparseArrays
function build_model(N, should_terminate)
Q = SparseArrays.spdiagm(
[i => rand(-9:9, N - i) for i in 0:div(N, 2)]...
)
SparseArrays.dropzeros!(Q)
model = direct_model(Gurobi.Optimizer())
set_silent(model)
@variable(model, rand(-7:-3) <= y[1:N] <= rand(3:7))
@variable(model, rand(-9:-5) <= x[1:N] <= rand(5:9))
@objective(model, Min, (x' * Q) * y)
function my_callback_function(cb_data, cb_where::Cint)
if should_terminate[]
Gurobi.GRBterminate(backend(model))
end
end
set_attribute(model, Gurobi.CallbackFunction(), my_callback_function)
optimize!(model)
return model
end
function main()
should_terminate = Threads.Atomic{Bool}(false)
tasks = map(N -> Threads.@spawn(build_model(N, should_terminate)), 20:60)
sleep(4.0)
should_terminate[] = true
models = fetch.(tasks)
@show termination_status.(models)
@show primal_status.(models)
return
end
but you’re spawning a lot of threads here.