-
-
Notifications
You must be signed in to change notification settings - Fork 834
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Python 3.7: RuntimeError: read() called while another coroutine is already waiting for incoming data #527
Comments
@iluxonchik posted another example in #382, which I'm reposting here in a runnable form: import asyncio
import httpx
async def print_content_from_url(url, client):
res = await client.get(url)
print(res, url)
async def main():
urls = ['https://example.com', 'https://google.com']*100
async with httpx.AsyncClient() as client:
await asyncio.gather(*(print_content_from_url(url, client) for url in urls))
asyncio.run(main()) This program runs fine on my side, on 3.7.3 and 3.8.0. @iluxonchik Which Python version were you using? |
@PrimozGodec I was able to run your code and reproduce the error you listed (on 3.8.0). From the traceback I noticed that the requests are made using HTTP/2. I forced usage of HTTP/1.1 by passing This is probably somewhat related to #514. |
Interestingly, if I remove the So I turned on TRACE logs and here's what we get: ❯ HTTPX_LOG_LEVEL=trace python debug/read_error_prizmo.py
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='api.garaza.io' port=443)
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='api.garaza.io' port=443)
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='api.garaza.io' port=443))
TRACE [2019-11-17 12:11:58] httpx.config - load_ssl_context verify=True cert=None trust_env=True http_versions=HTTPVersionConfig(['HTTP/1.1', 'HTTP/2'])
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='api.garaza.io' port=443))
TRACE [2019-11-17 12:11:58] httpx.config - load_ssl_context verify=True cert=None trust_env=True http_versions=HTTPVersionConfig(['HTTP/1.1', 'HTTP/2'])
TRACE [2019-11-17 12:11:58] httpx.config - load_verify_locations cafile=/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/certifi/cacert.pem
TRACE [2019-11-17 12:11:58] httpx.config - load_verify_locations cafile=/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/certifi/cacert.pem
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection - start_connect host='api.garaza.io' port=443 timeout=TimeoutConfig(timeout=60)
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection - start_connect host='api.garaza.io' port=443 timeout=TimeoutConfig(timeout=60)
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection - connected http_version='HTTP/2'
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - send_headers stream_id=1 method='POST' target='/image/inception-v3?machine=1&session=1&retry=0' headers=[(b':method', b'POST'), (b':authority', b'api.garaza.io'), (b':scheme', b'https'), (b':path', b'/image/inception-v3?machine=1&session=1&retry=0'), (b'user-agent', b'python-httpx/0.7.7'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'content-type', b'image/jpeg'), (b'content-length', b'30959')]
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=0 event=<RemoteSettingsChanged changed_settings:{ChangedSetting(setting=SettingCodes.MAX_CONCURRENT_STREAMS, original_value=None, new_value=128), ChangedSetting(setting=SettingCodes.INITIAL_WINDOW_SIZE, original_value=65535, new_value=65536), ChangedSetting(setting=SettingCodes.MAX_FRAME_SIZE, original_value=16384, new_value=16777215)}>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=0 event=<WindowUpdated stream_id:0, delta:2147418112>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - end_stream stream_id=1
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection - connected http_version='HTTP/2'
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - send_headers stream_id=1 method='POST' target='/image/inception-v3?machine=1&session=1&retry=0' headers=[(b':method', b'POST'), (b':authority', b'api.garaza.io'), (b':scheme', b'https'), (b':path', b'/image/inception-v3?machine=1&session=1&retry=0'), (b'user-agent', b'python-httpx/0.7.7'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'content-type', b'image/jpeg'), (b'content-length', b'30959')]
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=0 event=<RemoteSettingsChanged changed_settings:{ChangedSetting(setting=SettingCodes.MAX_CONCURRENT_STREAMS, original_value=None, new_value=128), ChangedSetting(setting=SettingCodes.INITIAL_WINDOW_SIZE, original_value=65535, new_value=65536), ChangedSetting(setting=SettingCodes.MAX_FRAME_SIZE, original_value=16384, new_value=16777215)}>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=0 event=<WindowUpdated stream_id:0, delta:2147418112>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - end_stream stream_id=1
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=0 event=<SettingsAcknowledged changed_settings:{}>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<WindowUpdated stream_id:1, delta:2147418111>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=0 event=<SettingsAcknowledged changed_settings:{}>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<WindowUpdated stream_id:1, delta:2147418111>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<ResponseReceived stream_id:1, headers:[(b':status', b'200'), (b'server', b'nginx/1.13.5'), (b'date', b'Sun, 17 Nov 2019 11:11:58 GMT'), (b'content-type', b'application/json'), (b'strict-transport-security', b'max-age=15724800; includeSubDomains;'), (b'content-encoding', b'gzip')]>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:2006, data:1f8b08000000000000034c974d8e64450c84efc2>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1940, data:54996d92eb2610453734492101022d28fbdf42ce>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1894, data:54da4996a4381004d00bf502100271ff8bb57d97>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1849, data:64db4992e3380c05d00bf5c2a2e6fb5facf13e28>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1861, data:5cdc59d69c2a1405e00965dda52828f39fd8e5db>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<ResponseReceived stream_id:1, headers:[(b':status', b'200'), (b'server', b'nginx/1.13.5'), (b'date', b'Sun, 17 Nov 2019 11:11:58 GMT'), (b'content-type', b'application/json'), (b'strict-transport-security', b'max-age=15724800; includeSubDomains;'), (b'content-encoding', b'gzip')]>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:2006, data:1f8b08000000000000034c974d8e64450c84efc2>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1940, data:54996d92eb2610453734492101022d28fbdf42ce>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1894, data:54da4996a4381004d00bf502100271ff8bb57d97>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1849, data:64db4992e3380c05d00bf5c2a2e6fb5facf13e28>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1861, data:5cdc59d69c2a1405e00965dda52828f39fd8e5db>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1861, data:54dc59b2a4380c05d00d755430d80cfbdf58fb5c>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1880, data:54dd5992e3380c04d00b755458bb74ff8b0d5f02>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1862, data:5cdd5996e3361044d11df99000c7fd6fccba9109>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1259, data:4cdd4992e3380c05d07d9fa516d44c1da68e5277>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:10, data:0300b2290cf3398a0000>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<StreamEnded stream_id:1>
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='api.garaza.io' port=443))
DEBUG [2019-11-17 12:11:58] httpx.client - HTTP Request: POST https://api.garaza.io/image/inception-v3?machine=1&session=1&retry=0 "HTTP/2 200 OK"
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1861, data:54dc59b2a4380c05d00d755430d80cfbdf58fb5c>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1880, data:54dd5992e3380c04d00b755458bb74ff8b0d5f02>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1862, data:5cdd5996e3361044d11df99000c7fd6fccba9109>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:1259, data:4cdd4992e3380c05d07d9fa516d44c1da68e5277>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:10, data:0300b2290cf3398a0000>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=1 event=<StreamEnded stream_id:1>
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='api.garaza.io' port=443))
DEBUG [2019-11-17 12:11:58] httpx.client - HTTP Request: POST https://api.garaza.io/image/inception-v3?machine=1&session=1&retry=0 "HTTP/2 200 OK"
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='api.garaza.io' port=443)
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection_pool - reuse_connection connection=HTTPConnection(origin=Origin(scheme='https' host='api.garaza.io' port=443))
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - send_headers stream_id=3 method='POST' target='/image/inception-v3?machine=1&session=1&retry=0' headers=[(b':method', b'POST'), (b':authority', b'api.garaza.io'), (b':scheme', b'https'), (b':path', b'/image/inception-v3?machine=1&session=1&retry=0'), (b'user-agent', b'python-httpx/0.7.7'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'content-type', b'image/jpeg'), (b'content-length', b'30959')]
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='api.garaza.io' port=443)
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection_pool - reuse_connection connection=HTTPConnection(origin=Origin(scheme='https' host='api.garaza.io' port=443))
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - send_headers stream_id=5 method='POST' target='/image/inception-v3?machine=1&session=1&retry=0' headers=[(b':method', b'POST'), (b':authority', b'api.garaza.io'), (b':scheme', b'https'), (b':path', b'/image/inception-v3?machine=1&session=1&retry=0'), (b'user-agent', b'python-httpx/0.7.7'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'content-type', b'image/jpeg'), (b'content-length', b'30959')]
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - end_stream stream_id=3
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - end_stream stream_id=5
TRACE [2019-11-17 12:11:58] httpx.dispatch.connection - close_connection
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=3 event=<WindowUpdated stream_id:3, delta:2147418111>
TRACE [2019-11-17 12:11:58] httpx.dispatch.http2 - receive_event stream_id=5 event=<WindowUpdated stream_id:5, delta:2147418111>
Traceback (most recent call last):
File "debug/read_error_prizmo.py", line 53, in <module>
asyncio.get_event_loop().run_until_complete(cl.embedd_batch())
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/asyncio/base_events.py", line 608, in run_until_complete
return future.result()
File "debug/read_error_prizmo.py", line 24, in embedd_batch
embeddings = await asyncio.gather(*requests)
File "debug/read_error_prizmo.py", line 40, in _send_to_server
emb = await self._send_request(client, im, url)
File "debug/read_error_prizmo.py", line 46, in _send_request
response = await client.post(url, headers=headers, data=image)
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/client.py", line 477, in post
return await self.request(
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/client.py", line 626, in request
response = await self.send(
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/client.py", line 650, in send
return await self._get_response(
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/client.py", line 273, in _get_response
return await get_response(request)
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/middleware/redirect.py", line 31, in __call__
response = await get_response(request)
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/client.py", line 227, in get_response
response = await dispatch.send(
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/dispatch/connection_pool.py", line 126, in send
raise exc
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/dispatch/connection_pool.py", line 120, in send
response = await connection.send(
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/dispatch/connection.py", line 62, in send
response = await self.h2_connection.send(request, timeout=timeout)
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/dispatch/http2.py", line 57, in send
status_code, headers = await self.receive_response(stream_id, timeout)
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/dispatch/http2.py", line 176, in receive_response
event = await self.receive_event(stream_id, timeout)
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/dispatch/http2.py", line 211, in receive_event
data = await self.stream.read(self.READ_NUM_BYTES, timeout, flag=flag)
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/site-packages/httpx/concurrency/asyncio.py", line 114, in read
data = await asyncio.wait_for(self.stream_reader.read(n), read_timeout)
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/asyncio/tasks.py", line 483, in wait_for
return fut.result()
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/asyncio/streams.py", line 701, in read
await self._wait_for_data('read')
File "/Users/florimond/.pyenv/versions/3.8.0/lib/python3.8/asyncio/streams.py", line 520, in _wait_for_data
raise RuntimeError(
RuntimeError: read() called while another coroutine is already waiting for incoming data It's definitely a race condition. I looked at the code, and roughly what we're doing is this: while not has_event():
data = await stream.read(...) # (A)
...
data_to_send = ...
await stream.write(data_to_send) # (B) From the logs, it seems that streams (as in HTTP/2 streams) 3 and 5 both got to the same point — they received an event, and are going to reach point B). Here's what I think the event loop is doing:
So, I guess the solution would be to put locks around all |
@florimondmanca I'm using Python 3.8 |
Here is the full stack trace:
So the origin of the issue seems to be here, since the Perhaps this is some sort of a race condition? Or something OS dependent? I'm running macOS Catalina. |
@iluxonchik Thanks for the detailed debugging material. As I wrote in a previous comment this does seem to be a race condition caused by HTTP/2 streams accessing the same asyncio stream. I pushed a fix in #535; if you'd like to check, you can install the patched version: pip install "git+https://github.com/encode/httpx.git@fix/concurrent-read" |
@florimondmanca I didn't read everything above 😅 Yes, I can confirm that it's fixed! However, now I have different issue (which randomly existed in the previous version as well). If you specify an
However, you seem to already be aware of this issue, so I won't be discussing this further here. By the way, if I reduce the timeout to something like
Again, I don't have time to investigate this further now, otherwise I would have posted something more complete. |
The read() error seems to be solved. I already go more responses when I run code from my example above. With the same script (code in an issue description) now I get (python 3.7):
|
Hmm, probably a similar problem but on the write side. I wasn’t able to naively reproduce it in a test for #535, but we could see if a write lock solves it. |
@florimondmanca I tested adding the lock around write and it solves the issue. :) Can you add that in #535? I also discovered another issue that is not directly connected with this one. So I prepared a pull request with a fix #540. |
After both bugs are fixed I am able to get at least 100 responses (which is much more than before), but then everything stops. There are no new
|
Using @florimondmanca's example I'm most of the time hitting the SSL error which seems the same as the one fixed by @JayH5. This happens on Python 3.8.0 and 3.7.4, both of which mentioned as fixed upstream in a similar aiohttp issue. Sometimes though I do get the |
@PrimozGodec I managed to reproduce the Also, I don't have the SSL errors @yeraydiazdiaz is experiencing. |
Should I open another issue about this? I also cannot reproduce this issue otherwise than with my script on images. |
I will check the server if it reports anything. |
As we already discussed with @florimondmanca in #382, I am reporting this issue here.
I get the following error:
RuntimeError: read() called while another coroutine is already waiting for incoming data
when I am sending images to the server. As you can see from the code it is implemented that only max 100 requests are sent to the server at the same time. I know there could be lazy loading used but the limitation is made custom because before we send images to the server we check whether they are in the local cache or not. The limit of 100 is set that not all images are loaded in the memory at the same time (there can be also 10000 images).Here is the trace:
The minimum example that reproduces the error is here:
All advice about this error is welcome. I tried to debug this error by myself but was not successful.
The pickle file required in the code snippet is here:
image.pkl.zip
The text was updated successfully, but these errors were encountered: