# HG changeset patch # User Yuya Nishihara # Date 1595244684 -32400 # Node ID a17454a189d10368a20f2b2763e6b95e78a7318b # Parent 3781e9f74b278213a4bcc6a78f4e03c92894119f chgserver: discard buffered output before restoring fds (issue6207) On Python 3, flush() appears not discarding buffered data on EPIPE, and the buffered data will be carried over to the restored stdout. diff -r 3781e9f74b27 -r a17454a189d1 mercurial/chgserver.py --- a/mercurial/chgserver.py Tue Jul 21 20:49:05 2020 +0900 +++ b/mercurial/chgserver.py Mon Jul 20 20:31:24 2020 +0900 @@ -434,8 +434,11 @@ self._oldios.append((ch, fp, fd)) def _restoreio(self): + if not self._oldios: + return + nullfd = os.open(os.devnull, os.O_WRONLY) ui = self.ui - for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels): + for (ch, fp, fd), (cn, fn, mode) in zip(self._oldios, _iochannels): newfp = getattr(ui, fn) # close newfp while it's associated with client; otherwise it # would be closed when newfp is deleted @@ -443,6 +446,12 @@ newfp.close() # restore original fd: fp is open again try: + if newfp is fp and 'w' in mode: + # Discard buffered data which couldn't be flushed because + # of EPIPE. The data should belong to the current session + # and should never persist. + os.dup2(nullfd, fp.fileno()) + fp.flush() os.dup2(fd, fp.fileno()) except OSError as err: # According to issue6330, running chg on heavy loaded systems @@ -459,6 +468,7 @@ os.close(fd) setattr(self, cn, ch) setattr(ui, fn, fp) + os.close(nullfd) del self._oldios[:] def validate(self): diff -r 3781e9f74b27 -r a17454a189d1 tests/test-chg.t --- a/tests/test-chg.t Tue Jul 21 20:49:05 2020 +0900 +++ b/tests/test-chg.t Mon Jul 20 20:31:24 2020 +0900 @@ -152,6 +152,49 @@ crash-pager: going to crash [255] +no stdout data should be printed after pager quits, and the buffered data +should never persist (issue6207) + +"killed!" may be printed if terminated by SIGPIPE, which isn't important +in this test. + + $ cat > $TESTTMP/bulkwrite.py <<'EOF' + > import time + > from mercurial import error, registrar + > cmdtable = {} + > command = registrar.command(cmdtable) + > @command(b'bulkwrite') + > def bulkwrite(ui, repo, *pats, **opts): + > ui.write(b'going to write massive data\n') + > ui.flush() + > t = time.time() + > while time.time() - t < 2: + > ui.write(b'x' * 1023 + b'\n') # will be interrupted by SIGPIPE + > raise error.Abort(b"write() doesn't block") + > EOF + + $ cat > $TESTTMP/fakepager.py <<'EOF' + > import sys + > import time + > sys.stdout.write('paged! %r\n' % sys.stdin.readline()) + > time.sleep(1) # new data will be written + > EOF + + $ cat >> .hg/hgrc < [extensions] + > bulkwrite = $TESTTMP/bulkwrite.py + > EOF + + $ chg bulkwrite --pager=on --color no --config ui.formatted=True + paged! 'going to write massive data\n' + killed! (?) + [255] + + $ chg bulkwrite --pager=on --color no --config ui.formatted=True + paged! 'going to write massive data\n' + killed! (?) + [255] + $ cd .. server lifecycle