use_dll: ensure proper cleanup of thread workers threads in case of early exit (e.g. upon failure)

master
Sébastien Villemot 2022-10-17 13:57:34 +02:00
parent 781e10c24b
commit 9f96db89ba
No known key found for this signature in database
GPG Key ID: 2CECE9350ECEBE4A
3 changed files with 46 additions and 56 deletions

View File

@ -535,12 +535,11 @@ main(int argc, char **argv)
nointeractive, config_file, check_model_changes, minimal_workspace, compute_xrefs, nointeractive, config_file, check_model_changes, minimal_workspace, compute_xrefs,
mexext, matlabroot, dynareroot, onlymodel, gui, notime); mexext, matlabroot, dynareroot, onlymodel, gui, notime);
/* Ensures that the preprocessor final message is printed after the end of /* Ensures that workers are not destroyed before they finish compiling.
compilation (and is not printed in case of compilation failure); also Also ensures that the preprocessor final message is printed after the end of
avoids potential issues with destroying the thread synchronization compilation (and is not printed in case of compilation failure). */
mechanism too soon. */
if (mod_file->use_dll) if (mod_file->use_dll)
ModelTree::terminateMEXCompilationWorkers(); ModelTree::waitForMEXCompilationWorkers();
cout << "Preprocessing completed." << endl; cout << "Preprocessing completed." << endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;

View File

@ -37,11 +37,15 @@
#include <utility> #include <utility>
#include <algorithm> #include <algorithm>
vector<jthread> ModelTree::mex_compilation_workers {}; /* NB: The workers must be listed *after* all the other static variables
condition_variable ModelTree::mex_compilation_cv; 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; mutex ModelTree::mex_compilation_mut;
vector<tuple<filesystem::path, set<filesystem::path>, string>> ModelTree::mex_compilation_queue; vector<tuple<filesystem::path, set<filesystem::path>, string>> ModelTree::mex_compilation_queue;
set<filesystem::path> ModelTree::mex_compilation_done; set<filesystem::path> ModelTree::mex_compilation_done;
vector<jthread> ModelTree::mex_compilation_workers;
void void
ModelTree::copyHelper(const ModelTree &m) ModelTree::copyHelper(const ModelTree &m)
@ -1892,64 +1896,52 @@ ModelTree::initializeMEXCompilationWorkers(int numworkers)
mex_compilation_workers.emplace_back([](stop_token stoken) mex_compilation_workers.emplace_back([](stop_token stoken)
{ {
unique_lock<mutex> lk {mex_compilation_mut}; unique_lock<mutex> lk {mex_compilation_mut};
filesystem::path output;
string cmd;
look_for_job: /* Look for an object to compile, whose prerequisites are already
for (auto it {mex_compilation_queue.begin()}; it != mex_compilation_queue.end(); ++it) 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. */
/* The following is a copy and not a reference, because we need it auto pick_job = [&cmd, &output]
after erasing it, and also after releasing the lock (at which {
point the mex_compilation_queue may be modified by others). */ for (auto it {mex_compilation_queue.begin()}; it != mex_compilation_queue.end(); ++it)
const auto [output, prerequisites, cmd] {*it}; if (const auto &prerequisites {get<1>(*it)}; // Will become dangling after erase
if (includes(mex_compilation_done.begin(), mex_compilation_done.end(), includes(mex_compilation_done.begin(), mex_compilation_done.end(),
prerequisites.begin(), prerequisites.end())) prerequisites.begin(), prerequisites.end()))
{ {
output = get<0>(*it);
cmd = get<2>(*it);
mex_compilation_queue.erase(it); mex_compilation_queue.erase(it);
lk.unlock(); // After that point, the iterator may become invalid return true;
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 false;
};
if (stoken.stop_requested()) while (!stoken.stop_requested())
return; if (mex_compilation_cv.wait(lk, stoken, pick_job))
{
mex_compilation_cv.wait(lk); lk.unlock();
if (system(cmd.c_str()))
goto look_for_job; {
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 void
ModelTree::terminateMEXCompilationWorkers() ModelTree::waitForMEXCompilationWorkers()
{ {
// Wait until the queue is empty
unique_lock<mutex> lk {mex_compilation_mut}; unique_lock<mutex> lk {mex_compilation_mut};
mex_compilation_cv.wait(lk, [] { return mex_compilation_queue.empty(); }); 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 void

View File

@ -345,7 +345,7 @@ private:
/* The following variables implement the thread synchronization mechanism for /* The following variables implement the thread synchronization mechanism for
limiting the number of concurrent GCC processes and tracking dependencies limiting the number of concurrent GCC processes and tracking dependencies
between object files. */ between object files. */
static condition_variable mex_compilation_cv; static condition_variable_any mex_compilation_cv;
static mutex mex_compilation_mut; static mutex mex_compilation_mut;
/* Object/MEX files waiting to be compiled (with their prerequisites as 2nd /* Object/MEX files waiting to be compiled (with their prerequisites as 2nd
element and compilation command as the 3rd element) */ element and compilation command as the 3rd element) */
@ -557,9 +557,8 @@ public:
// Initialize the MEX compilation workers // Initialize the MEX compilation workers
static void initializeMEXCompilationWorkers(int numworkers); static void initializeMEXCompilationWorkers(int numworkers);
/* Terminates all MEX compilation workers (after they have emptied the // Waits until the MEX compilation queue is empty
waiting queue) */ static void waitForMEXCompilationWorkers();
static void terminateMEXCompilationWorkers();
//! Returns all the equation tags associated to an equation //! Returns all the equation tags associated to an equation
map<string, string> map<string, string>