Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Unreleased
empty. :issue:`3019` :pr:`3021`
- When ``Sentinel.UNSET`` is found during parsing, it will skip calls to
``type_cast_value``. :issue:`3069` :pr:`3090`
- Fix regression related to ``click.testing.StreamMixer`` finalization in ``CliRunner``
that introduced a race condition. :issue:`3110` :pr:`3140`

Version 8.3.0
--------------
Expand Down
18 changes: 11 additions & 7 deletions src/click/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,20 @@ class BytesIOCopy(io.BytesIO):
.. versionadded:: 8.2
"""

copy_to: io.BytesIO

def __init__(self, copy_to: io.BytesIO) -> None:
super().__init__()
self.copy_to = copy_to

def flush(self) -> None:
if not self.copy_to.closed:
self.copy_to.flush()
Comment on lines +82 to +83
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately this LBYL (Look Before You Leap) approach is infamously inappropriate to guard against races; in a concurrent execution model, self.copy_to.closed could change between the moment the guard clause (if ...) is evaluated, and the rest of the block is, and then you're back to square one, but the code is more complex and the race is potentially harder to reproduce (which isn't necessarily a good thing when you're try to debug it).

super().flush()
self.copy_to.flush()

def write(self, b: ReadableBuffer) -> int:
self.copy_to.write(b)
if not self.copy_to.closed:
self.copy_to.write(b)
return super().write(b)


Expand All @@ -100,15 +104,15 @@ def __init__(self) -> None:

def __del__(self) -> None:
"""
Guarantee that embedded file-like objects are closed in a
Guarantee that embedded file-like objects are deleted in a
predictable order, protecting against races between
self.output being closed and other streams being flushed on close
self.output being deleted and other streams being flushed on deletion.

.. versionadded:: 8.2.2
"""
self.stderr.close()
self.stdout.close()
self.output.close()
del self.stdout
del self.stderr
del self.output


class _NamedTextIOWrapper(io.TextIOWrapper):
Expand Down