# HG changeset patch # User Augie Fackler # Date 1441136853 14400 # Node ID 4bc3707f3e67fe7d04a69c287668042e36cadefb # Parent 42bb1812686f9a82bf2936e9be5d08115828f73a bundle2: don't try to recover from a GeneratorExit (issue4785) GeneratorExit means the other end of the conversation has already stopped listening, so don't try and yield out error information. Instead, just let the GeneratorExit propagate normally. This should resolve esoteric issues observed with servers that have aggressive timeouts waiting for data to send to clients logging internal Python errors[0]. This has been observed with both gunicorn's gevent worker model and with scm-manager's built-in webserver (which itself is something sitting inside jetty.) 0: Exception RuntimeError: 'generator ignored GeneratorExit' in ignored diff -r 42bb1812686f -r 4bc3707f3e67 mercurial/bundle2.py --- a/mercurial/bundle2.py Tue Sep 01 16:46:05 2015 -0700 +++ b/mercurial/bundle2.py Tue Sep 01 15:47:33 2015 -0400 @@ -847,6 +847,12 @@ outdebug(ui, 'payload chunk size: %i' % len(chunk)) yield _pack(_fpayloadsize, len(chunk)) yield chunk + except GeneratorExit: + # GeneratorExit means that nobody is listening for our + # results anyway, so just bail quickly rather than trying + # to produce an error part. + ui.debug('bundle2-generatorexit\n') + raise except BaseException as exc: # backup exception data for later ui.debug('bundle2-input-stream-interrupt: encoding exception %s' diff -r 42bb1812686f -r 4bc3707f3e67 tests/test-bundle2-format.t --- a/tests/test-bundle2-format.t Tue Sep 01 16:46:05 2015 -0700 +++ b/tests/test-bundle2-format.t Tue Sep 01 15:47:33 2015 -0400 @@ -78,6 +78,7 @@ > ('', 'reply', False, 'produce a reply bundle'), > ('', 'pushrace', False, 'includes a check:head part with unknown nodes'), > ('', 'genraise', False, 'includes a part that raise an exception during generation'), + > ('', 'timeout', False, 'emulate a timeout during bundle generation'), > ('r', 'rev', [], 'includes those changeset in the bundle'),], > '[OUTPUTFILE]') > def cmdbundle2(ui, repo, path=None, **opts): @@ -143,6 +144,18 @@ > else: > file = open(path, 'wb') > + > if opts['timeout']: + > bundler.newpart('test:song', data=ELEPHANTSSONG, mandatory=False) + > for idx, junk in enumerate(bundler.getchunks()): + > ui.write('%d chunk\n' % idx) + > if idx > 4: + > # This throws a GeneratorExit inside the generator, which + > # can cause problems if the exception-recovery code is + > # too zealous. It's important for this test that the break + > # occur while we're in the middle of a part. + > break + > ui.write('fake timeout complete.\n') + > return > try: > for chunk in bundler.getchunks(): > file.write(chunk) @@ -239,6 +252,26 @@ $ hg bundle2 HG20\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc) +Test timeouts during bundling + $ hg bundle2 --timeout --debug --config devel.bundle2.debug=yes + bundle2-output-bundle: "HG20", 1 parts total + bundle2-output: start emission of HG20 stream + 0 chunk + bundle2-output: bundle parameter: + 1 chunk + bundle2-output: start of parts + bundle2-output: bundle part: "test:song" + bundle2-output-part: "test:song" (advisory) 178 bytes payload + bundle2-output: part 0: "test:song" + bundle2-output: header chunk size: 16 + 2 chunk + 3 chunk + bundle2-output: payload chunk size: 178 + 4 chunk + 5 chunk + bundle2-generatorexit + fake timeout complete. + Test unbundling $ hg bundle2 | hg statbundle2