MagicStack / MagicStack/uvloop

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

Đang mở
#543 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Ngôn ngữ chính
Cython
Star
11.9k
Fork
616
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Mô tả

* **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:
```

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với reproducer tối thiểu, đặc biệt là connect_stdin_stdout(), asyncio.run(), uvloop.install() và đường dẫn aioconsole. Chạy sáu bài kiểm thử được liệt kê trên các phiên bản Python, uvloop và Ubuntu được báo cáo, sau đó so sánh uvloop với vòng lặp asyncio mặc định. Hoàn tất khi đầu vào từ terminal hoạt động bất đồng bộ và báo cáo ngoại lệ tùy chỉnh hoạt động nhất quán mà không bị treo hoặc nuốt lỗi.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
cli
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
35/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.