From 4296c0b4a5cb9615b88b196ef7a4469a8a3f3697 Mon Sep 17 00:00:00 2001 From: Martin Noble Date: Tue, 21 Jul 2026 16:08:40 +0100 Subject: [PATCH] Use coot::get_max_number_of_threads() in calc_atom_map_edcalc calc_atom_map_edcalc() sized its thread count from std::thread::hardware_concurrency() directly, so set_max_number_of_threads() and COOT_N_THREADS had no effect on it. get_max_number_of_threads() still falls back to the system CPU count when nothing has been set, so callers that never set a limit are unaffected; callers that do set one are now honoured. On a workstation an over-large thread count is only a scheduling inefficiency. Under WebAssembly it is a correctness problem: pthreads are drawn from a fixed-size pool established at link time (-sPTHREAD_POOL_SIZE), and when it is exhausted emscripten's fallback needs the JS event loop to run before the thread can start -- which this function's spawn-then-join loop never allows. The result is a deadlock rather than a slowdown, and because the count tracked core count it was hardware-dependent. Measured in an embedded Moorhen session, reached via density_correlation_analysis -> map_to_model_correlation_per_residue -> calc_atom_map. The thread-count setting itself was working: instrumenting both accessors to print the value and the address of coot_n_threads showed set(3) and get -> 3 on the same object, while coot's own timing line in the same run reported "n_threads 8". After this change it reports "n_threads 3", peak concurrent pthreads over a full session drops from 13 to 8, and a build with PTHREAD_POOL_SIZE=8 goes from deadlocking on map load to loading cleanly. The same pattern appears in seven other places that are not part of the WebAssembly build and are left unchanged here: coot-utils/crowther.cc (three), ligand/molecular-replacement.cc (two), docking/semiflex-refine.cc and docking/rigid-body-dock.cc. --- coot-utils/edcalc.cc | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/coot-utils/edcalc.cc b/coot-utils/edcalc.cc index 7d263d3fbb..407abedbf2 100644 --- a/coot-utils/edcalc.cc +++ b/coot-utils/edcalc.cc @@ -36,6 +36,7 @@ #include #include +#include "utils/coot-utils.hh" #include "edcalc.hh" namespace coot { @@ -179,8 +180,21 @@ coot::calc_atom_map_edcalc(mmdb::Manager *mol, auto tp_accum_start = std::chrono::high_resolution_clock::now(); - // Threading setup - unsigned int n_threads = std::thread::hardware_concurrency(); + // Threading setup. + // + // Use coot's thread-count setting rather than std::thread::hardware_concurrency() + // directly, so that set_max_number_of_threads() (and COOT_N_THREADS) actually + // bound this function. get_max_number_of_threads() still falls back to the + // system CPU count when nothing has been set, so unconstrained callers behave + // as before. + // + // This matters where threads are a limited resource rather than merely a + // scheduling hint: under WebAssembly, pthreads are drawn from a fixed-size + // pool established at link time (-sPTHREAD_POOL_SIZE), and exhausting it + // deadlocks rather than degrades, because the on-demand fallback requires + // returning to the JS event loop -- which the spawn-then-join loop below + // never does. + unsigned int n_threads = coot::get_max_number_of_threads(); if (n_threads == 0) n_threads = 4; // For each of the 27 colours: all same-colour blocks are non-adjacent,