MagicStack / MagicStack/uvloop
Asynchronously gathering user input seems to break in uvloop but not in default asyncio loop
Nessuno ha ancora preso questa issue.
- Lingua principale
- Cython
- Stelle
- 11.9k
- Fork
- 616
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
* **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:
```
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Direzione di ricerca
Inizia con il riproduttore minimo, in particolare connect_stdin_stdout(), asyncio.run(), uvloop.install() e il percorso aioconsole. Esegui i sei test elencati con le versioni indicate di Python, uvloop e Ubuntu, quindi confronta uvloop con il loop asyncio predefinito. Il lavoro è completato quando l’input del terminale funziona in modo asincrono e il report delle eccezioni personalizzato si comporta in modo coerente, senza bloccarsi né nascondere l’errore.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- python
- Ambito
- cli
- Tipo di issue
- Bug
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Stato di attività
- Ferma
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 35/100