Skip to content

Commit 38360f9

Browse files
committed
refactor timeout support
1 parent 96c8190 commit 38360f9

17 files changed

+417
-480
lines changed

aiohttp/client.py

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
from . import client_exceptions, client_reqrep, hdrs, http
1616
from .client_exceptions import * # noqa
1717
from .client_exceptions import (ClientError, ClientOSError,
18-
ClientResponseError, WSServerHandshakeError)
18+
ClientResponseError, ServerTimeoutError,
19+
WSServerHandshakeError)
1920
from .client_reqrep import * # noqa
2021
from .client_reqrep import ClientRequest, ClientResponse
2122
from .client_ws import ClientWebSocketResponse
2223
from .connector import * # noqa
2324
from .connector import TCPConnector
2425
from .cookiejar import CookieJar
25-
from .helpers import PY_35, TimeService, noop
26+
from .helpers import PY_35, CeilTimeout, TimeoutHandle, noop
2627
from .http import WS_KEY, WebSocketReader, WebSocketWriter
2728
from .streams import FlowControlDataQueue
2829

@@ -48,8 +49,8 @@ def __init__(self, *, connector=None, loop=None, cookies=None,
4849
response_class=ClientResponse,
4950
ws_response_class=ClientWebSocketResponse,
5051
version=http.HttpVersion11,
51-
cookie_jar=None, read_timeout=None, time_service=None,
52-
connector_owner=True):
52+
cookie_jar=None, connector_owner=True,
53+
read_timeout=None, conn_timeout=None):
5354

5455
implicit_loop = False
5556
if loop is None:
@@ -93,6 +94,7 @@ def __init__(self, *, connector=None, loop=None, cookies=None,
9394
self._default_auth = auth
9495
self._version = version
9596
self._read_timeout = read_timeout
97+
self._conn_timeout = conn_timeout
9698

9799
# Convert to list of tuples
98100
if headers:
@@ -110,12 +112,6 @@ def __init__(self, *, connector=None, loop=None, cookies=None,
110112
self._response_class = response_class
111113
self._ws_response_class = ws_response_class
112114

113-
self._time_service_owner = time_service is None
114-
if time_service is None:
115-
time_service = TimeService(self._loop)
116-
117-
self._time_service = time_service
118-
119115
def __del__(self, _warnings=warnings):
120116
if not self.closed:
121117
self.close()
@@ -128,10 +124,6 @@ def __del__(self, _warnings=warnings):
128124
context['source_traceback'] = self._source_traceback
129125
self._loop.call_exception_handler(context)
130126

131-
@property
132-
def time_service(self):
133-
return self._time_service
134-
135127
def request(self, method, url, **kwargs):
136128
"""Perform HTTP request."""
137129
return _RequestContextManager(self._request(method, url, **kwargs))
@@ -195,22 +187,16 @@ def _request(self, method, url, *,
195187
if proxy is not None:
196188
proxy = URL(proxy)
197189

198-
# request timeout
199-
if timeout is None:
200-
timeout = self._read_timeout
201-
if timeout is None:
202-
timeout = self._connector.conn_timeout
203-
elif self._connector.conn_timeout is not None:
204-
timeout = max(timeout, self._connector.conn_timeout)
205-
206190
# timeout is cumulative for all request operations
207191
# (request, redirects, responses, data consuming)
208-
timer = self._time_service.timeout(timeout)
192+
tm = TimeoutHandle(
193+
self._loop, timeout if timeout is not None else self._read_timeout)
194+
handle = tm.start()
209195

196+
timer = tm.timer()
210197
with timer:
211198
while True:
212199
url = URL(url).with_fragment(None)
213-
214200
cookies = self._cookie_jar.filter_cookies(url)
215201

216202
req = self._request_class(
@@ -221,7 +207,14 @@ def _request(self, method, url, *,
221207
loop=self._loop, response_class=self._response_class,
222208
proxy=proxy, proxy_auth=proxy_auth, timer=timer)
223209

224-
conn = yield from self._connector.connect(req)
210+
# connection timeout
211+
try:
212+
with CeilTimeout(self._conn_timeout, loop=self._loop):
213+
conn = yield from self._connector.connect(req)
214+
except asyncio.TimeoutError as exc:
215+
raise ServerTimeoutError(
216+
'Connection timeout to host {0}'.format(url)) from exc
217+
225218
conn.writer.set_tcp_nodelay(True)
226219
try:
227220
resp = req.send(conn)
@@ -285,6 +278,13 @@ def _request(self, method, url, *,
285278

286279
break
287280

281+
# register connection
282+
if handle is not None:
283+
if resp.connection is not None:
284+
resp.connection.add_callback(handle.cancel)
285+
else:
286+
handle.cancel()
287+
288288
resp._history = tuple(history)
289289
return resp
290290

@@ -417,7 +417,6 @@ def _ws_connect(self, url, *,
417417
autoclose,
418418
autoping,
419419
self._loop,
420-
time_service=self.time_service,
421420
receive_timeout=receive_timeout,
422421
heartbeat=heartbeat)
423422

@@ -496,9 +495,6 @@ def close(self):
496495
self._connector.close()
497496
self._connector = None
498497

499-
if self._time_service_owner:
500-
self._time_service.close()
501-
502498
return noop()
503499

504500
@property

aiohttp/client_reqrep.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313

1414
from . import hdrs, helpers, http, payload
1515
from .formdata import FormData
16-
from .helpers import (PY_35, HeadersMixin, SimpleCookie,
17-
_TimeServiceTimeoutNoop, noop)
16+
from .helpers import PY_35, HeadersMixin, SimpleCookie, TimerNoop, noop
1817
from .http import HttpMessage
1918
from .log import client_logger
2019
from .streams import FlowControlStreamReader
@@ -81,7 +80,7 @@ def __init__(self, method, url, *,
8180
self.compress = compress
8281
self.loop = loop
8382
self.response_class = response_class or ClientResponse
84-
self._timer = timer if timer is not None else _TimeServiceTimeoutNoop()
83+
self._timer = timer if timer is not None else TimerNoop()
8584

8685
if loop.get_debug():
8786
self._source_traceback = traceback.extract_stack(sys._getframe(1))
@@ -448,15 +447,16 @@ def __init__(self, method, url, *,
448447
assert isinstance(url, URL)
449448

450449
self.method = method
450+
self.headers = None
451+
self.cookies = SimpleCookie()
452+
451453
self._url = url
452454
self._content = None
453455
self._writer = writer
454456
self._continue = continue100
455457
self._closed = True
456458
self._history = ()
457-
self.headers = None
458-
self._timer = timer if timer is not None else _TimeServiceTimeoutNoop()
459-
self.cookies = SimpleCookie()
459+
self._timer = timer if timer is not None else TimerNoop()
460460

461461
@property
462462
def url(self):
@@ -470,8 +470,6 @@ def url_obj(self):
470470

471471
@property
472472
def host(self):
473-
warnings.warn(
474-
"Deprecated, use .url.host", DeprecationWarning, stacklevel=2)
475473
return self._url.host
476474

477475
@property

aiohttp/client_ws.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import json
55

66
from .client_exceptions import ClientError
7-
from .helpers import PY_35, PY_352, create_future
7+
from .helpers import PY_35, PY_352, Timeout, call_later, create_future
88
from .http import (WS_CLOSED_MESSAGE, WS_CLOSING_MESSAGE,
99
WebSocketError, WSMessage, WSMsgType)
1010

@@ -13,15 +13,13 @@ class ClientWebSocketResponse:
1313

1414
def __init__(self, reader, writer, protocol,
1515
response, timeout, autoclose, autoping, loop, *,
16-
time_service=None,
1716
receive_timeout=None, heartbeat=None):
1817
self._response = response
1918
self._conn = response.connection
2019

2120
self._writer = writer
2221
self._reader = reader
2322
self._protocol = protocol
24-
self._time_service = time_service
2523
self._closed = False
2624
self._closing = False
2725
self._close_code = None
@@ -31,6 +29,8 @@ def __init__(self, reader, writer, protocol,
3129
self._autoping = autoping
3230
self._heartbeat = heartbeat
3331
self._heartbeat_cb = None
32+
if heartbeat is not None:
33+
self._pong_heartbeat = heartbeat/2.0
3434
self._pong_response_cb = None
3535
self._loop = loop
3636
self._waiting = None
@@ -51,17 +51,17 @@ def _reset_heartbeat(self):
5151
self._cancel_heartbeat()
5252

5353
if self._heartbeat is not None:
54-
self._heartbeat_cb = self._time_service.call_later(
55-
self._heartbeat, self._send_heartbeat)
54+
self._heartbeat_cb = call_later(
55+
self._send_heartbeat, self._heartbeat, self._loop)
5656

5757
def _send_heartbeat(self):
5858
if self._heartbeat is not None and not self._closed:
5959
self.ping()
6060

6161
if self._pong_response_cb is not None:
6262
self._pong_response_cb.cancel()
63-
self._pong_response_cb = self._time_service.call_later(
64-
self._heartbeat/2.0, self._pong_not_received)
63+
self._pong_response_cb = call_later(
64+
self._pong_not_received, self._pong_heartbeat, self._loop)
6565

6666
def _pong_not_received(self):
6767
self._closed = True
@@ -133,7 +133,7 @@ def close(self, *, code=1000, message=b''):
133133

134134
while True:
135135
try:
136-
with self._time_service.timeout(self._timeout):
136+
with Timeout(self._timeout, loop=self._loop):
137137
msg = yield from self._reader.read()
138138
except asyncio.CancelledError:
139139
self._close_code = 1006
@@ -168,11 +168,12 @@ def receive(self, timeout=None):
168168
try:
169169
try:
170170
self._waiting = create_future(self._loop)
171-
with self._time_service.timeout(
172-
timeout or self._receive_timeout):
171+
with Timeout(
172+
timeout or self._receive_timeout,
173+
loop=self._loop):
173174
msg = yield from self._reader.read()
174-
self._reset_heartbeat()
175175
finally:
176+
self._reset_heartbeat()
176177
waiter = self._waiting
177178
self._waiting = None
178179
waiter.set_result(True)

0 commit comments

Comments
 (0)