From 54951ffa2dced6dabe1ab3863351f88bdb086849 Mon Sep 17 00:00:00 2001 From: Alexandre Duret-Lutz Date: Tue, 29 Mar 2005 18:46:55 +0000 Subject: [PATCH] * lib/am/lisp.am ($(am__ELCFILES)): Prevent races if the recover rule is run with `make -j'. * doc/automake.texi (Multiple Outputs): Adjust. * tests/lisp6.test: Augment it. * tests/lisp8.test: New file. * tests/Makefile.am (TESTS): Add lisp8.test. Suggested by Bruno Haible. --- ChangeLog | 10 +++++ doc/automake.texi | 108 +++++++++++++++++++++++++++++++++++++++++------------- doc/stamp-vti | 2 +- doc/version.texi | 2 +- lib/am/lisp.am | 28 +++++++++++--- tests/Makefile.am | 1 + tests/Makefile.in | 1 + tests/lisp6.test | 10 ++++- tests/lisp8.test | 65 ++++++++++++++++++++++++++++++++ 9 files changed, 193 insertions(+), 34 deletions(-) create mode 100755 tests/lisp8.test diff --git a/ChangeLog b/ChangeLog index 2031103..f98a5aa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2005-03-29 Alexandre Duret-Lutz + + * lib/am/lisp.am ($(am__ELCFILES)): Prevent races if the recover + rule is run with `make -j'. + * doc/automake.texi (Multiple Outputs): Adjust. + * tests/lisp6.test: Augment it. + * tests/lisp8.test: New file. + * tests/Makefile.am (TESTS): Add lisp8.test. + Suggested by Bruno Haible. + 2005-03-27 Alexandre Duret-Lutz * doc/automake.texi: Use @:, @., @!, and @tie{} where appropriate. diff --git a/doc/automake.texi b/doc/automake.texi index b9efd13..b9b8dae 100644 --- a/doc/automake.texi +++ b/doc/automake.texi @@ -8895,38 +8895,47 @@ missing. Here it is: data.c: data.foo foo data.foo data.h: data.c +## Recover from the removal of $@@ @@if test -f $@@; then :; else \ rm -f data.c; \ $(MAKE) $(AM_MAKEFLAGS) data.c; \ fi @end example -The above scales easily to more outputs and more inputs. One of the -output is picked up to serve as a witness of the run of the command, -it depends upon all inputs, and all other outputs depend upon it. For -instance if @command{foo} should additionally read @file{data.bar} and -also produce @file{data.w} and @file{data.x}, we would write: +The above scheme can be extended to handle more outputs and more +inputs. One of the output is picked up to serve as a witness of the +run of the command, it depends upon all inputs, and all other outputs +depend upon it. For instance if @command{foo} should additionally +read @file{data.bar} and also produce @file{data.w} and @file{data.x}, +we would write: @example data.c: data.foo data.bar foo data.foo data.bar data.h data.w data.x: data.c +## Recover from the removal of $@@ @@if test -f $@@; then :; else \ rm -f data.c; \ $(MAKE) $(AM_MAKEFLAGS) data.c; \ fi @end example -There is still a minor problem with this setup. @command{foo} outputs -four files, but we do not know in which order these files are created. -Suppose that @file{data.h} is created before @file{data.c}. Then we -have a weird situation. The next time @command{make} is run, -@file{data.h} will appear older than @file{data.c}, the second rule -will be triggered, a shell will be started to execute the -@samp{if@dots{}fi} command, but actually it will just execute the -@code{then} branch, that is: nothing. In other words, because the -witness we selected is not the first file created by @command{foo}, -@command{make} will start a shell to do nothing each time it is run. +However there are now two minor problems in this setup. One is related +to the timestamp ordering of @file{data.h}, @file{data.w}, +@file{data.x}, and @file{data.c}. The other one is a race condition +if a parallel @command{make} attempts to run multiple instance of the +recover block at once. + +Let us deal with the first problem. @command{foo} outputs four files, +but we do not know in which order these files are created. Suppose +that @file{data.h} is created before @file{data.c}. Then we have a +weird situation. The next time @command{make} is run, @file{data.h} +will appear older than @file{data.c}, the second rule will be +triggered, a shell will be started to execute the @samp{if@dots{}fi} +command, but actually it will just execute the @code{then} branch, +that is: nothing. In other words, because the witness we selected is +not the first file created by @command{foo}, @command{make} will start +a shell to do nothing each time it is run. A simple riposte is to fix the timestamps when this happens. @@ -8937,14 +8946,14 @@ data.h data.w data.x: data.c @@if test -f $@@; then \ touch $@@; \ else \ +## Recover from the removal of $@@ rm -f data.c; \ $(MAKE) $(AM_MAKEFLAGS) data.c; \ fi @end example -Another solution, not incompatible with the previous one, is to use a -different and dedicated file as witness, rather than using any of -@command{foo}'s outputs. +Another solution is to use a different and dedicated file as witness, +rather than using any of @command{foo}'s outputs. @example data.stamp: data.foo data.bar @@ -8953,9 +8962,8 @@ data.stamp: data.foo data.bar foo data.foo data.bar @@mv -f data.tmp $@@ data.c data.h data.w data.x: data.stamp - @@if test -f $@@; then \ - touch $@@; \ - else \ +## Recover from the removal of $@@ + @@if test -f $@@; then :; else \ rm -f data.stamp; \ $(MAKE) $(AM_MAKEFLAGS) data.stamp; \ fi @@ -8966,6 +8974,44 @@ timestamp older than output files output by @command{foo}. It is then renamed to @file{data.stamp} after @command{foo} has run, because we do not want to update @file{data.stamp} if @command{foo} fails. +This solution still suffers from the second problem: the race +condition in the recover rule. If, after a successful build, a user +erases @file{data.c} and @file{data.h}, and run @samp{make -j}, then +@command{make} may start both recover rules in parallel. If the two +instances of the rule execute @samp{$(MAKE) $(AM_MAKEFLAGS) +data.stamp} concurrently the build is likely to fail (for instance the +two rules will create @file{data.tmp}, but only one can rename it). + +Admittedly, such weird situation does not happen during ordinary +builds. It occurs only when the build tree is mutilated. Here +@file{data.c} and @file{data.h} have been explicitly removed without +also removing @file{data.stamp} and the other output files. +@code{make clean; make} will always recover from these situations even +with parallel makes, so you may decide that the recover rule is just +an help to non-parallel make users and leave things as-is. Fixing +this requires some locking mechanism to ensure only one instance of +the recover rule rebuild @code{data.stamp}. One could imagine +something along the following lines. + +@example +data.c data.h data.w data.x: data.stamp +## Recover from the removal of $@@ + @@if test -f $@@; then :; else \ + trap 'rm -rf data.lock data.stamp 1 2 13 15; \ +## mkdir is a portable test-and-set + if mkdir data.lock 2>/dev/null; then \ +## This code is being executed by the first process. + rm -f data.stamp; \ + $(MAKE) $(AM_MAKEFLAGS) data.stamp; \ + else \ +## This code is being executed by the follower processes. +## Wait until the first process is done. + while test -d data.lock; do sleep 1; done; \ +## Succeed if and only if the first process succeeded. + test -f data.stamp; exit $$?; \ + fi +@end example + Using a dedicated witness like this is very handy when the list of output files is not known beforehand. As an illustration, consider the following rules to compile many @file{*.el} files into @@ -8984,11 +9030,21 @@ elc-stamp: $(ELFILES) @@mv -f elc-temp $@@ $(ELCFILES): elc-stamp - @@if test -f $@@; then \ - touch $@@; \ - else \ - rm -f elc-stamp; \ - $(MAKE) $(AM_MAKEFLAGS) elc-stamp; \ +## Recover from the removal of $@@ + @@if test -f $@@; then :; else \ + trap 'rm -rf elc-lock elc-stamp' 1 2 13 15; \ + if mkdir elc-lock 2>/dev/null; then \ +## This code is being executed by the first process. + rm -f elc-stamp; \ + $(MAKE) $(AM_MAKEFLAGS) elc-stamp; \ + rmdir elc-lock; \ + else \ +## This code is being executed by the follower processes. +## Wait until the first process is done. + while test -d elc-lock; do sleep 1; done; \ +## Succeed if and only if the first process succeeded. + test -f elc-stamp; exit $$?; \ + fi; \ fi @end example diff --git a/doc/stamp-vti b/doc/stamp-vti index b69d015..1c752fe 100644 --- a/doc/stamp-vti +++ b/doc/stamp-vti @@ -1,4 +1,4 @@ -@set UPDATED 27 March 2005 +@set UPDATED 29 March 2005 @set UPDATED-MONTH March 2005 @set EDITION 1.9a @set VERSION 1.9a diff --git a/doc/version.texi b/doc/version.texi index b69d015..1c752fe 100644 --- a/doc/version.texi +++ b/doc/version.texi @@ -1,4 +1,4 @@ -@set UPDATED 27 March 2005 +@set UPDATED 29 March 2005 @set UPDATED-MONTH March 2005 @set EDITION 1.9a @set VERSION 1.9a diff --git a/lib/am/lisp.am b/lib/am/lisp.am index 15b8bfe..dda1f59 100644 --- a/lib/am/lisp.am +++ b/lib/am/lisp.am @@ -49,12 +49,30 @@ elc-stamp: $(LISP) $(am__ELCFILES): elc-stamp ## Recover from the removal of $@. ## -## Make sure not to call `make elc-stamp' if emacs is not available, -## because as all *.elc files appear as missing, a parallel make would -## attempt to build elc-stamp several times. +## Do not call `make elc-stamp' if emacs is not available, because it would +## be useless. @if test "$(EMACS)" != no && test ! -f $@; then \ - rm -f elc-stamp; \ - $(MAKE) $(AM_MAKEFLAGS) elc-stamp; \ +## If `make -j' is used and more than one file has been erased, several +## processes can execute this block. We have to make sure that only +## the first one will run `$(MAKE) $(AM_MAKEFLAGS) elc-stamp', and the +## other ones will wait. +## +## There is a race here if only one child of make receive a signal. +## In that case the build may fail. We remove elc-stamp when we receive +## a signal so we are sure the build will succeed the next time. + trap 'rm -rf elc-lock elc-stamp' 1 2 13 15; \ + if mkdir elc-lock 2>/dev/null; then \ +## This code is being executed by the first process. + rm -f elc-stamp; \ + $(MAKE) $(AM_MAKEFLAGS) elc-stamp; \ + rmdir elc-lock; \ + else \ +## This code is being executed by the follower processes. +## Wait until the first process is done. + while test -d elc-lock; do sleep 1; done; \ +## Succeed if and only if the first process succeeded. + test -f elc-stamp; exit $$?; \ + fi; \ else : ; fi diff --git a/tests/Makefile.am b/tests/Makefile.am index af4ac59..e7b1b25 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -321,6 +321,7 @@ lisp4.test \ lisp5.test \ lisp6.test \ lisp7.test \ +lisp8.test \ listval.test \ location.test \ longline.test \ diff --git a/tests/Makefile.in b/tests/Makefile.in index 8e30dce..052987a 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -441,6 +441,7 @@ lisp4.test \ lisp5.test \ lisp6.test \ lisp7.test \ +lisp8.test \ listval.test \ location.test \ longline.test \ diff --git a/tests/lisp6.test b/tests/lisp6.test index 0b415a6..724c052 100755 --- a/tests/lisp6.test +++ b/tests/lisp6.test @@ -1,5 +1,5 @@ #! /bin/sh -# Copyright (C) 2004 Free Software Foundation, Inc. +# Copyright (C) 2004, 2005 Free Software Foundation, Inc. # # This file is part of GNU Automake. # @@ -83,6 +83,14 @@ test -f am-two.elc test -f am-three.elc test -f elc-stamp +# Let's mutilate the source tree, the check the recover rule. +rm -f am-*.elc +$MAKE +test -f am-one.elc +test -f am-two.elc +test -f am-three.elc +test -f elc-stamp + $MAKE install test -f lisp/am-one.el test -f lisp/am-one.elc diff --git a/tests/lisp8.test b/tests/lisp8.test new file mode 100755 index 0000000..650cdda --- /dev/null +++ b/tests/lisp8.test @@ -0,0 +1,65 @@ +#! /bin/sh +# Copyright (C) 2005 Free Software Foundation, Inc. +# +# This file is part of GNU Automake. +# +# GNU Automake 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 2, or (at your option) +# any later version. +# +# GNU Automake 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 Automake; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# Check the recover rule of lisp_LISP with parallel make. + +required='GNUmake emacs' +. ./defs || exit 1 + +set -e + +cat > Makefile.am << 'EOF' +dist_lisp_LISP = am-one.el am-two.el am-three.el +EOF + +cat >> configure.in << 'EOF' +AM_PATH_LISPDIR +AC_OUTPUT +EOF + +echo "(require 'am-two)" > am-one.el +echo "(require 'am-three) (provide 'am-two)" > am-two.el +echo "(provide 'am-three)" > am-three.el + +$ACLOCAL +$AUTOCONF +$AUTOMAKE --add-missing +./configure + +$MAKE -j >stdout + +cat stdout +test 1 -eq `grep 'Warnings can be ignored' stdout | wc -l` + +test -f am-one.elc +test -f am-two.elc +test -f am-three.elc +test -f elc-stamp + +rm -f am-*.elc + +$MAKE -j >stdout + +cat stdout +test 1 -eq `grep 'Warnings can be ignored' stdout | wc -l` +test -f am-one.elc +test -f am-two.elc +test -f am-three.elc +test -f elc-stamp -- 2.7.4