diff --git a/Makefile.am b/Makefile.am index d3eeaddaf4..908c48b4ef 100644 --- a/Makefile.am +++ b/Makefile.am @@ -597,6 +597,7 @@ SCM_TESTS = \ tests/size.scm \ tests/status.scm \ tests/store-database.scm \ + tests/store-deadlock.scm \ tests/store-deduplication.scm \ tests/store-roots.scm \ tests/store.scm \ diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc index 43a8a37184..edd01bab34 100644 --- a/nix/libstore/build.cc +++ b/nix/libstore/build.cc @@ -1200,6 +1200,7 @@ void DerivationGoal::tryToBuild() if (buildMode != bmCheck && validPaths.size() == drv.outputs.size()) { debug(format("skipping build of derivation `%1%', someone beat us to it") % drvPath); outputLocks.setDeletion(true); + outputLocks.unlock(); done(BuildResult::AlreadyValid); return; } @@ -3070,6 +3071,7 @@ void SubstitutionGoal::tryToRun() if (!repair && worker.store.isValidPath(storePath)) { debug(format("store path `%1%' has become valid") % storePath); outputLock->setDeletion(true); + outputLock.reset(); amDone(ecSuccess); return; } diff --git a/tests/store-deadlock.scm b/tests/store-deadlock.scm new file mode 100644 index 0000000000..e960448133 --- /dev/null +++ b/tests/store-deadlock.scm @@ -0,0 +1,90 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2024 Reepca Russelstein +;;; Copyright © 2024 Ludovic Courtès +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (test-store-deadlock) + #:use-module (guix tests) + #:use-module (guix store) + #:use-module (guix derivations) + #:use-module (guix gexp) + #:use-module (guix monads) + #:use-module (gnu packages bootstrap) + #:use-module (ice-9 threads) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-64)) + +(define input-drvs + (map (lambda (n) + (computed-file (string-append "drv" (number->string n)) + #~(begin + #$(random-text) + (sleep 4) + (mkdir #$output)) + #:guile %bootstrap-guile)) + (iota 15))) + +(define top-drv + (computed-file "top-drv" + #~(begin + #$(random-text) + (sleep 3) + (pk 'deps: #$@input-drvs) + (mkdir #$output)) + #:guile %bootstrap-guile)) + +(%graft? #f) + + +(test-begin "store-deadlock") + +(test-equal "no deadlock" ;https://issues.guix.gnu.org/31785 + '(thread1 thread2 thread3) + + ;; This test checks for the absence of a deadlock when guix-daemon spawns + ;; several child processes (one for each client) that compete to build the + ;; same set of derivations. See . + (let* ((drvs (cons top-drv input-drvs)) + (builder (lambda (name lst) + (call-with-new-thread + (lambda () + (with-store store + (set-build-options store + #:verbosity 3 + #:max-build-jobs 1) + (run-with-store store + (mlet %store-monad ((lst (mapm %store-monad + lower-object lst))) + (mbegin %store-monad + (built-derivations lst) + (return name))))))))) + (thread1 (builder 'thread1 drvs)) + (thread2 (begin + (sleep 1) + (builder 'thread2 drvs))) + (thread3 (begin + (sleep 1) + (builder 'thread3 drvs)))) + + ;; This test should complete in less than a minute; if not, there's a + ;; deadlock. + (let ((deadline (+ (current-time) 120))) + (list (join-thread thread1 deadline 'timeout) + (join-thread thread2 deadline 'timeout) + (join-thread thread3 deadline 'timeout))))) + +(test-end)