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

Yuya Nishihara yuya at tcha.org
Sat Apr 15 06:19:02 EDT 2017

# HG changeset patch
# User Yuya Nishihara <yuya at 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

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):
     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()

More information about the Mercurial-devel mailing list