diff options
author | Gavin Mak <gavinmak@google.com> | 2025-08-13 00:07:29 -0700 |
---|---|---|
committer | Gavin Mak <gavinmak@google.com> | 2025-08-13 23:16:55 -0700 |
commit | 3e6acf2778b533aedb2a1f6f6e3a3159e0b8c86d (patch) | |
tree | 43279ccd9542c3844e09c810144734a584c8f4cb | |
parent | a6e1a59ac16b294962a9d06f9ed67d78cf74ccd5 (diff) | |
download | git-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.py | 17 |
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: | |||
25 | from repo_trace import IsTraceToStderr | 25 | from 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): |