From ae3b6ee363fe76d3bd1a4b867e92609fea668e29 Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Sat, 13 Nov 2010 02:47:54 -0800 Subject: [PATCH] Basic locking infrastructure. So that more than one redo doesn't try to build the same thing at the same time. Kind of dumb, though, since it currently wipes out all the locks at the toplevel, so running more than one at a time won't give accurate results, but the -j option doesn't do anything yet. --- all.do | 2 +- redo.py | 47 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/all.do b/all.do index 5ebcc71..0335a31 100644 --- a/all.do +++ b/all.do @@ -1,2 +1,2 @@ -echo "Nothing to do. Try 'redo test'" >&2 +echo "Nothing to do. Try 'redo t' or 'redo test'" >&2 diff --git a/redo.py b/redo.py index 417658e..acd8421 100755 --- a/redo.py +++ b/redo.py @@ -1,5 +1,5 @@ #!/usr/bin/python -import sys, os, subprocess +import sys, os, subprocess, glob, time import options optspec = """ @@ -30,12 +30,21 @@ if not os.environ.get('REDO_BASE', ''): os.environ['REDO_BASE'] = base os.environ['REDO_STARTDIR'] = os.getcwd() + # FIXME: just wiping out all the locks is kind of cheating. But we + # only do this from the toplevel redo process, so unless the user + # deliberately starts more than one redo on the same repository, it's + # sort of ok. + for f in glob.glob('%s/lock.*' % base): + unlink(f) + import vars from helpers import * class BuildError(Exception): pass +class BuildLocked(Exception): + pass def find_do_file(t): @@ -71,7 +80,7 @@ def _preexec(t): os.chdir(dn) -def build(t): +def _build(t): unlink(sname('dep', t)) open(sname('dep', t), 'w').close() dofile = find_do_file(t) @@ -110,6 +119,23 @@ def build(t): raise BuildError('%s: exit code %d' % (t,rv)) +def build(t): + lockname = sname('lock', t) + try: + fd = os.open(lockname, os.O_CREAT|os.O_EXCL) + except OSError, e: + if e.errno == errno.EEXIST: + log('%s (locked...)\n' % relpath(t, vars.STARTDIR)) + raise BuildLocked(t) + else: + raise + os.close(fd) + try: + return _build(t) + finally: + unlink(lockname) + + if not vars.DEPTH: # toplevel call to redo exenames = [os.path.abspath(sys.argv[0]), os.path.realpath(sys.argv[0])] @@ -120,18 +146,31 @@ if not vars.DEPTH: try: retcode = 0 - startdir = os.getcwd() + locked = {} for t in targets: if os.path.exists('%s/all.do' % t): # t is a directory, but it has a default target t = '%s/all' % t mkdirp('%s/.redo' % vars.BASE) - os.chdir(startdir) try: build(t) except BuildError, e: err('%s\n' % e) retcode = 1 + except BuildLocked, e: + locked[t] = 1 + while locked: + for l in locked.keys(): + lockname = sname('lock', t) + stampname = sname('stamp', t) + if not os.path.exists(lockname): + relp = relpath(t, vars.STARTDIR) + log('%s (...unlocked!)\n' % relp) + if not os.path.exists(stampname): + err('%s: failed in another thread\n' % relp) + retcode = 2 + del locked[l] + time.sleep(0.2) sys.exit(retcode) except KeyboardInterrupt: sys.exit(200)