aioconsole icon indicating copy to clipboard operation
aioconsole copied to clipboard

Handle non-interactive scripts

Open AGhost-7 opened this issue 2 years ago • 2 comments

Certain programs can call your python program without a stdin. For example when you use docker run without the -i option you can get the following exception when calling a script using create_standard_streams:

Traceback (most recent call last):
  File "/usr/lib/python3.8/asyncio/selector_events.py", line 261, in _add_reader
    key = self._selector.get_key(fd)
  File "/usr/lib/python3.8/selectors.py", line 192, in get_key
    raise KeyError("{!r} is not registered".format(fileobj)) from None
KeyError: '0 is not registered'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.8/asyncio/events.py", line 81, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/lib/python3.8/asyncio/selector_events.py", line 263, in _add_reader
    self._selector.register(fd, selectors.EVENT_READ,
  File "/usr/lib/python3.8/selectors.py", line 359, in register
    self._selector.register(key.fd, poller_events)
PermissionError: [Errno 1] Operation not permitted

AGhost-7 avatar Aug 05 '22 16:08 AGhost-7

@AGhost-7 Thanks for the report !

Interesting, but how would you expect create_standard_streams to behave in this case? Do you still need async access to stdout and stderr even though stdin is not available?

vxgmichel avatar Sep 26 '22 17:09 vxgmichel

In that case I would probably expect to get None for stdout and stderr. Its pretty common that applications will display things to the terminal differently based on whether or not a session is interactive, so it is useful to expose that information. Alternatively you could provide a noop shim object, but I think the better option is the former.

AGhost-7 avatar Sep 30 '22 00:09 AGhost-7

I've encountered the same issue. This exception could also occur when running the program in the background in a script.

Is there any idea how to detect whether stdin is readable? If so, it can actually be fixed outside this library by checking it first before create_standard_streams.

DCsunset avatar Mar 02 '23 17:03 DCsunset

I'm very tempted to use the following fix:

diff --git a/aioconsole/stream.py b/aioconsole/stream.py
index 29d9827..43fbbcb 100644
--- a/aioconsole/stream.py
+++ b/aioconsole/stream.py
@@ -5,6 +5,7 @@ import sys
 import stat
 import weakref
 import asyncio
+import selectors
 from collections import deque
 from threading import Thread
 from concurrent.futures import Future
@@ -25,6 +26,14 @@ def is_pipe_transport_compatible(pipe):
     is_socket = stat.S_ISSOCK(mode)
     if not (is_char or is_fifo or is_socket):
         return False
+    # Fail early when the file descriptor cannot be registered.
+    # This happens with docker containers for instance.
+    # See issue #102: https://github.com/vxgmichel/aioconsole/issues/102
+    try:
+        with selectors.DefaultSelector() as selector:
+            selector.register(fileno, selectors.EVENT_READ | selectors.EVENT_WRITE)
+    except PermissionError:
+        return False
     return True

This way, create_standard_streams would return the fallback objects NonFileStreamReader/NonFileStreamWriter.

Then you would get the following error when trying to await ainput:

Traceback (most recent call last):                                                                                                             
  File "/usr/src/app/script.py", line 21, in <module>                                                                                          
    asyncio.run(main())
  File "/usr/local/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/local/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/usr/src/app/script.py", line 15, in main
    print(await aioconsole.ainput())
  File "/usr/src/app/aioconsole/stream.py", line 281, in ainput
    raise EOFError
EOFError

This is very similar to the behavior with input:

Traceback (most recent call last):                                                                                                             
  File "/usr/src/app/script.py", line 21, in <module>                                                                                          
    asyncio.run(main())
  File "/usr/local/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/local/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/usr/src/app/script.py", line 12, in main
    print(input())
EOFError: EOF when reading a line

Note that stdout.write/stdout.drain would also work as expected in the docker container.

What do you think @DCsunset @AGhost-7 ?

vxgmichel avatar Mar 03 '23 13:03 vxgmichel

Yeah I think it's a good idea! I've also tested the code in your PR and it works without problem

DCsunset avatar Mar 03 '23 19:03 DCsunset

Fixed in #106, available in v0.6.1 :tada:

vxgmichel avatar Mar 04 '23 10:03 vxgmichel