diff --git a/include/cse.h b/include/cse.h index 288783c31..3b545b4d7 100644 --- a/include/cse.h +++ b/include/cse.h @@ -272,7 +272,7 @@ cse_slave_index cse_execution_add_slave( int cse_execution_step(cse_execution* execution, size_t numSteps); /** - * Advances an execution to a specific point in time. + * Advances an execution to a specific point in time (blocking). * * \param [in] execution * The execution to be stepped. @@ -287,9 +287,10 @@ int cse_execution_simulate_until(cse_execution* execution, cse_time_point target /** - * Starts an execution. + * Starts an execution (non blocking). * - * The execution will run until `cse_execution_stop()` is called. + * The execution will run until `cse_execution_stop()` is called. The status of the + * simulation can be polled with `cse_execution_get_simulation_status()`. * * \param [in] execution * The execution to be started. @@ -300,6 +301,19 @@ int cse_execution_simulate_until(cse_execution* execution, cse_time_point target int cse_execution_start(cse_execution* execution); +/** + * Polls an execution for its simulation status. + * + * his method can be used to poll the status of the asynchronous simulation + * execution started by calling `cse_execution_start()`. Will return failure + * if an error occurred during the simulation execution. + * + * \returns + * 0 on success and -1 on error. + */ +int cse_execution_get_simulation_status(cse_execution* execution); + + /** * Stops an execution. * diff --git a/src/c/cse.cpp b/src/c/cse.cpp index 297a1186d..7c2c91696 100644 --- a/src/c/cse.cpp +++ b/src/c/cse.cpp @@ -62,14 +62,12 @@ cse_errc cpp_to_c_error_code(std::error_code ec) // These hold information about the last reported error. // They should only be set through `set_last_error()` and // `handle_current_exception()`. -static cse_errc g_lastErrorCode; -static std::string g_lastErrorMessage; -static std::mutex g_lock; +thread_local cse_errc g_lastErrorCode; +thread_local std::string g_lastErrorMessage; // Sets the last error code and message directly. void set_last_error(cse_errc ec, std::string message) { - std::lock_guard lock(g_lock); g_lastErrorCode = ec; g_lastErrorMessage = std::move(message); } @@ -135,13 +133,11 @@ void safe_strncpy(char* dest, const char* src, std::size_t count) cse_errc cse_last_error_code() { - std::lock_guard lock(g_lock); return g_lastErrorCode; } const char* cse_last_error_message() { - std::lock_guard lock(g_lock); return g_lastErrorMessage.c_str(); } @@ -151,6 +147,7 @@ struct cse_execution_s std::unique_ptr cpp_execution; std::thread t; boost::fibers::future simulate_result; + std::exception_ptr simulate_exception_ptr; std::atomic state; int error_code; }; @@ -500,13 +497,8 @@ int cse_execution_start(cse_execution* execution) auto task = boost::fibers::packaged_task([execution]() { auto future = execution->cpp_execution->simulate_until(std::nullopt); if (auto ep = future.get_exception_ptr()) { - try { - std::rethrow_exception(ep); - } catch (...) { - handle_current_exception(); - execution->state = CSE_EXECUTION_ERROR; - return false; - } + execution->simulate_exception_ptr = ep; + return false; } return future.get(); }); @@ -521,6 +513,26 @@ int cse_execution_start(cse_execution* execution) } } +int cse_execution_get_simulation_status(cse_execution* execution) +{ + if (execution->state != CSE_EXECUTION_RUNNING) { + return success; + } + const auto status = execution->simulate_result.wait_for(std::chrono::duration()); + if (boost::fibers::future_status::ready == status) { + if (execution->simulate_exception_ptr) { + try { + std::rethrow_exception(execution->simulate_exception_ptr); + } catch (...) { + handle_current_exception(); + execution->state = CSE_EXECUTION_ERROR; + return failure; + } + } + } + return success; +} + int cse_execution_stop(cse_execution* execution) { try { @@ -528,6 +540,9 @@ int cse_execution_stop(cse_execution* execution) if (execution->t.joinable()) { execution->simulate_result.get(); execution->t.join(); + if (execution->simulate_exception_ptr) { + std::rethrow_exception(execution->simulate_exception_ptr); + } } execution->state = CSE_EXECUTION_STOPPED; return success; diff --git a/test/c/simulation_error_handling_test.c b/test/c/simulation_error_handling_test.c index ecd630eeb..df76e2f27 100644 --- a/test/c/simulation_error_handling_test.c +++ b/test/c/simulation_error_handling_test.c @@ -67,6 +67,12 @@ int main() Sleep(100); + rc = cse_execution_get_simulation_status(execution); + if (rc < 0) { + fprintf(stderr, "Expected call to cse_execution_get_simulation_status() to return success."); + goto Lfailure; + } + cse_value_reference ref = 0; const bool val = true; // Produces a model error in the subsequent step @@ -76,6 +82,12 @@ int main() // Need to wait a bit due to stepping (and failure) happening in another thread. Sleep(400); + rc = cse_execution_get_simulation_status(execution); + if (rc >= 0) { + fprintf(stderr, "Expected call to cse_execution_get_simulation_status() to return failure."); + goto Lfailure; + } + cse_execution_status executionStatus; rc = cse_execution_get_status(execution, &executionStatus); if (rc < 0) { goto Lerror; } @@ -98,9 +110,16 @@ int main() } // What do we expect should happen if calling further methods? - // Sleep(100); - // rc = cse_execution_stop(execution); - // if (rc = 0) { goto Lfailure; } + Sleep(100); + rc = cse_execution_stop(execution); + if (rc >= 0) { goto Lfailure; } + + Sleep(100); + + rc = cse_execution_start(execution); + if (rc < 0) { goto Lerror; } + + Sleep(1000); goto Lcleanup;