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.