Open Kludex opened 11 months ago
This seems to be the same issue as in #2152
I've confirmed it also fails with Python 3.12, and it doesn't reproduce with Hypercorn.
It only reproduces with asyncio
.
@graingert Do you know whose's fault is it here?
@Kludex
Hello, I am not reproduce on windows 10 with python3.10.9 & uvicorn==0.22.0 & requests==2.31.0
I use pip install uvicorn==0.22.0 requests==2.31.0
And I reproduce this on Linux ubuntu 6.5.0-35-generic #35~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 7 09:00:52 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
, and fix with pip install uvicorn[standard]==0.22.0
updated: 2024-06-09
env
- OS: Ubuntu 22.04
- Python: 3.7, 3.8, 3.10 I can reproduce this for above three python versions and fixed with
pip install uvloop
And I found that It can reproduce with POST body's payload length > 65536 when second request, here new client.py We just make payload length start with 65531import time import requests
print("") print("Starting...")
def main(payload_len):
huge_payload = "a" * payload_len
# Send request each 1 second reusing single connection (without connection reuse it works well)
with requests.Session() as session:
# while True:
for i in range(2):
try:
response = session.post('http://127.0.0.1:8002/test', huge_payload)
print(f"payload_len:{payload_len}", response.text) # server will be responding with either 'OK' or 'FAIL'
# In case if server reports 'FAIL' - we reproduced the issue and are OK to exit
if response.text == "FAIL":
break
except KeyboardInterrupt:
break
except BaseException as ex:
print(str(ex))
time.sleep(0.1)
if name == "main": payload_len = 65531 for n in range(payload_len, payload_len + 10): main(n)
### why 65536(64KB)?
I grep 65536 in uvicorn code, found that:
When body payload greater than 65536, it calls `pause_reading` & `resume_reading`
When calls `pause_reading`, something broken
```python
# uvicorn/protocols/http/flow_control.py
# Line 15
HIGH_WATER_LIMIT = 65536
# uvicorn/protocols/http/h11_impl.py
# Line 280
def handle_events(self) -> None:
elif event_type is h11.Data:
if self.conn.our_state is h11.DONE:
continue
print(f"{id(self)=} {len(event.data)=}")
self.cycle.body += event.data
if len(self.cycle.body) > HIGH_WATER_LIMIT:
self.flow.pause_reading()
self.cycle.message_event.set()
asyncio
?when calls self.flow.pause_reading()
:
self.flow.pause_reading()
self._transport.pause_reading()
Know that at Linux, asyncio
use _UnixSelectorEventLoop
and _SelectorSocketTransport
And I let _SelectorSocketTransport. pause_reading
& _SelectorSocketTransport.resume_reading
do nothing, it fixed.
More than that, I remove self._loop._remove_reader(self._sock_fd)
and self._add_reader(self._sock_fd, self._read_ready)
in above _SelectorSocketTransport
functions, it fixed
# asyncio/select_events.py
# Line 802-816
def pause_reading(self):
if self._closing or self._paused:
return
self._paused = True
#self._loop._remove_reader(self._sock_fd)
if self._loop.get_debug():
logger.debug("%r pauses reading", self)
def resume_reading(self):
if self._closing or not self._paused:
return
self._paused = False
#self._add_reader(self._sock_fd, self._read_ready)
if self._loop.get_debug():
logger.debug("%r resumes reading", self)
I don't know why this fix, anyone can tell?
update: 2024-06-11
env
- OS: Ubuntu 22.04
- Python: 3.8
- aiohttp: 3.6.2
- requests: 2.31.0 It will not reproduce when not use
requests.Session
. And aiohttp will fail some time(50/1000)import time import requests
print("") print("Starting...")
def main(payload_len):
huge_payload = "a" * payload_len
# Send request each 1 second reusing single connection (without connection reuse it works well)
for i in range(2):
try:
response = requests.post('http://127.0.0.1:8002/test', huge_payload)
print(f"payload_len:{payload_len}",
response.text) # server will be responding with either 'OK' or 'FAIL'
# In case if server reports 'FAIL' - we reproduced the issue and are OK to exit
if response.text == "FAIL":
break
except KeyboardInterrupt:
break
except BaseException as ex:
print(str(ex))
time.sleep(0)
if name == "main": payload_len = 65537 for n in range(payload_len, payload_len + 1000): main(n)
@graingert Do you know whose's fault is it here?
I confirm the behavior in a Mac m1 pro.
server output
INFO: Started server process [96270]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8002 (Press CTRL+C to quit)
(1) 43946b83-8b27-44f4-9d66-dcdd316a08ad
(2) 43946b83-8b27-44f4-9d66-dcdd316a08ad
(3) 43946b83-8b27-44f4-9d66-dcdd316a08ad
(4) 43946b83-8b27-44f4-9d66-dcdd316a08ad
UNEXPECTED request_id value: 43946b83-8b27-44f4-9d66-dcdd316a08ad
(1) 96a1ecbb-6e16-4883-a108-dff0502f5000
(2) 96a1ecbb-6e16-4883-a108-dff0502f5000
(3) 96a1ecbb-6e16-4883-a108-dff0502f5000
(4) 96a1ecbb-6e16-4883-a108-dff0502f5000
client output
Starting...
OK
FAIL
Discussed in https://github.com/encode/uvicorn/discussions/2044