Make 'repo sync -jN' exit with an error code in the case of sync errors.

The bug that this is fixing is described here:

http://code.google.com/p/chromium-os/issues/detail?id=6813

This fix allows the helper threads to signal the main thread that they
saw an error.  When the main thread sees the error, it will let all
existing threads finish, then exit with an error.

Change-Id: If3019bc6b0b3ab9304d49ed2eea53e9d57f3095a
This commit is contained in:
Doug Anderson 2011-03-16 15:49:18 -07:00 committed by Shawn O. Pearce
parent fce89f218a
commit fc06ced9f9

View File

@ -39,6 +39,10 @@ from project import R_HEADS
from project import SyncBuffer from project import SyncBuffer
from progress import Progress from progress import Progress
class _FetchError(Exception):
"""Internal error thrown in _FetchHelper() when we don't want stack trace."""
pass
class Sync(Command, MirrorSafeCommand): class Sync(Command, MirrorSafeCommand):
jobs = 1 jobs = 1
common = True common = True
@ -135,18 +139,58 @@ later is required to fix a server side protocol bug.
dest='repo_upgraded', action='store_true', dest='repo_upgraded', action='store_true',
help=SUPPRESS_HELP) help=SUPPRESS_HELP)
def _FetchHelper(self, opt, project, lock, fetched, pm, sem): def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
if not project.Sync_NetworkHalf(quiet=opt.quiet): """Main function of the fetch threads when jobs are > 1.
Args:
opt: Program options returned from optparse. See _Options().
project: Project object for the project to fetch.
lock: Lock for accessing objects that are shared amongst multiple
_FetchHelper() threads.
fetched: set object that we will add project.gitdir to when we're done
(with our lock held).
pm: Instance of a Project object. We will call pm.update() (with our
lock held).
sem: We'll release() this semaphore when we exit so that another thread
can be started up.
err_event: We'll set this event in the case of an error (after printing
out info about the error).
"""
# We'll set to true once we've locked the lock.
did_lock = False
# Encapsulate everything in a try/except/finally so that:
# - We always set err_event in the case of an exception.
# - We always make sure we call sem.release().
# - We always make sure we unlock the lock if we locked it.
try:
success = project.Sync_NetworkHalf(quiet=opt.quiet)
# Lock around all the rest of the code, since printing, updating a set
# and Progress.update() are not thread safe.
lock.acquire()
did_lock = True
if not success:
print >>sys.stderr, 'error: Cannot fetch %s' % project.name print >>sys.stderr, 'error: Cannot fetch %s' % project.name
if opt.force_broken: if opt.force_broken:
print >>sys.stderr, 'warn: --force-broken, continuing to sync' print >>sys.stderr, 'warn: --force-broken, continuing to sync'
else: else:
sem.release() raise _FetchError()
sys.exit(1)
lock.acquire()
fetched.add(project.gitdir) fetched.add(project.gitdir)
pm.update() pm.update()
except BaseException, e:
# Notify the _Fetch() function about all errors.
err_event.set()
# If we got our own _FetchError, we don't want a stack trace.
# However, if we got something else (something in Sync_NetworkHalf?),
# we'd like one (so re-raise after we've set err_event).
if not isinstance(e, _FetchError):
raise
finally:
if did_lock:
lock.release() lock.release()
sem.release() sem.release()
@ -169,7 +213,13 @@ later is required to fix a server side protocol bug.
threads = set() threads = set()
lock = _threading.Lock() lock = _threading.Lock()
sem = _threading.Semaphore(self.jobs) sem = _threading.Semaphore(self.jobs)
err_event = _threading.Event()
for project in projects: for project in projects:
# Check for any errors before starting any new threads.
# ...we'll let existing threads finish, though.
if err_event.is_set():
break
sem.acquire() sem.acquire()
t = _threading.Thread(target = self._FetchHelper, t = _threading.Thread(target = self._FetchHelper,
args = (opt, args = (opt,
@ -177,13 +227,19 @@ later is required to fix a server side protocol bug.
lock, lock,
fetched, fetched,
pm, pm,
sem)) sem,
err_event))
threads.add(t) threads.add(t)
t.start() t.start()
for t in threads: for t in threads:
t.join() t.join()
# If we saw an error, exit with code 1 so that other scripts can check.
if err_event.is_set():
print >>sys.stderr, '\nerror: Exited sync due to fetch errors'
sys.exit(1)
pm.end() pm.end()
for project in projects: for project in projects:
project.bare_git.gc('--auto') project.bare_git.gc('--auto')