Skip to content

Commit 047fa06

Browse files
hellysmileasvetlov
authored andcommitted
Change behaviour for timeout <= 0 (#28)
* Change behaviour for timeout <= 0 * Remove comments * Test 3.6.2 and 3.6.1 some changes there * Simplify timeout <= 0 * Hackish fix expired property * Added tests for async with timeout=0 * Revert changes to support Tornado 5- * Switch to loop.call_soon * Fix extra comments * No need to explicit 3.6.2 3.6.1 * Fix tests * Clarify changes.
1 parent a68e482 commit 047fa06

File tree

4 files changed

+77
-20
lines changed

4 files changed

+77
-20
lines changed

CHANGES.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
CHANGES
22
=======
33

4+
x.x.x (xxxx-xx-xx)
5+
------------------
6+
7+
* Changed `timeout <= 0` behaviour
8+
9+
* Backward incompatibility change, prior this version `0` was shortcut for `None`
10+
* when timeout <= 0 `TimeoutError` raised faster
11+
412
1.4.0 (2017-09-09)
513
------------------
614

async_timeout/__init__.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ class timeout:
1919
loop - asyncio compatible event loop
2020
"""
2121
def __init__(self, timeout, *, loop=None):
22-
if timeout is not None and timeout == 0:
23-
timeout = None
2422
self._timeout = timeout
2523
if loop is None:
2624
loop = asyncio.get_event_loop()
@@ -56,13 +54,23 @@ def remaining(self):
5654
return None
5755

5856
def _do_enter(self):
59-
if self._timeout is not None:
60-
self._task = current_task(self._loop)
61-
if self._task is None:
62-
raise RuntimeError('Timeout context manager should be used '
63-
'inside a task')
64-
self._cancel_at = self._loop.time() + self._timeout
65-
self._cancel_handler = self._loop.call_at(self._cancel_at, self._cancel_task)
57+
# Support Tornado 5- without timeout
58+
# Details: https://github.com/python/asyncio/issues/392
59+
if self._timeout is None:
60+
return self
61+
62+
self._task = current_task(self._loop)
63+
if self._task is None:
64+
raise RuntimeError('Timeout context manager should be used '
65+
'inside a task')
66+
67+
if self._timeout <= 0:
68+
self._loop.call_soon(self._cancel_task)
69+
return self
70+
71+
self._cancel_at = self._loop.time() + self._timeout
72+
self._cancel_handler = self._loop.call_at(
73+
self._cancel_at, self._cancel_task)
6674
return self
6775

6876
def _do_exit(self, exc_type):

tests/test_py35.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,26 @@ async def test_async_no_timeout(loop):
1616
async with timeout(1, loop=loop) as cm:
1717
await asyncio.sleep(0, loop=loop)
1818
assert not cm.expired
19+
20+
21+
async def test_async_zero(loop):
22+
with pytest.raises(asyncio.TimeoutError):
23+
async with timeout(0, loop=loop) as cm:
24+
await asyncio.sleep(10, loop=loop)
25+
assert cm.expired
26+
27+
28+
async def test_async_zero_coro_not_started(loop):
29+
coro_started = False
30+
31+
async def coro():
32+
nonlocal coro_started
33+
coro_started = True
34+
35+
with pytest.raises(asyncio.TimeoutError):
36+
async with timeout(0, loop=loop) as cm:
37+
await asyncio.sleep(0, loop=loop)
38+
await coro()
39+
40+
assert cm.expired
41+
assert coro_started is False

tests/test_timeout.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,36 @@ def long_running_task():
8080
assert 0.09 < dt < 0.13, dt
8181

8282

83+
def test_timeout_is_none_no_task(loop):
84+
with timeout(None, loop=loop) as cm:
85+
assert cm._task is None
86+
87+
88+
@asyncio.coroutine
89+
def test_timeout_enable_zero(loop):
90+
with pytest.raises(asyncio.TimeoutError):
91+
with timeout(0, loop=loop) as cm:
92+
yield from asyncio.sleep(0.1, loop=loop)
93+
94+
assert cm.expired
95+
96+
8397
@asyncio.coroutine
84-
def test_timeout_disable_zero(loop):
98+
def test_timeout_enable_zero_coro_not_started(loop):
99+
coro_started = False
100+
85101
@asyncio.coroutine
86-
def long_running_task():
87-
yield from asyncio.sleep(0.1, loop=loop)
88-
return 'done'
102+
def coro():
103+
nonlocal coro_started
104+
coro_started = True
89105

90-
t0 = loop.time()
91-
with timeout(0, loop=loop):
92-
resp = yield from long_running_task()
93-
assert resp == 'done'
94-
dt = loop.time() - t0
95-
assert 0.09 < dt < 0.13, dt
106+
with pytest.raises(asyncio.TimeoutError):
107+
with timeout(0, loop=loop) as cm:
108+
yield from asyncio.sleep(0, loop=loop)
109+
yield from coro()
110+
111+
assert cm.expired
112+
assert coro_started is False
96113

97114

98115
@asyncio.coroutine
@@ -104,7 +121,7 @@ def test_timeout_not_relevant_exception(loop):
104121

105122

106123
@asyncio.coroutine
107-
def test_timeout_canceled_error_is_converted_to_timeout(loop):
124+
def test_timeout_canceled_error_is_not_converted_to_timeout(loop):
108125
yield from asyncio.sleep(0, loop=loop)
109126
with pytest.raises(asyncio.CancelledError):
110127
with timeout(0.001, loop=loop):
@@ -228,6 +245,7 @@ def test_timeout_inner_other_error(loop):
228245
raise RuntimeError
229246
assert not cm.expired
230247

248+
231249
@asyncio.coroutine
232250
def test_timeout_remaining(loop):
233251
with timeout(None, loop=loop) as cm:

0 commit comments

Comments
 (0)