[PATCH] chgserver: backport py3 buffered I/O workarounds from procutil
Augie Fackler
raf at durin42.com
Tue Nov 17 18:45:08 UTC 2020
queued, thanks
> On Nov 17, 2020, at 08:04, Yuya Nishihara <yuya at tcha.org> wrote:
>
> # HG changeset patch
> # User Yuya Nishihara <yuya at tcha.org>
> # Date 1605608948 -32400
> # Tue Nov 17 19:29:08 2020 +0900
> # Node ID 0748354881c942727aef46945d721bc51d213e9d
> # Parent d68618954adef9c2cbec868c1af0e01f67288cb8
> chgserver: backport py3 buffered I/O workarounds from procutil
>
> I've recently switched to new machine and I found chg's stdout is fully
> buffered.
>
> Even though chg server is a daemon process, it inherits the environment
> where the chg client originally forked the server. This means the server's
> stdout might have been wrapped by LineBufferedWrapper. That's why we need
> to do wrap/unwrap in both ways.
>
> The "if" condition in _restoreio() looks weird, but I'm not willing to
> clean things up because stdio behavior is fundamentally different between
> py2 and py3, and py2 support will be dropped anyway.
>
> diff --git a/mercurial/chgserver.py b/mercurial/chgserver.py
> --- a/mercurial/chgserver.py
> +++ b/mercurial/chgserver.py
> @@ -409,14 +409,23 @@ class chgcmdserver(commandserver.server)
> # be unbuffered no matter if it is a tty or not.
> if fn == b'ferr':
> newfp = fp
> + elif pycompat.ispy3:
> + # On Python 3, the standard library doesn't offer line-buffered
> + # binary streams, so wrap/unwrap it.
> + if fp.isatty():
> + newfp = procutil.make_line_buffered(fp)
> + else:
> + newfp = procutil.unwrap_line_buffered(fp)
> else:
> - # make it line buffered explicitly because the default is
> - # decided on first write(), where fout could be a pager.
> + # Python 2 uses the I/O streams provided by the C library, so
> + # make it line-buffered explicitly. Otherwise the default would
> + # be decided on first write(), where fout could be a pager.
> if fp.isatty():
> bufsize = 1 # line buffered
> else:
> bufsize = -1 # system default
> newfp = os.fdopen(fp.fileno(), mode, bufsize)
> + if newfp is not fp:
> setattr(ui, fn, newfp)
> setattr(self, cn, newfp)
>
> @@ -440,13 +449,16 @@ class chgcmdserver(commandserver.server)
> ui = self.ui
> 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
> - if newfp is not fp:
> + # On Python 2, newfp and fp may be separate file objects associated
> + # with the same fd, so we must close newfp while it's associated
> + # with the client. Otherwise the new associated fd would be closed
> + # when newfp gets deleted. On Python 3, newfp is just a wrapper
> + # around fp even if newfp is not fp, so deleting newfp is safe.
> + if not (pycompat.ispy3 or newfp is fp):
> newfp.close()
> # restore original fd: fp is open again
> try:
> - if newfp is fp and 'w' in mode:
> + if (pycompat.ispy3 or 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.
> diff --git a/mercurial/utils/procutil.py b/mercurial/utils/procutil.py
> --- a/mercurial/utils/procutil.py
> +++ b/mercurial/utils/procutil.py
> @@ -80,6 +80,13 @@ def make_line_buffered(stream):
> return LineBufferedWrapper(stream)
>
>
> +def unwrap_line_buffered(stream):
> + if isinstance(stream, LineBufferedWrapper):
> + assert not isinstance(stream.orig, LineBufferedWrapper)
> + return stream.orig
> + return stream
> +
> +
> class WriteAllWrapper(object):
> def __init__(self, orig):
> self.orig = orig
>
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
More information about the Mercurial-devel
mailing list