From 23299f19ebca57220af5946764d6aef9bd345cc9 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 16 May 2024 10:37:32 +1200 Subject: [PATCH 1/2] Improve stacktrace of errors thrown from within a callback --- src/MOI_wrapper/MOI_wrapper.jl | 2 +- test/MOI_callbacks.jl | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index d43e7b7..6cdd85a 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -232,7 +232,7 @@ function _internal_callback(tree::Ptr{Cvoid}, info::Ptr{Cvoid}) cb_data.callback_function(cb_data) catch ex glp_ios_terminate(tree) - cb_data.exception = ex + cb_data.exception = CapturedException(ex, catch_backtrace()) end return Cint(0) end diff --git a/test/MOI_callbacks.jl b/test/MOI_callbacks.jl index 2e5cc13..039331e 100644 --- a/test/MOI_callbacks.jl +++ b/test/MOI_callbacks.jl @@ -577,6 +577,25 @@ function test_no_cache_InterruptException() return end +function test_issue_232() + model = GLPK.Optimizer() + x = MOI.add_variable(model) + MOI.add_constraint(model, x, MOI.ZeroOne()) + my_callback(args...) = error("FooBar") + MOI.set(model, GLPK.CallbackFunction(), my_callback) + try + MOI.optimize!(model) + # To ensure that if an error is not thrown from the callback we still + # hit the catch block + @assert false + catch ex + @test ex isa CapturedException + @test occursin("my_callback", "$ex") + @test occursin("FooBar", "$ex") + end + return +end + end TestCallbacks.runtests() From 5911b028fe85fffa90baead26fdac44df627bb02 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 16 May 2024 11:01:51 +1200 Subject: [PATCH 2/2] Update --- src/MOI_wrapper/MOI_wrapper.jl | 17 +++++++++++++---- test/MOI_callbacks.jl | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 6cdd85a..13b8a20 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -1402,10 +1402,19 @@ function _solve_mip_problem(model::Optimizer) # # This next bit is _very_ important! See the note associated with # set_callback. - if model.callback_data.exception isa InterruptException - model.solver_status = GLP_ESTOP - elseif model.callback_data.exception !== nothing - throw(model.callback_data.exception) + if model.callback_data.exception !== nothing + model.callback_data.exception::CapturedException + inner = model.callback_data.exception.ex + if inner isa InterruptException + model.solver_status = GLP_ESTOP + elseif inner isa MOI.InvalidCallbackUsage + # Special-case throwing InvalidCallbackUsage to preserve + # backwards compatibility. But it does mean that they don't have + # good backtraces... + throw(inner) + else + throw(model.callback_data.exception) + end end finally for (column, lower, upper) in bounds_to_reset diff --git a/test/MOI_callbacks.jl b/test/MOI_callbacks.jl index 039331e..01bee3e 100644 --- a/test/MOI_callbacks.jl +++ b/test/MOI_callbacks.jl @@ -577,7 +577,7 @@ function test_no_cache_InterruptException() return end -function test_issue_232() +function test_no_cache_issue_232() model = GLPK.Optimizer() x = MOI.add_variable(model) MOI.add_constraint(model, x, MOI.ZeroOne())