Skip to content

Commit 3db6396

Browse files
authored
Merge pull request #1729 from willmcgugan/v10.15.1
revert thread safety issue
2 parents ad6e3de + 03f40a9 commit 3db6396

File tree

5 files changed

+53
-57
lines changed

5 files changed

+53
-57
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

+5-10
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,22 @@ labels: Needs triage
66
assignees: ""
77
---
88

9-
**Read the docs**
10-
You might find a solution to your problem in the [docs](https://rich.readthedocs.io/en/latest/introduction.html) -- consider using the search function there.
9+
You may find a solution to your problem in the [docs](https://rich.readthedocs.io/en/latest/introduction.html) or [issues](https://github.com/willmcgugan/rich/issues).
1110

1211
**Describe the bug**
13-
A clear and concise description of what the bug is.
1412

15-
**To Reproduce**
16-
A minimal code example that reproduces the problem would be a big help if you can provide it. If the issue is visual in nature, consider posting a screenshot.
13+
Edit this with a clear and concise description of what the bug.
14+
15+
Provide a minimal code example that demonstrates the issue if you can. If the issue is visual in nature, consider posting a screenshot.
1716

1817
**Platform**
18+
1919
What platform (Win/Linux/Mac) are you running on? What terminal software are you using?
2020

21-
**Diagnose**
2221
I may ask you to cut and paste the output of the following commands. It may save some time if you do it now.
2322

2423
```
2524
python -m rich.diagnose
2625
python -m rich._windows
2726
pip freeze | grep rich
2827
```
29-
30-
**Did I help?**
31-
32-
If I was able to resolve your problem, consider [sponsoring](https://github.com/sponsors/willmcgugan) my work on Rich, or [buy me a coffee](https://ko-fi.com/willmcgugan) to say thanks.

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [10.15.1] - 2021-11-29
9+
10+
### Fixed
11+
12+
- Reverted thread-safety fix for Live that introduced deadlock potential
13+
814
## [10.15.0] - 2021-11-28
915

1016
### Added

rich/console.py

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import inspect
22
import os
33
import platform
4-
import shutil
54
import sys
65
import threading
76
from abc import ABC, abstractmethod
@@ -13,6 +12,7 @@
1312
from inspect import isclass
1413
from itertools import islice
1514
from time import monotonic
15+
from threading import RLock
1616
from types import FrameType, TracebackType, ModuleType
1717
from typing import (
1818
IO,
@@ -25,7 +25,6 @@
2525
Mapping,
2626
NamedTuple,
2727
Optional,
28-
Set,
2928
TextIO,
3029
Tuple,
3130
Type,
@@ -836,14 +835,10 @@ def pop_render_hook(self) -> None:
836835
def __enter__(self) -> "Console":
837836
"""Own context manager to enter buffer context."""
838837
self._enter_buffer()
839-
if self._live:
840-
self._live._lock.acquire()
841838
return self
842839

843840
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
844841
"""Exit buffer context."""
845-
if self._live:
846-
self._live._lock.release()
847842
self._exit_buffer()
848843

849844
def begin_capture(self) -> None:
@@ -1512,9 +1507,8 @@ def control(self, *control: Control) -> None:
15121507
control_codes (str): Control codes, such as those that may move the cursor.
15131508
"""
15141509
if not self.is_dumb_terminal:
1515-
for _control in control:
1516-
self._buffer.append(_control.segment)
1517-
self._check_buffer()
1510+
with self:
1511+
self._buffer.extend(_control.segment for _control in control)
15181512

15191513
def out(
15201514
self,
@@ -1596,7 +1590,7 @@ def print(
15961590
if overflow is None:
15971591
overflow = "ignore"
15981592
crop = False
1599-
1593+
render_hooks = self._render_hooks[:]
16001594
with self:
16011595
renderables = self._collect_renderables(
16021596
objects,
@@ -1607,7 +1601,7 @@ def print(
16071601
markup=markup,
16081602
highlight=highlight,
16091603
)
1610-
for hook in self._render_hooks:
1604+
for hook in render_hooks:
16111605
renderables = hook.process_renderables(renderables)
16121606
render_options = self.options.update(
16131607
justify=justify,
@@ -1864,6 +1858,8 @@ def log(
18641858
if not objects:
18651859
objects = (NewLine(),)
18661860

1861+
render_hooks = self._render_hooks[:]
1862+
18671863
with self:
18681864
renderables = self._collect_renderables(
18691865
objects,
@@ -1898,7 +1894,7 @@ def log(
18981894
link_path=link_path,
18991895
)
19001896
]
1901-
for hook in self._render_hooks:
1897+
for hook in render_hooks:
19021898
renderables = hook.process_renderables(renderables)
19031899
new_segments: List[Segment] = []
19041900
extend = new_segments.extend

rich/live.py

+29-30
Original file line numberDiff line numberDiff line change
@@ -128,38 +128,37 @@ def stop(self) -> None:
128128
with self._lock:
129129
if not self._started:
130130
return
131-
self.console.clear_live()
132131
self._started = False
133-
try:
134-
if self.auto_refresh and self._refresh_thread is not None:
135-
self._refresh_thread.stop()
136-
self._refresh_thread.join()
137-
# allow it to fully render on the last even if overflow
138-
self.vertical_overflow = "visible"
139-
if not self._alt_screen and not self.console.is_jupyter:
140-
self.refresh()
141-
142-
finally:
143-
if self._refresh_thread is not None:
144-
self._refresh_thread = None
145-
146-
self._disable_redirect_io()
147-
self.console.pop_render_hook()
148-
if not self._alt_screen and self.console.is_terminal:
149-
self.console.line()
150-
self.console.show_cursor(True)
151-
if self._alt_screen:
152-
self.console.set_alt_screen(False)
153-
154-
if self.transient and not self._alt_screen:
155-
self.console.control(self._live_render.restore_cursor())
156-
if self.ipy_widget is not None: # pragma: no cover
157-
if self.transient:
158-
self.ipy_widget.close()
159-
else:
160-
# jupyter last refresh must occur after console pop render hook
161-
# i am not sure why this is needed
132+
133+
if self.auto_refresh and self._refresh_thread is not None:
134+
self._refresh_thread.stop()
135+
self._refresh_thread.join()
136+
self._refresh_thread = None
137+
# allow it to fully render on the last even if overflow
138+
self.vertical_overflow = "visible"
139+
with self.console:
140+
try:
141+
if not self._alt_screen and not self.console.is_jupyter:
162142
self.refresh()
143+
finally:
144+
self._disable_redirect_io()
145+
self.console.pop_render_hook()
146+
if not self._alt_screen and self.console.is_terminal:
147+
self.console.line()
148+
self.console.show_cursor(True)
149+
if self._alt_screen:
150+
self.console.set_alt_screen(False)
151+
152+
if self.transient and not self._alt_screen:
153+
self.console.control(self._live_render.restore_cursor())
154+
if self.ipy_widget is not None: # pragma: no cover
155+
if self.transient:
156+
self.ipy_widget.close()
157+
else:
158+
# jupyter last refresh must occur after console pop render hook
159+
# i am not sure why this is needed
160+
self.refresh()
161+
self.console.clear_live()
163162

164163
def __enter__(self) -> "Live":
165164
self.start(refresh=self._renderable is not None)

rich/live_render.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,15 @@ def __rich_console__(
8484
) -> RenderResult:
8585

8686
renderable = self.renderable
87-
_Segment = Segment
8887
style = console.get_style(self.style)
8988
lines = console.render_lines(renderable, options, style=style, pad=False)
90-
shape = _Segment.get_shape(lines)
89+
shape = Segment.get_shape(lines)
9190

9291
_, height = shape
9392
if height > options.size.height:
9493
if self.vertical_overflow == "crop":
9594
lines = lines[: options.size.height]
96-
shape = _Segment.get_shape(lines)
95+
shape = Segment.get_shape(lines)
9796
elif self.vertical_overflow == "ellipsis":
9897
lines = lines[: (options.size.height - 1)]
9998
overflow_text = Text(
@@ -104,10 +103,11 @@ def __rich_console__(
104103
style="live.ellipsis",
105104
)
106105
lines.append(list(console.render(overflow_text)))
107-
shape = _Segment.get_shape(lines)
106+
shape = Segment.get_shape(lines)
108107
self._shape = shape
109108

109+
new_line = Segment.line()
110110
for last, line in loop_last(lines):
111111
yield from line
112112
if not last:
113-
yield _Segment.line()
113+
yield new_line

0 commit comments

Comments
 (0)