Mercurial > hg
comparison mercurial/bundle2.py @ 29847:9a9629b9416c stable
bundle2: fail faster when interrupted
Before this patch, bundle2 application attempted to consume remaining
bundle2 part data when the process is interrupted (SIGINT) or when
sys.exit is called (translated into a SystemExit exception). This
meant that if one of these occurred when applying a say 1 GB
changegroup bundle2 part being downloaded over a network, it may take
Mercurial *several minutes* to terminate after a SIGINT because the
process is waiting on the network to stream megabytes of data. This is
not a great user experience and a regression from bundle1. Furthermore,
many process supervisors tend to only give processes a finite amount of
time to exit after delivering SIGINT: if processes take too long to
self-terminate, a SIGKILL is issued and Mercurial has no opportunity to
clean up. This would mean orphaned locks and transactions. Not good.
This patch changes the bundle2 application behavior to fail faster
when an interrupt or system exit is requested. It does so by not
catching BaseException (which includes KeyboardInterrupt and
SystemExit) and by explicitly checking for these conditions in
yet another handler which would also seek to the end of the current
bundle2 part on failure.
The end result of this patch is that SIGINT is now reacted to
significantly faster: the active transaction is rolled back
immediately without waiting for incoming bundle2 data to be consumed.
This restores the pre-bundle2 behavior and makes Mercurial treat
signals with the urgency they deserve.
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Thu, 25 Aug 2016 19:53:14 -0700 |
parents | 953839de96ab |
children | ccd436f7db6d |
comparison
equal
deleted
inserted
replaced
29766:5004ef47f437 | 29847:9a9629b9416c |
---|---|
351 part = None | 351 part = None |
352 nbpart = 0 | 352 nbpart = 0 |
353 try: | 353 try: |
354 for nbpart, part in iterparts: | 354 for nbpart, part in iterparts: |
355 _processpart(op, part) | 355 _processpart(op, part) |
356 except BaseException as exc: | 356 except Exception as exc: |
357 for nbpart, part in iterparts: | 357 for nbpart, part in iterparts: |
358 # consume the bundle content | 358 # consume the bundle content |
359 part.seek(0, 2) | 359 part.seek(0, 2) |
360 # Small hack to let caller code distinguish exceptions from bundle2 | 360 # Small hack to let caller code distinguish exceptions from bundle2 |
361 # processing from processing the old format. This is mostly | 361 # processing from processing the old format. This is mostly |
380 """process a single part from a bundle | 380 """process a single part from a bundle |
381 | 381 |
382 The part is guaranteed to have been fully consumed when the function exits | 382 The part is guaranteed to have been fully consumed when the function exits |
383 (even if an exception is raised).""" | 383 (even if an exception is raised).""" |
384 status = 'unknown' # used by debug output | 384 status = 'unknown' # used by debug output |
385 hardabort = False | |
385 try: | 386 try: |
386 try: | 387 try: |
387 handler = parthandlermapping.get(part.type) | 388 handler = parthandlermapping.get(part.type) |
388 if handler is None: | 389 if handler is None: |
389 status = 'unsupported-type' | 390 status = 'unsupported-type' |
434 output = op.ui.popbuffer() | 435 output = op.ui.popbuffer() |
435 if output: | 436 if output: |
436 outpart = op.reply.newpart('output', data=output, | 437 outpart = op.reply.newpart('output', data=output, |
437 mandatory=False) | 438 mandatory=False) |
438 outpart.addparam('in-reply-to', str(part.id), mandatory=False) | 439 outpart.addparam('in-reply-to', str(part.id), mandatory=False) |
440 # If exiting or interrupted, do not attempt to seek the stream in the | |
441 # finally block below. This makes abort faster. | |
442 except (SystemExit, KeyboardInterrupt): | |
443 hardabort = True | |
444 raise | |
439 finally: | 445 finally: |
440 # consume the part content to not corrupt the stream. | 446 # consume the part content to not corrupt the stream. |
441 part.seek(0, 2) | 447 if not hardabort: |
448 part.seek(0, 2) | |
442 | 449 |
443 | 450 |
444 def decodecaps(blob): | 451 def decodecaps(blob): |
445 """decode a bundle2 caps bytes blob into a dictionary | 452 """decode a bundle2 caps bytes blob into a dictionary |
446 | 453 |