diff --git a/src/DynareMain.cc b/src/DynareMain.cc index c1cf3eae..4acd1bd2 100644 --- a/src/DynareMain.cc +++ b/src/DynareMain.cc @@ -535,12 +535,11 @@ main(int argc, char **argv) nointeractive, config_file, check_model_changes, minimal_workspace, compute_xrefs, mexext, matlabroot, dynareroot, onlymodel, gui, notime); - /* Ensures that the preprocessor final message is printed after the end of - compilation (and is not printed in case of compilation failure); also - avoids potential issues with destroying the thread synchronization - mechanism too soon. */ + /* Ensures that workers are not destroyed before they finish compiling. + Also ensures that the preprocessor final message is printed after the end of + compilation (and is not printed in case of compilation failure). */ if (mod_file->use_dll) - ModelTree::terminateMEXCompilationWorkers(); + ModelTree::waitForMEXCompilationWorkers(); cout << "Preprocessing completed." << endl; return EXIT_SUCCESS; diff --git a/src/ModelTree.cc b/src/ModelTree.cc index e9cd191f..acef5a5a 100644 --- a/src/ModelTree.cc +++ b/src/ModelTree.cc @@ -37,11 +37,15 @@ #include #include -vector ModelTree::mex_compilation_workers {}; -condition_variable ModelTree::mex_compilation_cv; +/* NB: The workers must be listed *after* all the other static variables + related to MEX compilation, so that when the preprocessor exits, the workers + are destroyed *before* those variables (since the former rely on the latter + for their functioning). */ +condition_variable_any ModelTree::mex_compilation_cv; mutex ModelTree::mex_compilation_mut; vector, string>> ModelTree::mex_compilation_queue; set ModelTree::mex_compilation_done; +vector ModelTree::mex_compilation_workers; void ModelTree::copyHelper(const ModelTree &m) @@ -1892,64 +1896,52 @@ ModelTree::initializeMEXCompilationWorkers(int numworkers) mex_compilation_workers.emplace_back([](stop_token stoken) { unique_lock lk {mex_compilation_mut}; + filesystem::path output; + string cmd; - look_for_job: - for (auto it {mex_compilation_queue.begin()}; it != mex_compilation_queue.end(); ++it) - { - /* The following is a copy and not a reference, because we need it - after erasing it, and also after releasing the lock (at which - point the mex_compilation_queue may be modified by others). */ - const auto [output, prerequisites, cmd] {*it}; - if (includes(mex_compilation_done.begin(), mex_compilation_done.end(), + /* Look for an object to compile, whose prerequisites are already + compiled. If found, remove it from the queue, save the output path and + the compilation command, and return true. Must be run under the lock. */ + auto pick_job = [&cmd, &output] + { + for (auto it {mex_compilation_queue.begin()}; it != mex_compilation_queue.end(); ++it) + if (const auto &prerequisites {get<1>(*it)}; // Will become dangling after erase + includes(mex_compilation_done.begin(), mex_compilation_done.end(), prerequisites.begin(), prerequisites.end())) { + output = get<0>(*it); + cmd = get<2>(*it); mex_compilation_queue.erase(it); - lk.unlock(); // After that point, the iterator may become invalid - if (system(cmd.c_str())) - { - cerr << "Compilation failed" << endl; - exit(EXIT_FAILURE); - } - lk.lock(); - mex_compilation_done.insert(output); - /* The object just compiled may be a prerequisite for several - other objects, so notify all waiting workers. Also needed to - notify the main thread when in - ModelTree::terminateMEXCompilationWorkers(). */ - mex_compilation_cv.notify_all(); - goto look_for_job; + return true; } - } + return false; + }; - if (stoken.stop_requested()) - return; - - mex_compilation_cv.wait(lk); - - goto look_for_job; + while (!stoken.stop_requested()) + if (mex_compilation_cv.wait(lk, stoken, pick_job)) + { + lk.unlock(); + if (system(cmd.c_str())) + { + cerr << "Compilation failed" << endl; + exit(EXIT_FAILURE); + } + lk.lock(); + mex_compilation_done.insert(output); + /* The object just compiled may be a prerequisite for several + other objects, so notify all waiting workers. Also needed to + notify the main thread when in + ModelTree::waitForMEXCompilationWorkers().*/ + mex_compilation_cv.notify_all(); + } }); } void -ModelTree::terminateMEXCompilationWorkers() +ModelTree::waitForMEXCompilationWorkers() { - // Wait until the queue is empty unique_lock lk {mex_compilation_mut}; mex_compilation_cv.wait(lk, [] { return mex_compilation_queue.empty(); }); - - /* Request stop while still holding the lock, so we are sure that workers are - either compiling or waiting right now. Otherwise there could theoretically - be a race condition where the condition variable is notified just after - the thread has checked for its stoken, and just before it begins waiting; - this would be deadlock. */ - for (auto &it : mex_compilation_workers) - it.request_stop(); - - lk.unlock(); - - mex_compilation_cv.notify_all(); - for (auto &it : mex_compilation_workers) - it.join(); } void diff --git a/src/ModelTree.hh b/src/ModelTree.hh index 1f3bfe73..fcc59547 100644 --- a/src/ModelTree.hh +++ b/src/ModelTree.hh @@ -345,7 +345,7 @@ private: /* The following variables implement the thread synchronization mechanism for limiting the number of concurrent GCC processes and tracking dependencies between object files. */ - static condition_variable mex_compilation_cv; + static condition_variable_any mex_compilation_cv; static mutex mex_compilation_mut; /* Object/MEX files waiting to be compiled (with their prerequisites as 2nd element and compilation command as the 3rd element) */ @@ -557,9 +557,8 @@ public: // Initialize the MEX compilation workers static void initializeMEXCompilationWorkers(int numworkers); - /* Terminates all MEX compilation workers (after they have emptied the - waiting queue) */ - static void terminateMEXCompilationWorkers(); + // Waits until the MEX compilation queue is empty + static void waitForMEXCompilationWorkers(); //! Returns all the equation tags associated to an equation map