1From 572db464d3b5123e433759411a0c8796ea9fb5c9 Mon Sep 17 00:00:00 2001
2From: Andrew Svetlov <andrew.svetlov@gmail.com>
3Date: Sun, 29 Nov 2020 15:12:15 +0200
4Subject: [PATCH] Bump async-timeout version for aiohttp 3.8 (#5299)
5
6Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
7[james.hilliard1@gmail.com: backport from upstream commit
81e6ec85e709db083d240c5ca249660d0fa56c61c]
9---
10 aiohttp/client.py                  |  4 +--
11 aiohttp/client_ws.py               |  6 ++--
12 aiohttp/connector.py               | 15 ++++++---
13 aiohttp/helpers.py                 | 25 ++++++---------
14 aiohttp/web_protocol.py            |  6 ++--
15 aiohttp/web_ws.py                  |  6 ++--
16 setup.py                           |  2 +-
17 tests/test_client_ws_functional.py |  2 +-
18 tests/test_helpers.py              | 49 +++++++-----------------------
19 10 files changed, 44 insertions(+), 73 deletions(-)
20
21diff --git a/aiohttp/client.py b/aiohttp/client.py
22index a9da8e15..2c87eb52 100644
23--- a/aiohttp/client.py
24+++ b/aiohttp/client.py
25@@ -74,8 +74,8 @@ from .helpers import (
26     DEBUG,
27     PY_36,
28     BasicAuth,
29-    CeilTimeout,
30     TimeoutHandle,
31+    ceil_timeout,
32     get_running_loop,
33     proxies_from_env,
34     sentinel,
35@@ -515,7 +515,7 @@ class ClientSession:
36
37                     # connection timeout
38                     try:
39-                        with CeilTimeout(real_timeout.connect, loop=self._loop):
40+                        async with ceil_timeout(real_timeout.connect):
41                             assert self._connector is not None
42                             conn = await self._connector.connect(
43                                 req, traces=traces, timeout=real_timeout
44diff --git a/aiohttp/client_ws.py b/aiohttp/client_ws.py
45index 28fa371c..a4c7371f 100644
46--- a/aiohttp/client_ws.py
47+++ b/aiohttp/client_ws.py
48@@ -191,7 +191,7 @@ class ClientWebSocketResponse:
49
50             while True:
51                 try:
52-                    with async_timeout.timeout(self._timeout, loop=self._loop):
53+                    async with async_timeout.timeout(self._timeout):
54                         msg = await self._reader.read()
55                 except asyncio.CancelledError:
56                     self._close_code = 1006
57@@ -224,9 +224,7 @@ class ClientWebSocketResponse:
58             try:
59                 self._waiting = self._loop.create_future()
60                 try:
61-                    with async_timeout.timeout(
62-                        timeout or self._receive_timeout, loop=self._loop
63-                    ):
64+                    async with async_timeout.timeout(timeout or self._receive_timeout):
65                         msg = await self._reader.read()
66                     self._reset_heartbeat()
67                 finally:
68diff --git a/aiohttp/connector.py b/aiohttp/connector.py
69index 748b22a4..77a4f379 100644
70--- a/aiohttp/connector.py
71+++ b/aiohttp/connector.py
72@@ -44,7 +44,14 @@ from .client_exceptions import (
73 )
74 from .client_proto import ResponseHandler
75 from .client_reqrep import ClientRequest, Fingerprint, _merge_ssl_params
76-from .helpers import PY_36, CeilTimeout, get_running_loop, is_ip_address, noop, sentinel
77+from .helpers import (
78+    PY_36,
79+    ceil_timeout,
80+    get_running_loop,
81+    is_ip_address,
82+    noop,
83+    sentinel,
84+)
85 from .http import RESPONSES
86 from .locks import EventResultOrError
87 from .resolver import DefaultResolver
88@@ -965,7 +972,7 @@ class TCPConnector(BaseConnector):
89         **kwargs: Any,
90     ) -> Tuple[asyncio.Transport, ResponseHandler]:
91         try:
92-            with CeilTimeout(timeout.sock_connect):
93+            async with ceil_timeout(timeout.sock_connect):
94                 return await self._loop.create_connection(*args, **kwargs)  # type: ignore  # noqa
95         except cert_errors as exc:
96             raise ClientConnectorCertificateError(req.connection_key, exc) from exc
97@@ -1189,7 +1196,7 @@ class UnixConnector(BaseConnector):
98         self, req: "ClientRequest", traces: List["Trace"], timeout: "ClientTimeout"
99     ) -> ResponseHandler:
100         try:
101-            with CeilTimeout(timeout.sock_connect):
102+            async with ceil_timeout(timeout.sock_connect):
103                 _, proto = await self._loop.create_unix_connection(
104                     self._factory, self._path
105                 )
106@@ -1245,7 +1252,7 @@ class NamedPipeConnector(BaseConnector):
107         self, req: "ClientRequest", traces: List["Trace"], timeout: "ClientTimeout"
108     ) -> ResponseHandler:
109         try:
110-            with CeilTimeout(timeout.sock_connect):
111+            async with ceil_timeout(timeout.sock_connect):
112                 _, proto = await self._loop.create_pipe_connection(  # type: ignore
113                     self._factory, self._path
114                 )
115diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py
116index bbf5f129..a6b14025 100644
117--- a/aiohttp/helpers.py
118+++ b/aiohttp/helpers.py
119@@ -664,21 +664,16 @@ class TimerContext(BaseTimerContext):
120             self._cancelled = True
121
122
123-class CeilTimeout(async_timeout.timeout):
124-    def __enter__(self) -> async_timeout.timeout:
125-        if self._timeout is not None:
126-            self._task = current_task(loop=self._loop)
127-            if self._task is None:
128-                raise RuntimeError(
129-                    "Timeout context manager should be used inside a task"
130-                )
131-            now = self._loop.time()
132-            delay = self._timeout
133-            when = now + delay
134-            if delay > 5:
135-                when = ceil(when)
136-            self._cancel_handler = self._loop.call_at(when, self._cancel_task)
137-        return self
138+def ceil_timeout(delay: Optional[float]) -> async_timeout.Timeout:
139+    if delay is None:
140+        return async_timeout.timeout(None)
141+    else:
142+        loop = get_running_loop()
143+        now = loop.time()
144+        when = now + delay
145+        if delay > 5:
146+            when = ceil(when)
147+        return async_timeout.timeout_at(when)
148
149
150 class HeadersMixin:
151diff --git a/aiohttp/web_protocol.py b/aiohttp/web_protocol.py
152index 8e02bc4a..16f4d4ef 100644
153--- a/aiohttp/web_protocol.py
154+++ b/aiohttp/web_protocol.py
155@@ -13,7 +13,7 @@ import yarl
156
157 from .abc import AbstractAccessLogger, AbstractStreamWriter
158 from .base_protocol import BaseProtocol
159-from .helpers import CeilTimeout, current_task
160+from .helpers import ceil_timeout, current_task
161 from .http import (
162     HttpProcessingError,
163     HttpRequestParser,
164@@ -228,7 +228,7 @@ class RequestHandler(BaseProtocol):
165
166         # wait for handlers
167         with suppress(asyncio.CancelledError, asyncio.TimeoutError):
168-            with CeilTimeout(timeout, loop=self._loop):
169+            async with ceil_timeout(timeout):
170                 if self._error_handler is not None and not self._error_handler.done():
171                     await self._error_handler
172
173@@ -517,7 +517,7 @@ class RequestHandler(BaseProtocol):
174
175                         with suppress(asyncio.TimeoutError, asyncio.CancelledError):
176                             while not payload.is_eof() and now < end_t:
177-                                with CeilTimeout(end_t - now, loop=loop):
178+                                async with ceil_timeout(end_t - now):
179                                     # read and ignore
180                                     await payload.readany()
181                                 now = loop.time()
182diff --git a/aiohttp/web_ws.py b/aiohttp/web_ws.py
183index da7ce6df..5f3cce56 100644
184--- a/aiohttp/web_ws.py
185+++ b/aiohttp/web_ws.py
186@@ -359,7 +359,7 @@ class WebSocketResponse(StreamResponse):
187             reader = self._reader
188             assert reader is not None
189             try:
190-                with async_timeout.timeout(self._timeout, loop=self._loop):
191+                async with async_timeout.timeout(self._timeout):
192                     msg = await reader.read()
193             except asyncio.CancelledError:
194                 self._close_code = 1006
195@@ -400,9 +400,7 @@ class WebSocketResponse(StreamResponse):
196             try:
197                 self._waiting = loop.create_future()
198                 try:
199-                    with async_timeout.timeout(
200-                        timeout or self._receive_timeout, loop=self._loop
201-                    ):
202+                    async with async_timeout.timeout(timeout or self._receive_timeout):
203                         msg = await self._reader.read()
204                     self._reset_heartbeat()
205                 finally:
206diff --git a/setup.py b/setup.py
207index 54462ba7..c262de1e 100644
208--- a/setup.py
209+++ b/setup.py
210@@ -68,7 +68,7 @@ install_requires = [
211     "attrs>=17.3.0",
212     "chardet>=2.0,<5.0",
213     "multidict>=4.5,<7.0",
214-    "async_timeout>=3.0,<4.0",
215+    "async_timeout>=4.0.0a3,<5.0",
216     "yarl>=1.0,<2.0",
217     'idna-ssl>=1.0; python_version<"3.7"',
218     "typing_extensions>=3.6.5",
219diff --git a/tests/test_client_ws_functional.py b/tests/test_client_ws_functional.py
220index e423765a..76ef0525 100644
221--- a/tests/test_client_ws_functional.py
222+++ b/tests/test_client_ws_functional.py
223@@ -461,7 +461,7 @@ async def test_recv_timeout(aiohttp_client) -> None:
224     await resp.send_str("ask")
225
226     with pytest.raises(asyncio.TimeoutError):
227-        with async_timeout.timeout(0.01):
228+        async with async_timeout.timeout(0.01):
229             await resp.receive()
230
231     await resp.close()
232diff --git a/tests/test_helpers.py b/tests/test_helpers.py
233index 3367c24b..d36c7e4c 100644
234--- a/tests/test_helpers.py
235+++ b/tests/test_helpers.py
236@@ -3,7 +3,6 @@ import base64
237 import gc
238 import os
239 import platform
240-import sys
241 import tempfile
242 from math import isclose, modf
243 from unittest import mock
244@@ -391,48 +390,22 @@ async def test_weakref_handle_weak(loop) -> None:
245     await asyncio.sleep(0.1)
246
247
248-def test_ceil_call_later() -> None:
249-    cb = mock.Mock()
250-    loop = mock.Mock()
251-    loop.time.return_value = 10.1
252-    helpers.call_later(cb, 10.1, loop)
253-    loop.call_at.assert_called_with(21.0, cb)
254-
255-
256-def test_ceil_call_later_no_timeout() -> None:
257-    cb = mock.Mock()
258-    loop = mock.Mock()
259-    helpers.call_later(cb, 0, loop)
260-    assert not loop.call_at.called
261-
262-
263-async def test_ceil_timeout(loop) -> None:
264-    with helpers.CeilTimeout(None, loop=loop) as timeout:
265-        assert timeout._timeout is None
266-        assert timeout._cancel_handler is None
267+async def test_ceil_timeout() -> None:
268+    async with helpers.ceil_timeout(None) as timeout:
269+        assert timeout.deadline is None
270
271
272-def test_ceil_timeout_no_task(loop) -> None:
273-    with pytest.raises(RuntimeError):
274-        with helpers.CeilTimeout(10, loop=loop):
275-            pass
276-
277-
278-@pytest.mark.skipif(
279-    sys.version_info < (3, 7), reason="TimerHandle.when() doesn't exist"
280-)
281-async def test_ceil_timeout_round(loop) -> None:
282-    with helpers.CeilTimeout(7.5, loop=loop) as cm:
283-        frac, integer = modf(cm._cancel_handler.when())
284+async def test_ceil_timeout_round() -> None:
285+    async with helpers.ceil_timeout(7.5) as cm:
286+        assert cm.deadline is not None
287+        frac, integer = modf(cm.deadline)
288         assert frac == 0
289
290
291-@pytest.mark.skipif(
292-    sys.version_info < (3, 7), reason="TimerHandle.when() doesn't exist"
293-)
294-async def test_ceil_timeout_small(loop) -> None:
295-    with helpers.CeilTimeout(1.1, loop=loop) as cm:
296-        frac, integer = modf(cm._cancel_handler.when())
297+async def test_ceil_timeout_small() -> None:
298+    async with helpers.ceil_timeout(1.1) as cm:
299+        assert cm.deadline is not None
300+        frac, integer = modf(cm.deadline)
301         # a chance for exact integer with zero fraction is negligible
302         assert frac != 0
303
304--
3052.25.1
306
307