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.
This commit is contained in:
Avery Pennarun 2010-11-13 02:47:54 -08:00
commit ae3b6ee363
2 changed files with 44 additions and 5 deletions

2
all.do
View file

@ -1,2 +1,2 @@
echo "Nothing to do. Try 'redo test'" >&2 echo "Nothing to do. Try 'redo t' or 'redo test'" >&2

47
redo.py
View file

@ -1,5 +1,5 @@
#!/usr/bin/python #!/usr/bin/python
import sys, os, subprocess import sys, os, subprocess, glob, time
import options import options
optspec = """ optspec = """
@ -30,12 +30,21 @@ if not os.environ.get('REDO_BASE', ''):
os.environ['REDO_BASE'] = base os.environ['REDO_BASE'] = base
os.environ['REDO_STARTDIR'] = os.getcwd() 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 import vars
from helpers import * from helpers import *
class BuildError(Exception): class BuildError(Exception):
pass pass
class BuildLocked(Exception):
pass
def find_do_file(t): def find_do_file(t):
@ -71,7 +80,7 @@ def _preexec(t):
os.chdir(dn) os.chdir(dn)
def build(t): def _build(t):
unlink(sname('dep', t)) unlink(sname('dep', t))
open(sname('dep', t), 'w').close() open(sname('dep', t), 'w').close()
dofile = find_do_file(t) dofile = find_do_file(t)
@ -110,6 +119,23 @@ def build(t):
raise BuildError('%s: exit code %d' % (t,rv)) 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: if not vars.DEPTH:
# toplevel call to redo # toplevel call to redo
exenames = [os.path.abspath(sys.argv[0]), os.path.realpath(sys.argv[0])] exenames = [os.path.abspath(sys.argv[0]), os.path.realpath(sys.argv[0])]
@ -120,18 +146,31 @@ if not vars.DEPTH:
try: try:
retcode = 0 retcode = 0
startdir = os.getcwd() locked = {}
for t in targets: for t in targets:
if os.path.exists('%s/all.do' % t): if os.path.exists('%s/all.do' % t):
# t is a directory, but it has a default target # t is a directory, but it has a default target
t = '%s/all' % t t = '%s/all' % t
mkdirp('%s/.redo' % vars.BASE) mkdirp('%s/.redo' % vars.BASE)
os.chdir(startdir)
try: try:
build(t) build(t)
except BuildError, e: except BuildError, e:
err('%s\n' % e) err('%s\n' % e)
retcode = 1 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) sys.exit(retcode)
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit(200) sys.exit(200)