mirror of https://github.com/python/cpython
asyncio: Merge 3.4 -- Support PEP 492. Issue #24017.
This commit is contained in:
commit
dc6a9c0423
|
@ -191,8 +191,8 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
self._thread_id = None
|
self._thread_id = None
|
||||||
self._clock_resolution = time.get_clock_info('monotonic').resolution
|
self._clock_resolution = time.get_clock_info('monotonic').resolution
|
||||||
self._exception_handler = None
|
self._exception_handler = None
|
||||||
self._debug = (not sys.flags.ignore_environment
|
self.set_debug((not sys.flags.ignore_environment
|
||||||
and bool(os.environ.get('PYTHONASYNCIODEBUG')))
|
and bool(os.environ.get('PYTHONASYNCIODEBUG'))))
|
||||||
# In debug mode, if the execution of a callback or a step of a task
|
# In debug mode, if the execution of a callback or a step of a task
|
||||||
# exceed this duration in seconds, the slow callback/task is logged.
|
# exceed this duration in seconds, the slow callback/task is logged.
|
||||||
self.slow_callback_duration = 0.1
|
self.slow_callback_duration = 0.1
|
||||||
|
@ -360,13 +360,18 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
return
|
return
|
||||||
if self._debug:
|
if self._debug:
|
||||||
logger.debug("Close %r", self)
|
logger.debug("Close %r", self)
|
||||||
self._closed = True
|
try:
|
||||||
self._ready.clear()
|
self._closed = True
|
||||||
self._scheduled.clear()
|
self._ready.clear()
|
||||||
executor = self._default_executor
|
self._scheduled.clear()
|
||||||
if executor is not None:
|
executor = self._default_executor
|
||||||
self._default_executor = None
|
if executor is not None:
|
||||||
executor.shutdown(wait=False)
|
self._default_executor = None
|
||||||
|
executor.shutdown(wait=False)
|
||||||
|
finally:
|
||||||
|
# It is important to unregister "sys.coroutine_wrapper"
|
||||||
|
# if it was registered.
|
||||||
|
self.set_debug(False)
|
||||||
|
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
"""Returns True if the event loop was closed."""
|
"""Returns True if the event loop was closed."""
|
||||||
|
@ -1199,3 +1204,27 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
|
|
||||||
def set_debug(self, enabled):
|
def set_debug(self, enabled):
|
||||||
self._debug = enabled
|
self._debug = enabled
|
||||||
|
wrapper = coroutines.debug_wrapper
|
||||||
|
|
||||||
|
try:
|
||||||
|
set_wrapper = sys.set_coroutine_wrapper
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
current_wrapper = sys.get_coroutine_wrapper()
|
||||||
|
if enabled:
|
||||||
|
if current_wrapper not in (None, wrapper):
|
||||||
|
warnings.warn(
|
||||||
|
"loop.set_debug(True): cannot set debug coroutine "
|
||||||
|
"wrapper; another wrapper is already set %r" %
|
||||||
|
current_wrapper, RuntimeWarning)
|
||||||
|
else:
|
||||||
|
set_wrapper(wrapper)
|
||||||
|
else:
|
||||||
|
if current_wrapper not in (None, wrapper):
|
||||||
|
warnings.warn(
|
||||||
|
"loop.set_debug(False): cannot unset debug coroutine "
|
||||||
|
"wrapper; another wrapper was set %r" %
|
||||||
|
current_wrapper, RuntimeWarning)
|
||||||
|
else:
|
||||||
|
set_wrapper(None)
|
||||||
|
|
|
@ -14,6 +14,9 @@ from . import futures
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
|
||||||
|
|
||||||
|
_PY35 = sys.version_info >= (3, 5)
|
||||||
|
|
||||||
|
|
||||||
# Opcode of "yield from" instruction
|
# Opcode of "yield from" instruction
|
||||||
_YIELD_FROM = opcode.opmap['YIELD_FROM']
|
_YIELD_FROM = opcode.opmap['YIELD_FROM']
|
||||||
|
|
||||||
|
@ -30,6 +33,27 @@ _DEBUG = (not sys.flags.ignore_environment
|
||||||
and bool(os.environ.get('PYTHONASYNCIODEBUG')))
|
and bool(os.environ.get('PYTHONASYNCIODEBUG')))
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
types.coroutine
|
||||||
|
except AttributeError:
|
||||||
|
native_coroutine_support = False
|
||||||
|
else:
|
||||||
|
native_coroutine_support = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
_iscoroutinefunction = inspect.iscoroutinefunction
|
||||||
|
except AttributeError:
|
||||||
|
_iscoroutinefunction = lambda func: False
|
||||||
|
|
||||||
|
try:
|
||||||
|
inspect.CO_COROUTINE
|
||||||
|
except AttributeError:
|
||||||
|
_is_native_coro_code = lambda code: False
|
||||||
|
else:
|
||||||
|
_is_native_coro_code = lambda code: (code.co_flags &
|
||||||
|
inspect.CO_COROUTINE)
|
||||||
|
|
||||||
|
|
||||||
# Check for CPython issue #21209
|
# Check for CPython issue #21209
|
||||||
def has_yield_from_bug():
|
def has_yield_from_bug():
|
||||||
class MyGen:
|
class MyGen:
|
||||||
|
@ -54,16 +78,27 @@ _YIELD_FROM_BUG = has_yield_from_bug()
|
||||||
del has_yield_from_bug
|
del has_yield_from_bug
|
||||||
|
|
||||||
|
|
||||||
|
def debug_wrapper(gen):
|
||||||
|
# This function is called from 'sys.set_coroutine_wrapper'.
|
||||||
|
# We only wrap here coroutines defined via 'async def' syntax.
|
||||||
|
# Generator-based coroutines are wrapped in @coroutine
|
||||||
|
# decorator.
|
||||||
|
if _is_native_coro_code(gen.gi_code):
|
||||||
|
return CoroWrapper(gen, None)
|
||||||
|
else:
|
||||||
|
return gen
|
||||||
|
|
||||||
|
|
||||||
class CoroWrapper:
|
class CoroWrapper:
|
||||||
# Wrapper for coroutine object in _DEBUG mode.
|
# Wrapper for coroutine object in _DEBUG mode.
|
||||||
|
|
||||||
def __init__(self, gen, func):
|
def __init__(self, gen, func=None):
|
||||||
assert inspect.isgenerator(gen), gen
|
assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen
|
||||||
self.gen = gen
|
self.gen = gen
|
||||||
self.func = func
|
self.func = func # Used to unwrap @coroutine decorator
|
||||||
self._source_traceback = traceback.extract_stack(sys._getframe(1))
|
self._source_traceback = traceback.extract_stack(sys._getframe(1))
|
||||||
# __name__, __qualname__, __doc__ attributes are set by the coroutine()
|
self.__name__ = getattr(gen, '__name__', None)
|
||||||
# decorator
|
self.__qualname__ = getattr(gen, '__qualname__', None)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
coro_repr = _format_coroutine(self)
|
coro_repr = _format_coroutine(self)
|
||||||
|
@ -75,6 +110,9 @@ class CoroWrapper:
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
if _PY35:
|
||||||
|
__await__ = __iter__ # make compatible with 'await' expression
|
||||||
|
|
||||||
def __next__(self):
|
def __next__(self):
|
||||||
return next(self.gen)
|
return next(self.gen)
|
||||||
|
|
||||||
|
@ -133,6 +171,14 @@ def coroutine(func):
|
||||||
If the coroutine is not yielded from before it is destroyed,
|
If the coroutine is not yielded from before it is destroyed,
|
||||||
an error message is logged.
|
an error message is logged.
|
||||||
"""
|
"""
|
||||||
|
is_coroutine = _iscoroutinefunction(func)
|
||||||
|
if is_coroutine and _is_native_coro_code(func.__code__):
|
||||||
|
# In Python 3.5 that's all we need to do for coroutines
|
||||||
|
# defiend with "async def".
|
||||||
|
# Wrapping in CoroWrapper will happen via
|
||||||
|
# 'sys.set_coroutine_wrapper' function.
|
||||||
|
return func
|
||||||
|
|
||||||
if inspect.isgeneratorfunction(func):
|
if inspect.isgeneratorfunction(func):
|
||||||
coro = func
|
coro = func
|
||||||
else:
|
else:
|
||||||
|
@ -144,18 +190,22 @@ def coroutine(func):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
if not _DEBUG:
|
if not _DEBUG:
|
||||||
wrapper = coro
|
if native_coroutine_support:
|
||||||
|
wrapper = types.coroutine(coro)
|
||||||
|
else:
|
||||||
|
wrapper = coro
|
||||||
else:
|
else:
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(*args, **kwds):
|
def wrapper(*args, **kwds):
|
||||||
w = CoroWrapper(coro(*args, **kwds), func)
|
w = CoroWrapper(coro(*args, **kwds), func=func)
|
||||||
if w._source_traceback:
|
if w._source_traceback:
|
||||||
del w._source_traceback[-1]
|
del w._source_traceback[-1]
|
||||||
if hasattr(func, '__name__'):
|
# Python < 3.5 does not implement __qualname__
|
||||||
w.__name__ = func.__name__
|
# on generator objects, so we set it manually.
|
||||||
if hasattr(func, '__qualname__'):
|
# We use getattr as some callables (such as
|
||||||
w.__qualname__ = func.__qualname__
|
# functools.partial may lack __qualname__).
|
||||||
w.__doc__ = func.__doc__
|
w.__name__ = getattr(func, '__name__', None)
|
||||||
|
w.__qualname__ = getattr(func, '__qualname__', None)
|
||||||
return w
|
return w
|
||||||
|
|
||||||
wrapper._is_coroutine = True # For iscoroutinefunction().
|
wrapper._is_coroutine = True # For iscoroutinefunction().
|
||||||
|
@ -164,7 +214,8 @@ def coroutine(func):
|
||||||
|
|
||||||
def iscoroutinefunction(func):
|
def iscoroutinefunction(func):
|
||||||
"""Return True if func is a decorated coroutine function."""
|
"""Return True if func is a decorated coroutine function."""
|
||||||
return getattr(func, '_is_coroutine', False)
|
return (getattr(func, '_is_coroutine', False) or
|
||||||
|
_iscoroutinefunction(func))
|
||||||
|
|
||||||
|
|
||||||
_COROUTINE_TYPES = (types.GeneratorType, CoroWrapper)
|
_COROUTINE_TYPES = (types.GeneratorType, CoroWrapper)
|
||||||
|
|
|
@ -19,6 +19,7 @@ _CANCELLED = 'CANCELLED'
|
||||||
_FINISHED = 'FINISHED'
|
_FINISHED = 'FINISHED'
|
||||||
|
|
||||||
_PY34 = sys.version_info >= (3, 4)
|
_PY34 = sys.version_info >= (3, 4)
|
||||||
|
_PY35 = sys.version_info >= (3, 5)
|
||||||
|
|
||||||
Error = concurrent.futures._base.Error
|
Error = concurrent.futures._base.Error
|
||||||
CancelledError = concurrent.futures.CancelledError
|
CancelledError = concurrent.futures.CancelledError
|
||||||
|
@ -387,6 +388,9 @@ class Future:
|
||||||
assert self.done(), "yield from wasn't used with future"
|
assert self.done(), "yield from wasn't used with future"
|
||||||
return self.result() # May raise too.
|
return self.result() # May raise too.
|
||||||
|
|
||||||
|
if _PY35:
|
||||||
|
__await__ = __iter__ # make compatible with 'await' expression
|
||||||
|
|
||||||
|
|
||||||
def wrap_future(fut, *, loop=None):
|
def wrap_future(fut, *, loop=None):
|
||||||
"""Wrap concurrent.futures.Future object."""
|
"""Wrap concurrent.futures.Future object."""
|
||||||
|
|
|
@ -11,6 +11,7 @@ import functools
|
||||||
import inspect
|
import inspect
|
||||||
import linecache
|
import linecache
|
||||||
import sys
|
import sys
|
||||||
|
import types
|
||||||
import traceback
|
import traceback
|
||||||
import warnings
|
import warnings
|
||||||
import weakref
|
import weakref
|
||||||
|
@ -73,7 +74,10 @@ class Task(futures.Future):
|
||||||
super().__init__(loop=loop)
|
super().__init__(loop=loop)
|
||||||
if self._source_traceback:
|
if self._source_traceback:
|
||||||
del self._source_traceback[-1]
|
del self._source_traceback[-1]
|
||||||
self._coro = iter(coro) # Use the iterator just in case.
|
if coro.__class__ is types.GeneratorType:
|
||||||
|
self._coro = coro
|
||||||
|
else:
|
||||||
|
self._coro = iter(coro) # Use the iterator just in case.
|
||||||
self._fut_waiter = None
|
self._fut_waiter = None
|
||||||
self._must_cancel = False
|
self._must_cancel = False
|
||||||
self._loop.call_soon(self._step)
|
self._loop.call_soon(self._step)
|
||||||
|
@ -236,7 +240,7 @@ class Task(futures.Future):
|
||||||
elif value is not None:
|
elif value is not None:
|
||||||
result = coro.send(value)
|
result = coro.send(value)
|
||||||
else:
|
else:
|
||||||
result = next(coro)
|
result = coro.send(None)
|
||||||
except StopIteration as exc:
|
except StopIteration as exc:
|
||||||
self.set_result(exc.value)
|
self.set_result(exc.value)
|
||||||
except futures.CancelledError as exc:
|
except futures.CancelledError as exc:
|
||||||
|
|
|
@ -61,7 +61,8 @@ class BaseEventLoopTests(test_utils.TestCase):
|
||||||
NotImplementedError,
|
NotImplementedError,
|
||||||
self.loop._make_write_pipe_transport, m, m)
|
self.loop._make_write_pipe_transport, m, m)
|
||||||
gen = self.loop._make_subprocess_transport(m, m, m, m, m, m, m)
|
gen = self.loop._make_subprocess_transport(m, m, m, m, m, m, m)
|
||||||
self.assertRaises(NotImplementedError, next, iter(gen))
|
with self.assertRaises(NotImplementedError):
|
||||||
|
gen.send(None)
|
||||||
|
|
||||||
def test_close(self):
|
def test_close(self):
|
||||||
self.assertFalse(self.loop.is_closed())
|
self.assertFalse(self.loop.is_closed())
|
||||||
|
|
|
@ -1638,7 +1638,7 @@ class TaskTests(test_utils.TestCase):
|
||||||
return a
|
return a
|
||||||
|
|
||||||
def call(arg):
|
def call(arg):
|
||||||
cw = asyncio.coroutines.CoroWrapper(foo(), foo)
|
cw = asyncio.coroutines.CoroWrapper(foo())
|
||||||
cw.send(None)
|
cw.send(None)
|
||||||
try:
|
try:
|
||||||
cw.send(arg)
|
cw.send(arg)
|
||||||
|
@ -1653,7 +1653,7 @@ class TaskTests(test_utils.TestCase):
|
||||||
def test_corowrapper_weakref(self):
|
def test_corowrapper_weakref(self):
|
||||||
wd = weakref.WeakValueDictionary()
|
wd = weakref.WeakValueDictionary()
|
||||||
def foo(): yield from []
|
def foo(): yield from []
|
||||||
cw = asyncio.coroutines.CoroWrapper(foo(), foo)
|
cw = asyncio.coroutines.CoroWrapper(foo())
|
||||||
wd['cw'] = cw # Would fail without __weakref__ slot.
|
wd['cw'] = cw # Would fail without __weakref__ slot.
|
||||||
cw.gen = None # Suppress warning from __del__.
|
cw.gen = None # Suppress warning from __del__.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue