Skip to content

Commit c07b16b

Browse files
committed
Sync with asyncio repo
1 parent 6829dbb commit c07b16b

File tree

4 files changed

+235
-7
lines changed

4 files changed

+235
-7
lines changed

Lib/asyncio/coroutines.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
# before you define your coroutines. A downside of using this feature
2828
# is that tracebacks show entries for the CoroWrapper.__next__ method
2929
# when _DEBUG is true.
30-
_DEBUG = (not sys.flags.ignore_environment
31-
and bool(os.environ.get('PYTHONASYNCIODEBUG')))
30+
_DEBUG = (not sys.flags.ignore_environment and
31+
bool(os.environ.get('PYTHONASYNCIODEBUG')))
3232

3333

3434
try:
@@ -86,7 +86,7 @@ class CoroWrapper:
8686
def __init__(self, gen, func=None):
8787
assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen
8888
self.gen = gen
89-
self.func = func # Used to unwrap @coroutine decorator
89+
self.func = func # Used to unwrap @coroutine decorator
9090
self._source_traceback = traceback.extract_stack(sys._getframe(1))
9191
self.__name__ = getattr(gen, '__name__', None)
9292
self.__qualname__ = getattr(gen, '__qualname__', None)
@@ -283,10 +283,13 @@ def _format_coroutine(coro):
283283
coro_frame = coro.cr_frame
284284

285285
filename = coro_code.co_filename
286-
if (isinstance(coro, CoroWrapper)
287-
and not inspect.isgeneratorfunction(coro.func)
288-
and coro.func is not None):
289-
filename, lineno = events._get_function_source(coro.func)
286+
lineno = 0
287+
if (isinstance(coro, CoroWrapper) and
288+
not inspect.isgeneratorfunction(coro.func) and
289+
coro.func is not None):
290+
source = events._get_function_source(coro.func)
291+
if source is not None:
292+
filename, lineno = source
290293
if coro_frame is None:
291294
coro_repr = ('%s done, defined at %s:%s'
292295
% (coro_name, filename, lineno))

Lib/asyncio/tasks.py

+51
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
'FIRST_COMPLETED', 'FIRST_EXCEPTION', 'ALL_COMPLETED',
55
'wait', 'wait_for', 'as_completed', 'sleep', 'async',
66
'gather', 'shield', 'ensure_future', 'run_coroutine_threadsafe',
7+
'timeout',
78
]
89

910
import concurrent.futures
@@ -732,3 +733,53 @@ def callback():
732733

733734
loop.call_soon_threadsafe(callback)
734735
return future
736+
737+
738+
def timeout(timeout, *, loop=None):
739+
"""A factory which produce a context manager with timeout.
740+
741+
Useful in cases when you want to apply timeout logic around block
742+
of code or in cases when asyncio.wait_for is not suitable.
743+
744+
For example:
745+
746+
>>> with asyncio.timeout(0.001):
747+
>>> yield from coro()
748+
749+
750+
timeout: timeout value in seconds
751+
loop: asyncio compatible event loop
752+
"""
753+
if loop is None:
754+
loop = events.get_event_loop()
755+
return _Timeout(timeout, loop=loop)
756+
757+
758+
class _Timeout:
759+
def __init__(self, timeout, *, loop):
760+
self._timeout = timeout
761+
self._loop = loop
762+
self._task = None
763+
self._cancelled = False
764+
self._cancel_handler = None
765+
766+
def __enter__(self):
767+
self._task = Task.current_task(loop=self._loop)
768+
if self._task is None:
769+
raise RuntimeError('Timeout context manager should be used '
770+
'inside a task')
771+
self._cancel_handler = self._loop.call_later(
772+
self._timeout, self._cancel_task)
773+
return self
774+
775+
def __exit__(self, exc_type, exc_val, exc_tb):
776+
if exc_type is futures.CancelledError and self._cancelled:
777+
self._cancel_handler = None
778+
self._task = None
779+
raise futures.TimeoutError
780+
self._cancel_handler.cancel()
781+
self._cancel_handler = None
782+
self._task = None
783+
784+
def _cancel_task(self):
785+
self._cancelled = self._task.cancel()

Lib/test/test_asyncio/test_tasks.py

+169
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
import re
88
import sys
9+
import time
910
import types
1011
import unittest
1112
import weakref
@@ -2235,5 +2236,173 @@ def coro():
22352236
self.assertEqual(result, 11)
22362237

22372238

2239+
class TimeoutTests(test_utils.TestCase):
2240+
def setUp(self):
2241+
self.loop = asyncio.new_event_loop()
2242+
asyncio.set_event_loop(None)
2243+
2244+
def tearDown(self):
2245+
self.loop.close()
2246+
self.loop = None
2247+
2248+
def test_timeout(self):
2249+
canceled_raised = [False]
2250+
2251+
@asyncio.coroutine
2252+
def long_running_task():
2253+
try:
2254+
yield from asyncio.sleep(10, loop=self.loop)
2255+
except asyncio.CancelledError:
2256+
canceled_raised[0] = True
2257+
raise
2258+
2259+
@asyncio.coroutine
2260+
def go():
2261+
with self.assertRaises(asyncio.TimeoutError):
2262+
with asyncio.timeout(0.01, loop=self.loop) as t:
2263+
yield from long_running_task()
2264+
self.assertIs(t._loop, self.loop)
2265+
2266+
self.loop.run_until_complete(go())
2267+
self.assertTrue(canceled_raised[0], 'CancelledError was not raised')
2268+
2269+
def test_timeout_finish_in_time(self):
2270+
@asyncio.coroutine
2271+
def long_running_task():
2272+
yield from asyncio.sleep(0.01, loop=self.loop)
2273+
return 'done'
2274+
2275+
@asyncio.coroutine
2276+
def go():
2277+
with asyncio.timeout(0.1, loop=self.loop):
2278+
resp = yield from long_running_task()
2279+
self.assertEqual(resp, 'done')
2280+
2281+
self.loop.run_until_complete(go())
2282+
2283+
def test_timeout_gloabal_loop(self):
2284+
asyncio.set_event_loop(self.loop)
2285+
2286+
@asyncio.coroutine
2287+
def run():
2288+
with asyncio.timeout(0.1) as t:
2289+
yield from asyncio.sleep(0.01)
2290+
self.assertIs(t._loop, self.loop)
2291+
2292+
self.loop.run_until_complete(run())
2293+
2294+
def test_timeout_not_relevant_exception(self):
2295+
@asyncio.coroutine
2296+
def go():
2297+
yield from asyncio.sleep(0, loop=self.loop)
2298+
with self.assertRaises(KeyError):
2299+
with asyncio.timeout(0.1, loop=self.loop):
2300+
raise KeyError
2301+
2302+
self.loop.run_until_complete(go())
2303+
2304+
def test_timeout_canceled_error_is_converted_to_timeout(self):
2305+
@asyncio.coroutine
2306+
def go():
2307+
yield from asyncio.sleep(0, loop=self.loop)
2308+
with self.assertRaises(asyncio.CancelledError):
2309+
with asyncio.timeout(0.001, loop=self.loop):
2310+
raise asyncio.CancelledError
2311+
2312+
self.loop.run_until_complete(go())
2313+
2314+
def test_timeout_blocking_loop(self):
2315+
@asyncio.coroutine
2316+
def long_running_task():
2317+
time.sleep(0.05)
2318+
return 'done'
2319+
2320+
@asyncio.coroutine
2321+
def go():
2322+
with asyncio.timeout(0.01, loop=self.loop):
2323+
result = yield from long_running_task()
2324+
self.assertEqual(result, 'done')
2325+
2326+
self.loop.run_until_complete(go())
2327+
2328+
def test_for_race_conditions(self):
2329+
fut = asyncio.Future(loop=self.loop)
2330+
self.loop.call_later(0.1, fut.set_result('done'))
2331+
2332+
@asyncio.coroutine
2333+
def go():
2334+
with asyncio.timeout(0.2, loop=self.loop):
2335+
resp = yield from fut
2336+
self.assertEqual(resp, 'done')
2337+
2338+
self.loop.run_until_complete(go())
2339+
2340+
def test_timeout_time(self):
2341+
@asyncio.coroutine
2342+
def go():
2343+
foo_running = None
2344+
2345+
start = self.loop.time()
2346+
with self.assertRaises(asyncio.TimeoutError):
2347+
with asyncio.timeout(0.1, loop=self.loop):
2348+
foo_running = True
2349+
try:
2350+
yield from asyncio.sleep(0.2, loop=self.loop)
2351+
finally:
2352+
foo_running = False
2353+
2354+
dt = self.loop.time() - start
2355+
self.assertTrue(0.09 < dt < 0.11, dt)
2356+
self.assertFalse(foo_running)
2357+
2358+
self.loop.run_until_complete(go())
2359+
2360+
def test_raise_runtimeerror_if_no_task(self):
2361+
with self.assertRaises(RuntimeError):
2362+
with asyncio.timeout(0.1, loop=self.loop):
2363+
pass
2364+
2365+
def test_outer_coro_is_not_cancelled(self):
2366+
2367+
has_timeout = [False]
2368+
2369+
@asyncio.coroutine
2370+
def outer():
2371+
try:
2372+
with asyncio.timeout(0.001, loop=self.loop):
2373+
yield from asyncio.sleep(1, loop=self.loop)
2374+
except asyncio.TimeoutError:
2375+
has_timeout[0] = True
2376+
2377+
@asyncio.coroutine
2378+
def go():
2379+
task = asyncio.ensure_future(outer(), loop=self.loop)
2380+
yield from task
2381+
self.assertTrue(has_timeout[0])
2382+
self.assertFalse(task.cancelled())
2383+
self.assertTrue(task.done())
2384+
2385+
self.loop.run_until_complete(go())
2386+
2387+
def test_cancel_outer_coro(self):
2388+
fut = asyncio.Future(loop=self.loop)
2389+
2390+
@asyncio.coroutine
2391+
def outer():
2392+
fut.set_result(None)
2393+
yield from asyncio.sleep(1, loop=self.loop)
2394+
2395+
@asyncio.coroutine
2396+
def go():
2397+
task = asyncio.ensure_future(outer(), loop=self.loop)
2398+
yield from fut
2399+
task.cancel()
2400+
with self.assertRaises(asyncio.CancelledError):
2401+
yield from task
2402+
self.assertTrue(task.cancelled())
2403+
self.assertTrue(task.done())
2404+
2405+
self.loop.run_until_complete(go())
2406+
22382407
if __name__ == '__main__':
22392408
unittest.main()

Misc/NEWS

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ Library
2525
- Issue #24120: Ignore PermissionError when traversing a tree with
2626
pathlib.Path.[r]glob(). Patch by Ulrich Petri.
2727

28+
- Skip getaddrinfo if host is already resolved.
29+
Patch by A. Jesse Jiryu Davis.
30+
31+
- Add asyncio.timeout() context manager.
32+
2833

2934
What's New in Python 3.4.4?
3035
===========================

0 commit comments

Comments
 (0)