vxgmichel/aioconsole

`EOFError` when using `input()` after `ainput()`

anhtuan23 opened this issue · 2 comments

Hi, thanks for this very helpful package.

Running this simple script:

import asyncio
from aioconsole.stream import ainput


async def main():
    input1 = await ainput("Input1: ")
    input2 = input("Input2: ")

if __name__ == "__main__":
    asyncio.run(main())

I have the following error appears after the first input:

 % python draft.py
Input1: hello
Input2: Traceback (most recent call last):
  File "/Users/kaestrl/draft.py", line 10, in <module>
    asyncio.run(main())
  File "/Users/kaestrl/miniforge3/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Users/kaestrl/miniforge3/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/Users/kaestrl/draft.py", line 7, in main
    input2 = input("Input2: ")
EOFError

This problem appears on MacOS Monterey 12.1's terminal, but not on Windows 11.

Hi @anhtuan23 and thanks for the report !

The problem here is that the stdin file descriptor needs to be set to non-blocking mode in order to be registered in the asyncio event loop, but the input builtin requires this file descriptor to be in blocking mode. If you were to patch the input() builtin with the following code, your example would work properly:

import os
import sys
import builtins

def input(arg):
    try:
        os.set_blocking(sys.stdin.fileno(), True)
        return builtins.input(arg)
    finally:
        os.set_blocking(sys.stdin.fileno(), False)

However, 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.

I hope that helps :)

Thank you very much @vxgmichel, your code works beautifully.

I have to use blocking input() because my script have an outer "menu" loop that also continuously waiting for input.

It's something like this:

import asyncio
from aioconsole.stream import ainput
import os
import sys
import builtins


def _binput(arg):
    try:
        os.set_blocking(sys.stdin.fileno(), True)
        return builtins.input(arg)
    finally:
        os.set_blocking(sys.stdin.fileno(), False)


async def _job1():
    answer = _binput("Answer: ")
    # answer = await ainput("Answer: ")
    print(f"You answered {answer}")
    while True:
        print("Running job1...")
        await asyncio.sleep(1)


async def main():
    running_task = None

    while True:
        _input = await ainput("Enter job name: ")
        print(f"You entered {_input}")

        if _input == "job1":
            running_task = asyncio.create_task(_job1())

        if _input == "stop":
            try:
                running_task.cancel()
            except Exception as e:
                print(f"Cannot cancel task: {e}")

        if _input == "exit":
            break


if __name__ == "__main__":
    asyncio.run(main())

If ainput() is used in _job1(), answer would never receive user input since the "menu" outer loop would consume it first.

Thanks again for your reply.