progress: retry ferr.flush() and .write() on EINTR (issue5532)
authorYuya Nishihara <yuya@tcha.org>
Thu, 13 Apr 2017 22:31:17 +0900
changeset 32049 ed42e00a5c4e
parent 32048 c3ef33fd0058
child 32054 616e788321cc
child 32110 6cacc271ee0a
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
mercurial/progress.py
--- a/mercurial/progress.py	Thu Apr 13 22:27:25 2017 +0900
+++ b/mercurial/progress.py	Thu Apr 13 22:31:17 2017 +0900
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import errno
 import threading
 import time
 
@@ -60,6 +61,24 @@
     # 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 @@
         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()