Repository navigation
How to make a connection never timeout #411
Copy link
Copy link
Closed
Labels
questionFurther information is requestedFurther information is requested
Description
Problem
I use httpx to request a kubernetes watch api. The api is based on HTTP chunked transfer encoding. When the server generates a new pod message, it will send to client.
Simplified code is as follows
import httpx
resp = httpx.get('http://127.0.0.1:18081/api/v1/watch/pods', stream=True)
for chunk in resp.stream():
passAfter 5 second, I get a ReadTimeout error
Traceback (most recent call last):
File "t.py", line 4, in <module>
for line in resp.stream():
File "/usr/local/lib/python3.6/site-packages/httpx/models.py", line 1077, in stream
for chunk in self.raw():
File "/usr/local/lib/python3.6/site-packages/httpx/models.py", line 1105, in raw
for part in self._raw_stream:
File "/usr/local/lib/python3.6/site-packages/httpx/concurrency/base.py", line 163, in iterate
yield self.run(async_iterator.__anext__)
File "/usr/local/lib/python3.6/site-packages/httpx/concurrency/asyncio.py", line 261, in run
return self.loop.run_until_complete(coroutine(*args, **kwargs))
File "/usr/lib64/python3.6/asyncio/base_events.py", line 484, in run_until_complete
return future.result()
File "/usr/local/lib/python3.6/site-packages/httpx/dispatch/http11.py", line 152, in _receive_response_data
event = await self._receive_event(timeout)
File "/usr/local/lib/python3.6/site-packages/httpx/dispatch/http11.py", line 174, in _receive_event
self.READ_NUM_BYTES, timeout, flag=self.timeout_flag
File "/usr/local/lib/python3.6/site-packages/httpx/concurrency/asyncio.py", line 83, in read
raise ReadTimeout() from None
httpx.exceptions.ReadTimeout
I change the timeout to 60 sec, and I will get RemoteProtocolError
Traceback (most recent call last):
File "t.py", line 4, in <module>
for chunk in resp.stream():
File "/usr/local/lib/python3.6/site-packages/httpx/models.py", line 1077, in stream
for chunk in self.raw():
File "/usr/local/lib/python3.6/site-packages/httpx/models.py", line 1105, in raw
for part in self._raw_stream:
File "/usr/local/lib/python3.6/site-packages/httpx/concurrency/base.py", line 163, in iterate
yield self.run(async_iterator.__anext__)
File "/usr/local/lib/python3.6/site-packages/httpx/concurrency/asyncio.py", line 261, in run
return self.loop.run_until_complete(coroutine(*args, **kwargs))
File "/usr/lib64/python3.6/asyncio/base_events.py", line 484, in run_until_complete
return future.result()
File "/usr/local/lib/python3.6/site-packages/httpx/dispatch/http11.py", line 152, in _receive_response_data
event = await self._receive_event(timeout)
File "/usr/local/lib/python3.6/site-packages/httpx/dispatch/http11.py", line 164, in _receive_event
event = self.h11_state.next_event()
File "/usr/local/lib/python3.6/site-packages/h11/_connection.py", line 420, in next_event
event = self._extract_next_receive_event()
File "/usr/local/lib/python3.6/site-packages/h11/_connection.py", line 369, in _extract_next_receive_event
event = self._reader.read_eof()
File "/usr/local/lib/python3.6/site-packages/h11/_readers.py", line 170, in read_eof
"peer closed connection without sending complete message body "
h11._util.RemoteProtocolError: peer closed connection without sending complete message body (incomplete chunked read)
Environment
- Python 3.6.3
- httpx 0.7.4
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
questionFurther information is requestedFurther information is requested
Activity
thebigmunch commentedon Sep 29, 2019
This is a difference coming from
requestsI ran into as well.httpxsets a default timeout, so you'll have to addtimeout=Noneto your request calls or client instance (if used) if you want no timeout.It would probably be nice to add to the Timeouts docs as well as the requests compatibility docs when they get fleshed out. I have a feeling this one will come up a fair bit.
Hanaasagi commentedon Sep 29, 2019
Thanks for the quick reply.
timeout=Noneis the default behavior ofhttpx.gethttpx/httpx/api.py
Line 68 in 5ced56b
And I see if the
timeoutisNone,TCPStream.readwill change it toDEFAULT_TIMEOUT_CONFIGhttpx/httpx/concurrency/asyncio/backend.py
Lines 63 to 67 in 5ced56b
I don't find some way to control this behavior.
thebigmunch commentedon Sep 29, 2019
Ah, this seems like it might be a bug. Setting
timeout=Noneon a client instance works properly. Settingtimeout=Noneon any of therequestcalls, regardless if its through the top-level API or through a client method uses the default timeout.Edit: That is, if you used:
you should get the behavior you want for now.
florimondmanca commentedon Sep 29, 2019
There are actually three kinds of timeouts you can control in HTTPX: connect, read, and write. Unfortunately, the documentation doesn't show this ability to fine-tune timeouts yet (
TimeoutConfigisn't even documented), and I agree it's something we need to address.For your use case, it seems what you want is to modify the read timeout. (That way, you'll still timeout when connecting or when sending data, which is good because those are unexpected situations.)
The following should work:
Unfortunately there's no way to override only one of the timeouts while keeping default values for the others, so you need to explicitly set the connect and write timeouts. I reckon what we'd need is something like:
but that doesn't exist yet.
Also — according to this SO thread you should expect the connection to drop anyway, so a long timeout along with an infinite loop that reconnects when losing the connection to the watch endpoint might be preferable:
florimondmanca commentedon Sep 29, 2019
I'm going to ping @tomchristie on this one — it might be the intended behavior for the high-level API. I think the rationale is that users should use a client for anything advanced, and disabling timeouts surely seems like an advanced action to make.
Hanaasagi commentedon Sep 29, 2019
Replace
NonewithTimeoutConfig(connect_timeout=5, read_timeout=None, write_timeout=5)and it works. Thanks!thebigmunch commentedon Sep 29, 2019
I don't think disabling timeouts should be considered anything more advanced than setting a different timeout. I'm certainly fine with there being a default timeout. But if a user can simply say
httpx.get(..., timeout=10), thenhttpx.get(..., timeout=Noneshould just work. I certainly wouldn't expect httpx to just flat-out ignore what is a perfectly valid input elsewhere (i.e. Client instances).Edit: Implementation-wise, setting defaults on the methods and API functions might be a nicer way to do it. And
Noneinput should be converted to aTimeoutConfigjust like a number input, shouldn't it?Hanaasagi commentedon Sep 29, 2019
A little confused about the meaning of
None. In some api,timeout=Nonemeans using the defaulttimeout, likehttpx/httpx/dispatch/connection.py
Lines 69 to 76 in 315a18b
But in
Clientinitialization, it means not set the timeout.thebigmunch commentedon Sep 29, 2019
That's the point I've brought up in a couple of my comments. Worse is:
also results in the call using the default timeout config instead of
None. Which surely shouldn't be the case as we're using the "advanced" way of creating a client ourselves and setting atimeouttoNone, we just want no timeout for that specific call in this example. But it's not possible without manually constructing aTimeoutConfigwhich isn't necessary for any other value a user might be expected to give.florimondmanca commentedon Sep 29, 2019
Hmm, yeah, seems like we should review the consistency of timeout behaviors across the various request entrypoints.
Right now, disabling timeouts via
timeout=httpx.TimeoutConfig()should work in all cases.thebigmunch commentedon Sep 29, 2019
Yeah, I forgot to qualify my statement in the previous comment. It's now qualified : P
thebigmunch commentedon Sep 29, 2019
And just so it has its own comment:
As @florimondmanca pointed out, if you set one of the specific timeout parameters, you would have to set the others if you didn't want them to be None. httpx should probably use the default timeout config values in these cases.
lovelydinosaur commentedon Sep 30, 2019
Certainly the behavior of
Client(timeout=...)and.get(timeout=...)ought to match.We can either:
timeout=Nonebe used to indicate that no timeouts should be used. Switch the.getdefault value to a sential value such asUSE_CLIENT_DEFAULTorUNSET.timeout=Falsebe used to indicate that no timeouts should be used. Keep the.getdefault value asNone(Tho explicitly settingtimeout=Nonewill still have unexpected behavior, and we can't detect the difference between that and an "unset" value.)thebigmunch commentedon Sep 30, 2019
I definitely prefer the first option.
timeout=Falsedoesn't really make sense when read. Andtimeout=Noneis how it's spelled in requests.florimondmanca commentedon Sep 30, 2019
I’m more inclined towards timeout=None with a sentinel object (to be used whenever the timeout config is replaced by the default config, eg in Stream.read) as well.
lovelydinosaur commentedon Sep 30, 2019
Fine with that too, yup.
florimondmanca commentedon Oct 3, 2019
Opened #433 based on the discussions here. I think the original issue here was dealt with, so I'm closing this one!
luckydonald commentedon Oct 7, 2020
Note,
TimeoutConfigis now simply calledTimeout.ialvata commentedon Oct 19, 2022
Also,
it seems that now, either you define a value for the 1st positional argument, and possibly some other kwarg, or you must define values for all 4 kwargs (they also changed the name):
httpx.Timeout(connect=5, read=None, write=5, pool = 5)is the same as
httpx.Timeout(5, read=None)