prompt-toolkit / prompt-toolkit/python-prompt-toolkit
Context race condition with patch_stdout, ProgressBar and in_terminal
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 10.6k
- Forks
- 815
- PR merge metrics
- No merged PRs in 30d
Description
I am trying to print_formatted_text asynchronously while ProgressBar is running. I tried this:
import asyncio as aio
from prompt_toolkit.application import in_terminal
from prompt_toolkit import print_formatted_text
from prompt_toolkit.shortcuts import ProgressBar
from prompt_toolkit.patch_stdout import patch_stdout
async def log():
await aio.sleep(1)
count = 1
while True:
print_formatted_text(f'log{count}')
count += 1
await aio.sleep(.1)
async def progress():
with ProgressBar() as pb:
for _ in pb(range(100)):
await aio.sleep(.1)
loop.create_task(progress())
if __name__ == '__main__':
with patch_stdout():
loop = aio.get_event_loop()
loop.create_task(log())
loop.create_task(progress())
loop.run_forever()
But print_formatted_text messed up the ProgressBar
log1.0% [==========> ] 9/100 eta [00:09]
log2 ==> 11
log3 ==> 2 8
log4 ==> 4
log5 => 5
...
After some research of old issues and docs i tried to change print_formatted_text(f'log{count}') to
async with in_terminal():
print_formatted_text(f'log{count}')
Everything prints as expected but sometimes throws an error:
Unhandled exception in event loop:
File "C:\Python391\lib\asyncio\events.py", line 80, in _run
self._context.run(self._callback, *self._args)
File "D:\_Projects\python\bot\py\lib\site-packages\prompt_toolkit\eventloop\utils.py", line 77, in schedule
func()
File "D:\_Projects\python\bot\py\lib\site-packages\prompt_toolkit\application\application.py", line 476, in redraw
self._redraw()
File "D:\_Projects\python\bot\py\lib\site-packages\prompt_toolkit\application\application.py", line 543, in _redraw
self.context.run(run_in_context)
Exception cannot enter context: <Context object at 0x00000233BE38AC80> is already entered
I tried to fix this by changing:
https://github.com/prompt-toolkit/python-prompt-toolkit/blob/c8fb6e2e6eae34fb57ac225a09565ba071f59fe5/prompt_toolkit/application/application.py#L542-L543
To:
if self.context is not None:
call_soon_threadsafe(
lambda: self.context.run(run_in_context),
max_postpone_time = self.max_render_postpone_time,
loop=self.loop,
)
And it worked but not sure if this is correct way
Reproducable example
import asyncio as aio
from prompt_toolkit.application import in_terminal
from prompt_toolkit import print_formatted_text
from prompt_toolkit.shortcuts import ProgressBar
from prompt_toolkit.patch_stdout import patch_stdout
async def log():
await aio.sleep(1)
count = 1
while True:
async with in_terminal():
print_formatted_text(f'log{count}')
count += 1
await aio.sleep(.2)
async def progress():
with ProgressBar() as pb:
for _ in pb(range(100)):
await aio.sleep(.1)
loop.create_task(progress())
if __name__ == '__main__':
with patch_stdout():
loop = aio.get_event_loop()
loop.create_task(log())
loop.create_task(progress())
loop.run_forever()
My environment:
- windows 10
- python 3.9.1
- prompt-toolkit 3.0.14
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the reproducible asyncio example and inspect prompt_toolkit/application/application.py around lines 542-543, where the context is entered during redraw. Reproduce the interaction between patch_stdout, ProgressBar, and in_terminal, then verify that redraws no longer raise an already-entered-context error and that progress output remains intact.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100