prompt-toolkit / prompt-toolkit/python-prompt-toolkit
`patch_stdout` Causing Missing Print Statements on Application Exit
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 10.6k
- Forks
- 815
- PR merge metrics
- No merged PRs in 30d
Description
I noticed some odd behavior regarding patch_stdout when the application is exited, where print statements that are executed close to the application exiting aren't printed. To demonstrate, I modified the examples\prompts\async-prompt.py example to the following:
import asyncio
from prompt_toolkit.patch_stdout import patch_stdout
from prompt_toolkit.shortcuts import PromptSession
session = PromptSession("Say something: ")
do_exit = False
times_finally_called = 0
print_executed = False
async def print_counter(id: int, exit_on: int = -1):
"""
Coroutine that prints counters.
"""
global session, do_exit, times_finally_called
try:
i = 0
while True:
print("Counter %i: %i" % (id, i))
if i == exit_on:
session.app.exit()
do_exit = True
return
i += 1
await asyncio.sleep(1)
except asyncio.CancelledError:
print("Background task %i cancelled." % id)
finally:
print('Background task %i exiting.' % id)
times_finally_called += 1
async def interactive_shell():
"""
Like `interactive_shell`, but doing things manual.
"""
global session, do_exit
# Add background tasks
session.app.create_background_task(print_counter(1))
session.app.create_background_task(print_counter(2, 3))
# Run echo loop. I should probably use AppResult in exit and just use that to exit the loop
# but this works for demonstration
while not do_exit:
try:
result = await session.prompt_async()
print('You said: "{0}"'.format(result))
except (EOFError, KeyboardInterrupt):
return
async def main():
global print_executed
with patch_stdout():
await interactive_shell()
print("Quitting event loop. Bye.")
print_executed = True
if __name__ == "__main__":
try:
from asyncio import run
except ImportError:
print('using run until complete')
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
else:
print('using run')
asyncio.run(main())
print('Finally was actually called %i times' % times_finally_called)
print('Print was executed: %s' % str(print_executed))
Basically, I wanted to force an application (prompt) exit from a coroutine. This example is a bit contrived, however it is less so in more practical applications like exiting the application as a result of an exception.
When running the above example in Python 3.7 and Python 3.8 on a Windows machine, the "Quitting event loop. Bye." message does not display, even though it was clearly executed because the variable assignment to True below the print statement did occur.
This appears to have something to do with how patch_stdout works or how it interacts with application exit, as simply moving that "Quitting event loop" message to outside of the with patch_stdout(): context causes it to display just fine.
I also tested this on Python 3.6, and the behavior is more complicated there. First, the original example was calling asyncio.run_until_complete, which I believe may be an error as that function doesn't exist -- run_until_complete appears to be only a function of event loops, and there is no module-level equivalent. Thus, I modified the code to simply create an event loop and call run_until_complete. However, the behavior here is quite a bit worse -- not only are most of the print statements involving the exit path missing, but the finally block in print_counter appears to be called only once instead of twice. I'm not quite sure what is occurring here, however I did notice that the run function added in 3.7 ensures that asynchronous generators are finalized (which run_until_complete does not), so I'd imagine that may have something to do with it.
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 examples/prompts/async-prompt.py and reproduce the missing output around session.app.exit() under the Python versions and Windows setup described. Then inspect patch_stdout and the application exit path; done means the exit-time print statements and both task finally blocks behave as expected without moving the print outside the patch_stdout context.
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
- 35/100