aioconsole
aioconsole copied to clipboard
Can't mix ainput() with input() on Unix
On Linux, trying to use regular blocking input() after a call to ainput() fails with EOFError.
Minimal example:
import asyncio
from aioconsole import ainput
async def main():
await ainput("ainput: ")
input("input: ")
asyncio.run(main())
On Ubuntu 20.04 (tested in WSL), if you run this and enter something at the first prompt (ainput: ), then the input() call fails immediately with EOFError.
On the other hand, this works fine on Windows.
If the input() call is made before ainput(), then there are no problems.
This seems to be due to the fact that ainput() sets the O_NONBLOCK flag in stdin/stdout. Indeed, if this flag is cleared after the call to ainput(), then input() works fine again:
import asyncio
import fcntl
import sys
from aioconsole import ainput
async def main():
await ainput("ainput: ")
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, 0)
input("input: ")
asyncio.run(main())
Maybe this should be done automatically by ainput.
Hi @plammens and thanks for your report!
We already had a similar discussion here: https://github.com/vxgmichel/aioconsole/issues/90#issuecomment-997997618
Still, your suggestion about setting the non-blocking mode only when necessary is interesting, I'll think about it :)
However and as I said in the comment mentioned above, I would advise to simply avoid using input() from an asyncio coroutine as it would block the event loop while waiting for the user input.
Thanks @vxgmichel, sorry hadn't seen #90, it's the same issue!
I also wanted to add that I've found that when using ainput, you shouldn't use regular input but neither regular print, the latter because it risks raising a BlockingIOError if the buffer is full (e.g. if you try to print a lot of stuff) since stdout is in non-blocking mode.
await ainput()
print('a' * 10**7) # BlockingIOError
So currently, if you use ainput once you should always use ainput/aprint and never input/print.
Regarding my suggestion, I realised it might lead to problems if multiple ainputs are being awaited simultaneously (is that even possible?):
- The first
ainputsets non-blocking mode and starts waiting for input - The second
ainputsets non-blocking mode (no-op) and starts waiting for input - User enters input
- The first
ainputreturns, sets blocking mode
Would that mess up the second one, which is still waiting?
Would that mess up the second one, which is still waiting?
Yes it would, so the implementation would have to take care of that. Another solution would be to patch builtins.print and builtins.input to add a warning but patching stdlib is often more annoying than helpful. I'm not sure what to do about this issue :thinking:
This is still causing havoc for us in the form of logging errors:
--- Logging error ---
Traceback (most recent call last):
File "/usr/lib/python3.11/logging/__init__.py", line 1113, in emit
stream.write(msg + self.terminator)
BlockingIOError: [Errno 11] write could not complete without blocking
Call stack:
File "<string>", line 1, in <module>
File "/home/bls/Downloads/code/bbot/bbot/cli.py", line 387, in main
asyncio.run(_main())
File "/usr/lib/python3.11/asyncio/runners.py", line 190, in run
return runner.run(main)
File "/usr/lib/python3.11/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
File "/usr/lib/python3.11/asyncio/base_events.py", line 640, in run_until_complete
self.run_forever()
File "/usr/lib/python3.11/asyncio/base_events.py", line 607, in run_forever
self._run_once()
File "/usr/lib/python3.11/asyncio/base_events.py", line 1922, in _run_once
handle._run()
File "/usr/lib/python3.11/asyncio/events.py", line 80, in _run
self._context.run(self._callback, *self._args)
File "/home/bls/Downloads/code/bbot/bbot/modules/output/human.py", line 40, in handle_event
self.stdout(event_str)
File "/home/bls/Downloads/code/bbot/bbot/modules/base.py", line 1175, in stdout
self.log.stdout(*args, extra={"scan_id": self.scan.id}, **kwargs)
File "/home/bls/Downloads/code/bbot/bbot/core/logger/logger.py", line 83, in logForLevel
self._log(levelNum, message, args, **kwargs)
I understand the need for stdin to be set to non-blocking mode, but is there a way to ensure that doesn't happen to stdout?
Discovered this isn't an issue with aioconsole. The behavior is coming from somwhere inside asyncio. The following code is enough to set stdout to non-blocking mode (note in the code there is no reference to stdout):
reader = asyncio.StreamReader()
protocol = asyncio.StreamReaderProtocol(reader)
await asyncio.get_event_loop().connect_read_pipe(lambda: protocol, sys.stdin)