From d0b14c358dfd60cd7beab809a572b0e08a815d1b Mon Sep 17 00:00:00 2001 From: Vincent Fazio Date: Tue, 30 Apr 2024 10:01:18 -0500 Subject: [PATCH] fix race condition and KeyError exception in executor (#9335) Previously, it was possible that `Executor.supports_fancy_output` would flip-flop between True and False if a thread was updating a section. This could lead to a crash when an operation got different reponses as it made progress. `Executor.supports_fancy_output` reflects the value of the underlying cleo Formatter used by the Output object. The Formatter is shared by any SectionOutputs derived by that Output object. If a thread (tA) is in the middle of `Formatter.remove_format` while updating a section, the flag to show decorator support is temporarily toggled off and then restored. This opens a window where another thread could get an incorrect answer when querying `supports_fancy_output`. If a parallel thread (tB) queries `supports_fancy_output` and sees it is False, the operation will not get added to the Executor's _sections dictionary. If tB's operation progresses after tA has restored the decorator value and attempts to write out progress information it will call `Executor._write`, see that `supports_fancy_output` is now True and attempt to find the operation in the _sections dictionary, however there will not be an entry for that operation due to the earlier query that returned False. This causes tB to throw a KeyError and causes the install to shutdown. Now, the Executor queries and caches whether the Output is decorated during init. This value is used in `supports_fancy_output` so as to not be affected by changes to the underlying Formatter object during section updates. Signed-off-by: Vincent Fazio (cherry picked from commit acaf9c8ba4f719e8d9c37883a7ac075fc0f8e52f) --- src/poetry/installation/executor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/poetry/installation/executor.py b/src/poetry/installation/executor.py index 84af089e445..7ea5818e2e0 100644 --- a/src/poetry/installation/executor.py +++ b/src/poetry/installation/executor.py @@ -106,6 +106,10 @@ def __init__( self._shutdown = False self._hashes: dict[str, str] = {} + # Cache whether decorated output is supported. + # https://github.com/python-poetry/cleo/issues/423 + self._decorated_output: bool = self._io.output.is_decorated() + @property def installations_count(self) -> int: return self._executed["install"] @@ -123,7 +127,7 @@ def enabled(self) -> bool: return self._enabled def supports_fancy_output(self) -> bool: - return self._io.output.is_decorated() and not self._dry_run + return self._decorated_output and not self._dry_run def disable(self) -> Executor: self._enabled = False