summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGavin Mak <gavinmak@google.com>2025-08-13 00:07:29 -0700
committerGavin Mak <gavinmak@google.com>2025-08-13 23:16:55 -0700
commit3e6acf2778b533aedb2a1f6f6e3a3159e0b8c86d (patch)
tree43279ccd9542c3844e09c810144734a584c8f4cb
parenta6e1a59ac16b294962a9d06f9ed67d78cf74ccd5 (diff)
downloadgit-repo-3e6acf2778b533aedb2a1f6f6e3a3159e0b8c86d.tar.gz
progress: Fix race condition causing fileno crash
A race condition occurs when sync redirects sys.stderr to capture worker output, while a background progress thread simultaneously calls fileno() on it. This causes an io.UnsupportedOperation error. Fix by caching the original sys.stderr for all progress bar IO. Change-Id: Idb1f45d707596d31238a19fd373cac3bf669c405 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/498121 Tested-by: Gavin Mak <gavinmak@google.com> Reviewed-by: Scott Lee <ddoman@google.com>
-rw-r--r--progress.py17
1 files changed, 10 insertions, 7 deletions
diff --git a/progress.py b/progress.py
index 30ec8c3b..9a91dcd6 100644
--- a/progress.py
+++ b/progress.py
@@ -25,7 +25,10 @@ except ImportError:
25from repo_trace import IsTraceToStderr 25from repo_trace import IsTraceToStderr
26 26
27 27
28_TTY = sys.stderr.isatty() 28# Capture the original stderr stream. We use this exclusively for progress
29# updates to ensure we talk to the terminal even if stderr is redirected.
30_STDERR = sys.stderr
31_TTY = _STDERR.isatty()
29 32
30# This will erase all content in the current line (wherever the cursor is). 33# This will erase all content in the current line (wherever the cursor is).
31# It does not move the cursor, so this is usually followed by \r to move to 34# It does not move the cursor, so this is usually followed by \r to move to
@@ -133,11 +136,11 @@ class Progress:
133 def _write(self, s): 136 def _write(self, s):
134 s = "\r" + s 137 s = "\r" + s
135 if self._elide: 138 if self._elide:
136 col = os.get_terminal_size(sys.stderr.fileno()).columns 139 col = os.get_terminal_size(_STDERR.fileno()).columns
137 if len(s) > col: 140 if len(s) > col:
138 s = s[: col - 1] + ".." 141 s = s[: col - 1] + ".."
139 sys.stderr.write(s) 142 _STDERR.write(s)
140 sys.stderr.flush() 143 _STDERR.flush()
141 144
142 def start(self, name): 145 def start(self, name):
143 self._active += 1 146 self._active += 1
@@ -211,9 +214,9 @@ class Progress:
211 214
212 # Erase the current line, print the message with a newline, 215 # Erase the current line, print the message with a newline,
213 # and then immediately redraw the progress bar on the new line. 216 # and then immediately redraw the progress bar on the new line.
214 sys.stderr.write("\r" + CSI_ERASE_LINE) 217 _STDERR.write("\r" + CSI_ERASE_LINE)
215 sys.stderr.write(msg + "\n") 218 _STDERR.write(msg + "\n")
216 sys.stderr.flush() 219 _STDERR.flush()
217 self.update(inc=0) 220 self.update(inc=0)
218 221
219 def end(self): 222 def end(self):