Patchwork [2,of,2] progress: retry ferr.flush() and .write() on EINTR (issue5532)

login
register
mail settings
Submitter Yuya Nishihara
Date April 15, 2017, 10:19 a.m.
Message ID <9de8286012a70e815978.1492251542@mimosa>
Download mbox | patch
Permalink /patch/20217/
State Accepted
Headers show

Comments

Yuya Nishihara - April 15, 2017, 10:19 a.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1492090277 -32400
#      Thu Apr 13 22:31:17 2017 +0900
# Node ID 9de8286012a70e815978f595c28f13c28d72c266
# Parent  087fef3472d6f204c8ef55c5257b9950aac35e72
progress: retry ferr.flush() and .write() on EINTR (issue5532)

See the inline comment how this could mitigate the issue.

I couldn't reproduce the exact problem on my Linux machine, but there are
at least two people who got EINTR in progress.py, and it seems file_write()
of Python 2 is fundamentally broken [1]. Let's make something in on 4.2.

 [1]: https://hg.python.org/cpython/file/v2.7.13/Objects/fileobject.c#l1850
Augie Fackler - April 18, 2017, 3:37 p.m.
On Sat, Apr 15, 2017 at 07:19:02PM +0900, Yuya Nishihara wrote:
> # HG changeset patch
> # User Yuya Nishihara <yuya@tcha.org>
> # Date 1492090277 -32400
> #      Thu Apr 13 22:31:17 2017 +0900
> # Node ID 9de8286012a70e815978f595c28f13c28d72c266
> # Parent  087fef3472d6f204c8ef55c5257b9950aac35e72
> progress: retry ferr.flush() and .write() on EINTR (issue5532)

queued, thanks

Patch

diff --git a/mercurial/progress.py b/mercurial/progress.py
--- a/mercurial/progress.py
+++ b/mercurial/progress.py
@@ -7,6 +7,7 @@ 
 
 from __future__ import absolute_import
 
+import errno
 import threading
 import time
 
@@ -60,6 +61,24 @@  def fmtremaining(seconds):
     # i18n: format X years and YY weeks as "XyYYw"
     return _("%dy%02dw") % (years, weeks)
 
+# file_write() and file_flush() of Python 2 do not restart on EINTR if
+# the file is attached to a "slow" device (e.g. a terminal) and raise
+# IOError. We cannot know how many bytes would be written by file_write(),
+# but a progress text is known to be short enough to be written by a
+# single write() syscall, so we can just retry file_write() with the whole
+# text. (issue5532)
+#
+# This should be a short-term workaround. We'll need to fix every occurrence
+# of write() to a terminal or pipe.
+def _eintrretry(func, *args):
+    while True:
+        try:
+            return func(*args)
+        except IOError as err:
+            if err.errno == errno.EINTR:
+                continue
+            raise
+
 class progbar(object):
     def __init__(self, ui):
         self.ui = ui
@@ -179,10 +198,10 @@  class progbar(object):
         self._flusherr()
 
     def _flusherr(self):
-        self.ui.ferr.flush()
+        _eintrretry(self.ui.ferr.flush)
 
     def _writeerr(self, msg):
-        self.ui.ferr.write(msg)
+        _eintrretry(self.ui.ferr.write, msg)
 
     def width(self):
         tw = self.ui.termwidth()