fix: scope per-build module-state reset to the config being built
All checks were successful
CI / build-test-release (pull_request) Successful in 6m41s
All checks were successful
CI / build-test-release (pull_request) Successful in 6m41s
Build() resets each Module/ModulePartition's per-build `compiled`/`checked` flags so a reused Configuration re-evaluates mtimes. That reset recursed into cfg.dependencies — but dependency Configurations are shared across the build DAG and each is compiled concurrently by its own Build() call. A parent/sibling's recursive reset could therefore clear a shared dependency's module `compiled` atomic *after* that dependency's module-compile thread had set it true and exited, but before an intra-config waiter (its impl, or a dependent partition) ran compiled.wait(false). The waiter then blocked forever on a flag nothing would re-signal: the build froze mid-compile, idle, with no compiler process alive — exactly the hang in issue #16. Reset only the current configuration's own modules. Every config in the tree already gets its own Build() call (the per-PcmDir builder registered in depResults), which resets its own state at the top of that call, sequenced before its compile threads spawn. Cross-config module state is consulted only via PCM file mtimes and the depResults futures, never via these flags, so the narrower reset is correct and removes the data race entirely. Adds ConcurrentDependencyReset: builds a static-lib dependency fully, then builds a consumer that depends on it while the dependency is already cached in depResults (so it is never rebuilt), and asserts the consumer build leaves the dependency's module `compiled` flag intact. Fails deterministically on the old recursive reset; passes with the fix. Resolves #16 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0dd1738e33
commit
e76f92ae0a
6 changed files with 167 additions and 19 deletions
|
|
@ -215,25 +215,30 @@ void Configuration::GetInterfacesAndImplementations(std::span<fs::path> interfac
|
|||
BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, std::shared_future<BuildResult>>& depResults, std::mutex& depMutex) {
|
||||
// Reset per-build cached state on every Module/ModulePartition so that
|
||||
// successive Build() calls on the same Configuration re-evaluate mtimes
|
||||
// (incremental-rebuild test scenarios). Walks cfg.dependencies recursively
|
||||
// with a seen-set so diamond deps don't loop.
|
||||
{
|
||||
std::unordered_set<Configuration*> resetSeen;
|
||||
std::function<void(Configuration*)> reset = [&](Configuration* c) {
|
||||
if (!resetSeen.insert(c).second) return;
|
||||
for (auto& iface : c->interfaces) {
|
||||
iface->checked = false;
|
||||
iface->compiled.store(false);
|
||||
for (auto& part : iface->partitions) {
|
||||
part->checked = false;
|
||||
part->compiled.store(false);
|
||||
}
|
||||
}
|
||||
for (Configuration* dep : c->dependencies) {
|
||||
reset(dep);
|
||||
}
|
||||
};
|
||||
reset(&config);
|
||||
// (incremental-rebuild test scenarios).
|
||||
//
|
||||
// Reset ONLY this configuration's own modules — never recurse into
|
||||
// dependencies. Configuration objects are shared across the dependency DAG
|
||||
// (diamond deps point at the same Configuration*), and every config in the
|
||||
// tree gets its own Build() call: the per-PcmDir builder registered in
|
||||
// depResults resets its own state here, at the top of its own Build(),
|
||||
// sequenced before that config's compile threads spawn. Recursing into
|
||||
// dependencies from here would re-clear a shared dependency's `compiled`
|
||||
// atomic from a parent/sibling thread *while that dependency's own Build()
|
||||
// is concurrently compiling it* — after its module-compile thread set the
|
||||
// flag true and exited but before its impl/partition waiter ran
|
||||
// compiled.wait(false). The waiter would then block forever on a flag
|
||||
// nothing re-signals: the build freezes mid-compile, idle, with no compiler
|
||||
// process alive (issue #16). Cross-config module state is consulted only via
|
||||
// PCM file mtimes and the depResults futures, never via these flags, so the
|
||||
// narrower reset is correct.
|
||||
for (auto& iface : config.interfaces) {
|
||||
iface->checked = false;
|
||||
iface->compiled.store(false);
|
||||
for (auto& part : iface->partitions) {
|
||||
part->checked = false;
|
||||
part->compiled.store(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-detect the WASI sysroot before any compile step runs so BuildStdPcm
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue