From 89c9bc11abc02cc746838bfef101e5fecc59a6c5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 31 Jan 2005 22:01:55 +0000 Subject: [PATCH] * Add a test for a more subtle race: a process starting after the temporary root files have been read but creating outputs before the store directory has been read. --- src/libstore/gc.cc | 22 +++++++++++++++++-- tests/Makefile.am | 10 ++++----- tests/gc-concurrent.sh | 39 ++++++++++++++++++++++----------- tests/gc-concurrent2.builder.sh | 9 ++++++++ tests/gc-concurrent2.nix.in | 25 +++++++++++++++++++++ 5 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 tests/gc-concurrent2.builder.sh create mode 100644 tests/gc-concurrent2.nix.in diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index af40e0b92..c89f7a8a1 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -12,10 +12,24 @@ #include +/* Acquire the global GC lock. */ +static AutoCloseFD openGCLock(LockType lockType) +{ +#if 0 + Path fnGCLock = (format("%1%/%2%/%3%") + % nixStateDir % tempRootsDir % getpid()).str(); + + fdTempRoots = open(fnTempRoots.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0600); + if (fdTempRoots == -1) + throw SysError(format("opening temporary roots file `%1%'") % fnTempRoots); +#endif +} + + static string tempRootsDir = "temproots"; /* The file to which we write our temporary roots. */ -Path fnTempRoots; +static Path fnTempRoots; static AutoCloseFD fdTempRoots; @@ -213,6 +227,10 @@ void collectGarbage(const PathSet & roots, GCAction action, else tempRootsClosed.insert(*i); + /* For testing - see tests/gc-concurrent.sh. */ + if (getenv("NIX_DEBUG_GC_WAIT")) + sleep(2); + /* After this point the set of roots or temporary roots cannot increase, since we hold locks on everything. So everything that is not currently in in `livePaths' or `tempRootsClosed' @@ -231,7 +249,7 @@ void collectGarbage(const PathSet & roots, GCAction action, /* !!! when we have multiple output paths per derivation, this will not work anymore because we get cycles. */ storePaths = topoSort(storePaths2); - + for (Paths::iterator i = storePaths.begin(); i != storePaths.end(); ++i) { debug(format("considering deletion of `%1%'") % *i); diff --git a/tests/Makefile.am b/tests/Makefile.am index c491aa64d..e1c158500 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -34,12 +34,12 @@ fallback.sh: fallback.nix nix-push.sh: dependencies.nix nix-pull.sh: dependencies.nix gc.sh: dependencies.nix -gc-concurrent.sh: gc-concurrent.nix +gc-concurrent.sh: gc-concurrent.nix gc-concurrent2.nix -TESTS = init.sh hash.sh lang.sh simple.sh dependencies.sh locking.sh parallel.sh \ - build-hook.sh substitutes.sh substitutes2.sh fallback.sh nix-push.sh gc.sh \ - gc-concurrent.sh verify.sh nix-pull.sh -#TESTS = init.sh gc-concurrent.sh +#TESTS = init.sh hash.sh lang.sh simple.sh dependencies.sh locking.sh parallel.sh \ +# build-hook.sh substitutes.sh substitutes2.sh fallback.sh nix-push.sh gc.sh \ +# gc-concurrent.sh verify.sh nix-pull.sh +TESTS = init.sh gc-concurrent.sh XFAIL_TESTS = diff --git a/tests/gc-concurrent.sh b/tests/gc-concurrent.sh index c41475fb6..fd329b457 100644 --- a/tests/gc-concurrent.sh +++ b/tests/gc-concurrent.sh @@ -1,21 +1,34 @@ -storeExpr=$($TOP/src/nix-instantiate/nix-instantiate gc-concurrent.nix) -outPath=$($TOP/src/nix-store/nix-store -q $storeExpr) +storeExpr1=$($TOP/src/nix-instantiate/nix-instantiate gc-concurrent.nix) +outPath1=$($TOP/src/nix-store/nix-store -q $storeExpr1) + +storeExpr2=$($TOP/src/nix-instantiate/nix-instantiate gc-concurrent2.nix) +outPath2=$($TOP/src/nix-store/nix-store -q $storeExpr2) ls -l test-tmp/state/temproots +# Start build #1 in the background. It starts immediately. +$TOP/src/nix-store/nix-store -rvv "$storeExpr1" & +pid1=$! -# Start a build in the background. -$TOP/src/nix-store/nix-store -rvv "$storeExpr" & -pid=$! +# Start build #2 in the background after 3 seconds. +(sleep 3 && $TOP/src/nix-store/nix-store -rvv "$storeExpr2") & +pid2=$! -# Run the garbage collector while the build is running. +# Run the garbage collector while the build is running. Note: the GC +# sleeps for *another* 2 seconds after acquiring the GC lock. This +# checks whether build #1 sleep 2 -$NIX_BIN_DIR/nix-collect-garbage -vvvvv +NIX_DEBUG_GC_WAIT=1 $NIX_BIN_DIR/nix-collect-garbage -vvvvv -# Wait for the build to finish. -echo waiting for pid $pid to finish... -wait $pid +# Wait for build #1/#2 to finish. +echo waiting for pid $pid1 to finish... +wait $pid1 +echo waiting for pid $pid2 to finish... +wait $pid2 -# Check that the root and its dependencies haven't been deleted. -cat $outPath/foobar -cat $outPath/input-2/bar +# Check that the root of build #1 and its dependencies haven't been +# deleted. +cat $outPath1/foobar +cat $outPath1/input-2/bar + +cat $outPath2/foobar diff --git a/tests/gc-concurrent2.builder.sh b/tests/gc-concurrent2.builder.sh new file mode 100644 index 000000000..d94e46971 --- /dev/null +++ b/tests/gc-concurrent2.builder.sh @@ -0,0 +1,9 @@ +export PATH=/bin:/usr/bin:$PATH + +mkdir $out +echo $(cat $input1/foo)$(cat $input2/bar)xyzzy > $out/foobar + +# Check that the GC hasn't deleted the lock on our output. +test -e "$out.lock" + +sleep 3 \ No newline at end of file diff --git a/tests/gc-concurrent2.nix.in b/tests/gc-concurrent2.nix.in new file mode 100644 index 000000000..e6050f1bb --- /dev/null +++ b/tests/gc-concurrent2.nix.in @@ -0,0 +1,25 @@ +let { + + input1 = derivation { + name = "dependencies-input-1"; + system = "@system@"; + builder = "@shell@"; + args = ["-e" "-x" ./dependencies.builder1.sh]; + }; + + input2 = derivation { + name = "dependencies-input-2"; + system = "@system@"; + builder = "@shell@"; + args = ["-e" "-x" ./dependencies.builder2.sh]; + }; + + body = derivation { + name = "gc-concurrent2"; + system = "@system@"; + builder = "@shell@"; + args = ["-e" "-x" ./gc-concurrent2.builder.sh]; + inherit input1 input2; + }; + +} \ No newline at end of file