builder.py: atomically replace the log for a given target.

Previously we were truncating the log if it existed.  This would cause
redo-log to produce invalid output if you had the following (admittedly
rare) sequence in a single session:
- start building X
- redo-log starts showing the log for X
- finish building X
- redo-log has not finished showing the log for X yet
- start building X again for some reason
- redo-log sees a truncated logfile.

Now, redo-log can finish reading the original file (which no longer has
a filename since it was overwritten) while the new file is being
created.
This commit is contained in:
Avery Pennarun 2019-03-02 03:45:35 -05:00
commit 63230a1ae3
2 changed files with 14 additions and 4 deletions

View file

@ -229,10 +229,20 @@ class _BuildJob(object):
firstline = open(os.path.join(dodir, dofile)).readline().strip()
if firstline.startswith('#!/'):
argv[0:2] = firstline[2:].split(' ')
# make sure to create the logfile *before* writing the meta() about it.
# that way redo-log won't trace into an obsolete logfile.
# make sure to create the logfile *before* writing the meta() about
# it. that way redo-log won't trace into an obsolete logfile.
#
# We open a temp file and atomically rename it into place here.
# This guarantees that redo-log will never experience a file that
# gets truncated halfway through reading (eg. if we build the same
# target more than once in a run). Similarly, we don't want to
# actually unlink() the file in case redo-log is about to start
# reading a previous instance created during this session. It
# should always see either the old or new instance.
if env.v.LOG:
open(state.logname(self.sf.id), 'w')
lfd, lfname = tempfile.mkstemp(prefix='redo.', suffix='.log.tmp')
os.fdopen(lfd, 'w')
os.rename(lfname, state.logname(self.sf.id))
dof = state.File(name=os.path.join(dodir, dofile))
dof.set_static()
dof.save()