77#include < iostream>
88
99#include " catch.hpp"
10+ #include " interfaces/highs_c_api.h"
1011#include " matrix_multiplication.hpp"
1112#include " parallel/HighsParallel.h"
1213
@@ -305,4 +306,48 @@ TEST_CASE("CancelNestedTasks", "[parallel]") {
305306 }
306307
307308 HighsTaskExecutor::shutdown ();
309+ }
310+
311+ TEST_CASE (" ParallelCApi" , " [parallel]" ) {
312+ // This test exposes a failure in the C api when creating and destroying Highs
313+ // instances on separate threads.
314+ // One thread solves a mip, while the other loses some time before destroying
315+ // the instance. If this causes the scheduler to be terminated (i.e., if
316+ // Highs_destroy calls resetGlobalScheduler) while the first thread is still
317+ // using it, the first thread may crash, hang, or terminate with wrong
318+ // results.
319+
320+ highs::parallel::initialize_scheduler ();
321+
322+ std::string model = std::string (HIGHS_DIR ) + " /check/instances/egout.mps" ;
323+ const double expected_obj = 568.1007 ;
324+ double actual_obj;
325+ double total = 0 ;
326+
327+ highs::parallel::TaskGroup tg;
328+
329+ tg.spawn ([&]() {
330+ void * highs = Highs_create ();
331+ Highs_setBoolOptionValue (highs, " output_flag" , dev_run);
332+ Highs_setStringOptionValue (highs, " parallel" , " on" );
333+ Highs_readModel (highs, model.c_str ());
334+ Highs_run (highs);
335+ Highs_getDoubleInfoValue (highs, " objective_function_value" , &actual_obj);
336+ Highs_destroy (highs);
337+ });
338+
339+ tg.spawn ([&]() {
340+ void * highs = Highs_create ();
341+ for (int i = 0 ; i < 1e6 ; ++i) {
342+ total += (double )i * i * i;
343+ }
344+ Highs_destroy (highs);
345+ });
346+
347+ tg.taskWait ();
348+
349+ REQUIRE (total > 1e18 );
350+ REQUIRE (std::abs (actual_obj - expected_obj) / std::abs (expected_obj) < 1e-4 );
351+
352+ HighsTaskExecutor::shutdown (true );
308353}
0 commit comments