* 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.
This commit is contained in:
Eelco Dolstra 2005-01-31 22:01:55 +00:00
parent 207bdcbe86
commit 89c9bc11ab
5 changed files with 85 additions and 20 deletions

View file

@ -12,10 +12,24 @@
#include <unistd.h> #include <unistd.h>
/* 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"; static string tempRootsDir = "temproots";
/* The file to which we write our temporary roots. */ /* The file to which we write our temporary roots. */
Path fnTempRoots; static Path fnTempRoots;
static AutoCloseFD fdTempRoots; static AutoCloseFD fdTempRoots;
@ -213,6 +227,10 @@ void collectGarbage(const PathSet & roots, GCAction action,
else else
tempRootsClosed.insert(*i); 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 /* After this point the set of roots or temporary roots cannot
increase, since we hold locks on everything. So everything increase, since we hold locks on everything. So everything
that is not currently in in `livePaths' or `tempRootsClosed' 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 /* !!! when we have multiple output paths per derivation, this
will not work anymore because we get cycles. */ will not work anymore because we get cycles. */
storePaths = topoSort(storePaths2); storePaths = topoSort(storePaths2);
for (Paths::iterator i = storePaths.begin(); i != storePaths.end(); ++i) { for (Paths::iterator i = storePaths.begin(); i != storePaths.end(); ++i) {
debug(format("considering deletion of `%1%'") % *i); debug(format("considering deletion of `%1%'") % *i);

View file

@ -34,12 +34,12 @@ fallback.sh: fallback.nix
nix-push.sh: dependencies.nix nix-push.sh: dependencies.nix
nix-pull.sh: dependencies.nix nix-pull.sh: dependencies.nix
gc.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 \ #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 \ # build-hook.sh substitutes.sh substitutes2.sh fallback.sh nix-push.sh gc.sh \
gc-concurrent.sh verify.sh nix-pull.sh # gc-concurrent.sh verify.sh nix-pull.sh
#TESTS = init.sh gc-concurrent.sh TESTS = init.sh gc-concurrent.sh
XFAIL_TESTS = XFAIL_TESTS =

View file

@ -1,21 +1,34 @@
storeExpr=$($TOP/src/nix-instantiate/nix-instantiate gc-concurrent.nix) storeExpr1=$($TOP/src/nix-instantiate/nix-instantiate gc-concurrent.nix)
outPath=$($TOP/src/nix-store/nix-store -q $storeExpr) 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 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. # Start build #2 in the background after 3 seconds.
$TOP/src/nix-store/nix-store -rvv "$storeExpr" & (sleep 3 && $TOP/src/nix-store/nix-store -rvv "$storeExpr2") &
pid=$! 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 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. # Wait for build #1/#2 to finish.
echo waiting for pid $pid to finish... echo waiting for pid $pid1 to finish...
wait $pid wait $pid1
echo waiting for pid $pid2 to finish...
wait $pid2
# Check that the root and its dependencies haven't been deleted. # Check that the root of build #1 and its dependencies haven't been
cat $outPath/foobar # deleted.
cat $outPath/input-2/bar cat $outPath1/foobar
cat $outPath1/input-2/bar
cat $outPath2/foobar

View file

@ -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

View file

@ -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;
};
}