libutil: thread-pool: ensure threads finished on error

This fixes segfaults with nix copy when there was an error processing
addMultipleToStore.

Running with ASAN/TSAN pointed at an use-after-free with threads from
the pool accessing the graph declared in processGraph after the function
was exiting and destructing the variables.

It turns out that if there is an error before pool.process() is called,
for example while we are still enqueueing tasks, then pool.process()
isn't called and threads are still left to run.

By creating the pool last we ensure that it is stopped first before
running other destructors even if an exception happens early.

fixes #618

Change-Id: I42a355f632aa0354df94c5d5d8cbe7ab5196c9a6
This commit is contained in:
Dominique Martinet
2025-01-12 04:14:28 +09:00
parent 3413ab5629
commit 4737d8b65e
2 changed files with 8 additions and 7 deletions
+2 -6
View File
@@ -306,9 +306,7 @@ void Store::addMultipleToStore(
act.progress(nrDone, pathsToCopy.size(), nrRunning, nrFailed);
};
ThreadPool pool{"addMultipleToStore pool"};
processGraph<StorePath>(pool,
processGraph<StorePath>("addMultipleToStore pool",
storePathsToAdd,
[&](const StorePath & path) {
@@ -1137,12 +1135,10 @@ std::map<StorePath, StorePath> copyPaths(
}
auto pathsMap = copyPaths(srcStore, dstStore, storePaths, repair, checkSigs, substitute);
ThreadPool pool{"copyPaths pool"};
try {
// Copy the realisation closure
processGraph<Realisation>(
pool, Realisation::closure(srcStore, toplevelRealisations),
"copyPaths pool", Realisation::closure(srcStore, toplevelRealisations),
[&](const Realisation & current) -> std::set<Realisation> {
std::set<Realisation> children;
for (const auto & [drvOutput, _] : current.dependentRealisations) {
+6 -1
View File
@@ -85,7 +85,7 @@ private:
*/
template<typename T>
void processGraph(
ThreadPool & pool,
const char *poolName,
const std::set<T> & nodes,
std::function<std::set<T>(const T &)> getEdges,
std::function<void(const T &)> processNode)
@@ -99,6 +99,11 @@ void processGraph(
std::function<void(const T &)> worker;
/* Create pool last to ensure threads are stopped before other destructors
* run */
ThreadPool pool{poolName};
worker = [&](const T & node) {
{