MagicStack / MagicStack/uvloop

Asynchronously gathering user input seems to break in uvloop but not in default asyncio loop

Abierto
#543 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Lenguaje dominante
Cython
Estrellas
11.9k
Forks
616
Métricas de merge de PR
Sin PR fusionados en 30 d

Descripción

* **uvloop version**: 0.17.0, 0.16.0, 0.15.2
* **Python version**: 3.9, 3.11
* **Platform**: Lenovo T15g Gen 1, Ubuntu 20.04, Kernel 5.15.0-46-generic
* **Can you reproduce the bug with `PYTHONASYNCIODEBUG` in env?**: Yes, though doesn't look like it provides any useful info
* **Does uvloop behave differently from vanilla asyncio? How?**: Asynchronously gathering user input from the terminal doesn't seem to be possible with any method that I am aware of. When not using uvloop, there are two approaches that work fine.

Here is the minimal reproducible example that I am using to try to understand this bug (note requires installing `uvloop` and :
```python
import os
os.environ['PYTHONASYNCIODEBUG'] = '1'

import asyncio
from typing import Tuple, Type, Optional
from types import TracebackType
import traceback
import sys

import uvloop
import aioconsole

async def connect_stdin_stdout() -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]:
"""Returns asyncio-compatible stream readers and writers for stdin and stdout.
Useful for CLI interaction without blocking the asyncio event loop.
"""
loop = asyncio.get_event_loop()

reader = asyncio.StreamReader()
r_protocol = asyncio.StreamReaderProtocol(reader)
await loop.connect_read_pipe(lambda: r_protocol, sys.stdin)

w_transport, w_protocol = await loop.connect_write_pipe(
asyncio.streams.FlowControlMixin, sys.stdout
)
writer = asyncio.StreamWriter(w_transport, w_protocol, reader, loop)

return reader, writer

def _log_exceptions(
exc_type: Type[BaseException],
exc_value: BaseException,
exc_traceback: Optional[TracebackType]
):
traceback_lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
print("---- CUSTOM ERROR REPORT ----")
for line in traceback_lines:
print(line)
print("-----------------------------")

async def main(trigger_exception = False, use_aioconsole = False) -> None:
if use_aioconsole:
stdout = sys.stdout.buffer
user_input = (await aioconsole.ainput(">> ")).encode()
else:
stdin, stdout = await connect_stdin_stdout()
stdout.write(b">> ")
user_input = (await stdin.readline())

if trigger_exception:
raise RuntimeError()

stdout.write(user_input)

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

# Test 2
# uvloop.install()
# asyncio.run(main())

# Test 3
# asyncio.run(main(use_aioconsole=True))

# Test 4
# uvloop.install()
# asyncio.run(main(use_aioconsole=True))

# Test 5
# sys.excepthook = _log_exceptions
# asyncio.run(main(trigger_exception=True, use_aioconsole=True))

# Test 6
# uvloop.install()
# sys.excepthook = _log_exceptions
# asyncio.run(main(trigger_exception=True, use_aioconsole=True))

```

There are two main things that I would like to be able to do, that I cannot find a solution for:
* Gather user input from the terminal asynchronously without using another thread
* Ensure we can still make some pretty error logs when exceptions occur

Both of these tasks work fine with the default asyncio loop, but seem to fail for me with `uvloop`. The two approaches I know are `connect_stdin_stdout` from this [stackoverflow answer](https://stackoverflow.com/questions/64303607/python-asyncio-how-to-read-stdin-and-write-to-stdout), and to use a third party library `aioconsole` (which under the hood does a lot of the same things as the stackoverflow answer).

Are you able to replicate on other systems? It is easy to replicate for me on a variety of python and uvloop installations (noted above).

Is there a workaround I could use to gather user input in a uvloop environment?

-------------------------------
In case replication is hard, here are the outputs for each test on my system

* Test 1
```
>> a
a
```

* Test 2 (note it just hangs forever and never returns)
```
>> a
```

* Test 3
```
>> a
a⏎
```

* Test 4
```
>> a
a⏎
```

* Test 5 (note the "custom error report" formatting)
```
>> a
---- CUSTOM ERROR REPORT ----
Traceback (most recent call last):

File "/home/ethan/mariana/src/python/potomac/node/test_node.py", line 74, in
asyncio.run(main(trigger_exception=True, use_aioconsole=True))

File "/usr/lib/python3.9/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)

File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()

File "/home/ethan/mariana/src/python/potomac/node/test_node.py", line 53, in main
raise RuntimeError()

RuntimeError

-----------------------------
```

* Test 6 (apologies that this is totally useless, I think something is getting swallowed but I'm not sure why)
```
>> a
Error in sys.excepthook:

Original exception was:
```

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Comienza con el reproductor mínimo, especialmente con connect_stdin_stdout(), asyncio.run(), uvloop.install() y la ruta de aioconsole. Ejecuta las seis pruebas indicadas con las versiones de Python, uvloop y Ubuntu mencionadas, y compara uvloop con el bucle asyncio predeterminado. Se considera terminado cuando la entrada del terminal funciona de forma asíncrona y el informe de excepciones personalizado se comporta de manera coherente sin bloquearse ni ocultar el error.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
cli
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Estancado
Claridad
Bastante claro
Aptitud para principiantes
35/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.