comparison mercurial/chgserver.py @ 45852:b56feaa9b520

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.
author Yuya Nishihara <yuya@tcha.org>
date Tue, 17 Nov 2020 19:29:08 +0900
parents d2e1dcd4490d
children 4b4160a83303
comparison
equal deleted inserted replaced
45847:d68618954ade 45852:b56feaa9b520
407 # to see output immediately on pager, the mode stays unchanged 407 # to see output immediately on pager, the mode stays unchanged
408 # when client re-attached. ferr is unchanged because it should 408 # when client re-attached. ferr is unchanged because it should
409 # be unbuffered no matter if it is a tty or not. 409 # be unbuffered no matter if it is a tty or not.
410 if fn == b'ferr': 410 if fn == b'ferr':
411 newfp = fp 411 newfp = fp
412 elif pycompat.ispy3:
413 # On Python 3, the standard library doesn't offer line-buffered
414 # binary streams, so wrap/unwrap it.
415 if fp.isatty():
416 newfp = procutil.make_line_buffered(fp)
417 else:
418 newfp = procutil.unwrap_line_buffered(fp)
412 else: 419 else:
413 # make it line buffered explicitly because the default is 420 # Python 2 uses the I/O streams provided by the C library, so
414 # decided on first write(), where fout could be a pager. 421 # make it line-buffered explicitly. Otherwise the default would
422 # be decided on first write(), where fout could be a pager.
415 if fp.isatty(): 423 if fp.isatty():
416 bufsize = 1 # line buffered 424 bufsize = 1 # line buffered
417 else: 425 else:
418 bufsize = -1 # system default 426 bufsize = -1 # system default
419 newfp = os.fdopen(fp.fileno(), mode, bufsize) 427 newfp = os.fdopen(fp.fileno(), mode, bufsize)
428 if newfp is not fp:
420 setattr(ui, fn, newfp) 429 setattr(ui, fn, newfp)
421 setattr(self, cn, newfp) 430 setattr(self, cn, newfp)
422 431
423 self._ioattached = True 432 self._ioattached = True
424 self.cresult.write(struct.pack(b'>i', len(clientfds))) 433 self.cresult.write(struct.pack(b'>i', len(clientfds)))
438 return 447 return
439 nullfd = os.open(os.devnull, os.O_WRONLY) 448 nullfd = os.open(os.devnull, os.O_WRONLY)
440 ui = self.ui 449 ui = self.ui
441 for (ch, fp, fd), (cn, fn, mode) in zip(self._oldios, _iochannels): 450 for (ch, fp, fd), (cn, fn, mode) in zip(self._oldios, _iochannels):
442 newfp = getattr(ui, fn) 451 newfp = getattr(ui, fn)
443 # close newfp while it's associated with client; otherwise it 452 # On Python 2, newfp and fp may be separate file objects associated
444 # would be closed when newfp is deleted 453 # with the same fd, so we must close newfp while it's associated
445 if newfp is not fp: 454 # with the client. Otherwise the new associated fd would be closed
455 # when newfp gets deleted. On Python 3, newfp is just a wrapper
456 # around fp even if newfp is not fp, so deleting newfp is safe.
457 if not (pycompat.ispy3 or newfp is fp):
446 newfp.close() 458 newfp.close()
447 # restore original fd: fp is open again 459 # restore original fd: fp is open again
448 try: 460 try:
449 if newfp is fp and 'w' in mode: 461 if (pycompat.ispy3 or newfp is fp) and 'w' in mode:
450 # Discard buffered data which couldn't be flushed because 462 # Discard buffered data which couldn't be flushed because
451 # of EPIPE. The data should belong to the current session 463 # of EPIPE. The data should belong to the current session
452 # and should never persist. 464 # and should never persist.
453 os.dup2(nullfd, fp.fileno()) 465 os.dup2(nullfd, fp.fileno())
454 fp.flush() 466 fp.flush()