@@ -195,6 +195,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
195195 termination_status:: MOI.TerminationStatusCode
196196 silent:: Bool
197197 time_limit_sec:: Union{Nothing,Float64}
198+ allow_inner_interrupt:: Bool
198199 start_time:: Float64
199200 solve_time:: Float64
200201 ideal_point:: Vector{Float64}
@@ -217,6 +218,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
217218 MOI. OPTIMIZE_NOT_CALLED,
218219 false ,
219220 nothing ,
221+ false ,
220222 NaN ,
221223 NaN ,
222224 Float64[],
@@ -311,6 +313,34 @@ function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, ::Nothing)
311313 return
312314end
313315
316+ # ## AllowInnerInterrupt
317+
318+ """
319+ AllowInnerInterrupt() <: MOI.AbstractOptimizerAttribute
320+
321+ An optimizer attribute that controls whether the SIGINT (`[CTRL]+[C]`) are
322+ enabled during the solve of the inner optimization problems.
323+
324+ By default, this attribute is set to `false`.
325+
326+ !!! warning
327+ This attribute should be used with caution. It is safe to set to `true` only
328+ if the inner optimization has native support for safely interrupting a call
329+ to `optimize!`. It is undefined behavior if you set this attribute to `true`
330+ and you interrupt a solver that does not have support for safely
331+ interrupting a solve.
332+ """
333+ struct AllowInnerInterrupt <: MOI.AbstractOptimizerAttribute end
334+
335+ MOI. supports (:: Optimizer , :: AllowInnerInterrupt ) = true
336+
337+ MOI. get (model:: Optimizer , :: AllowInnerInterrupt ) = model. allow_inner_interrupt
338+
339+ function MOI. set (model:: Optimizer , :: AllowInnerInterrupt , value:: Bool )
340+ model. allow_inner_interrupt = value
341+ return
342+ end
343+
314344# ## SolveTimeSec
315345
316346function MOI. get (model:: Optimizer , :: MOI.SolveTimeSec )
@@ -701,6 +731,13 @@ function MOI.delete(model::Optimizer, ci::MOI.ConstraintIndex)
701731 return
702732end
703733
734+ function _call_with_sigint_if (f:: Function , enable_sigint:: Bool )
735+ if enable_sigint
736+ return reenable_sigint (f)
737+ end
738+ return f ()
739+ end
740+
704741"""
705742 optimize_inner!(model::Optimizer)
706743
@@ -715,7 +752,18 @@ packages.
715752"""
716753function optimize_inner! (model:: Optimizer )
717754 start_time = time ()
718- MOI. optimize! (model. inner)
755+ # If AllowInnerInterrupt=true, there are two cases that can happen:
756+ #
757+ # 1. the interrupt is gracefully caught by the inner optimizer, in which
758+ # case no error is thrown. The interrupt is handled by the regular
759+ # termination status check, just as it would for any other problematic
760+ # solve.
761+ #
762+ # 2. the interrupt is not gracefully caught, and an InterruptException is
763+ # thrown. Bad user. They shouldn't have set AllowInnerInterrupt.
764+ _call_with_sigint_if (MOI. get (model, AllowInnerInterrupt ())) do
765+ return MOI. optimize! (model. inner)
766+ end
719767 model. solve_time_inner += time () - start_time
720768 model. subproblem_count += 1
721769 return
0 commit comments