asyncio: sync with Tulip

- Sort imports
- Simplify/optimize iscoroutine(). Inline inspect.isgenerator(obj): replace it
  with isinstance(obj, types.GeneratorType)
- CoroWrapper: check at runtime if Python has the yield-from bug #21209.  If
  Python has the bug, check if CoroWrapper.send() was called by yield-from to
  decide if parameters must be unpacked or not.
- Fix "Task was destroyed but it is pending!" warning in
  test_task_source_traceback()
This commit is contained in:
Victor Stinner 2014-06-30 14:39:47 +02:00
commit 91dd20ba60
3 changed files with 52 additions and 10 deletions

View File

@ -19,11 +19,11 @@ import concurrent.futures
import heapq import heapq
import inspect import inspect
import logging import logging
import os
import socket import socket
import subprocess import subprocess
import traceback
import time import time
import os import traceback
import sys import sys
from . import coroutines from . import coroutines

View File

@ -3,14 +3,20 @@ __all__ = ['coroutine',
import functools import functools
import inspect import inspect
import opcode
import os import os
import sys import sys
import traceback import traceback
import types
from . import events from . import events
from . import futures from . import futures
from .log import logger from .log import logger
# Opcode of "yield from" instruction
_YIELD_FROM = opcode.opmap['YIELD_FROM']
# If you set _DEBUG to true, @coroutine will wrap the resulting # If you set _DEBUG to true, @coroutine will wrap the resulting
# generator objects in a CoroWrapper instance (defined below). That # generator objects in a CoroWrapper instance (defined below). That
# instance will log a message when the generator is never iterated # instance will log a message when the generator is never iterated
@ -25,6 +31,31 @@ _DEBUG = (not sys.flags.ignore_environment
_PY35 = (sys.version_info >= (3, 5)) _PY35 = (sys.version_info >= (3, 5))
# Check for CPython issue #21209
def has_yield_from_bug():
class MyGen:
def __init__(self):
self.send_args = None
def __iter__(self):
return self
def __next__(self):
return 42
def send(self, *what):
self.send_args = what
return None
def yield_from_gen(gen):
yield from gen
value = (1, 2, 3)
gen = MyGen()
coro = yield_from_gen(gen)
next(coro)
coro.send(value)
return gen.send_args != (value,)
_YIELD_FROM_BUG = has_yield_from_bug()
del has_yield_from_bug
class CoroWrapper: class CoroWrapper:
# Wrapper for coroutine in _DEBUG mode. # Wrapper for coroutine in _DEBUG mode.
@ -40,13 +71,21 @@ class CoroWrapper:
def __next__(self): def __next__(self):
return next(self.gen) return next(self.gen)
def send(self, *value): if _YIELD_FROM_BUG:
# We use `*value` because of a bug in CPythons prior # For for CPython issue #21209: using "yield from" and a custom
# to 3.4.1. See issue #21209 and test_yield_from_corowrapper # generator, generator.send(tuple) unpacks the tuple instead of passing
# for details. This workaround should be removed in 3.5.0. # the tuple unchanged. Check if the caller is a generator using "yield
if len(value) == 1: # from" to decide if the parameter should be unpacked or not.
value = value[0] def send(self, *value):
return self.gen.send(value) frame = sys._getframe()
caller = frame.f_back
assert caller.f_lasti >= 0
if caller.f_code.co_code[caller.f_lasti] != _YIELD_FROM:
value = value[0]
return self.gen.send(value)
else:
def send(self, value):
return self.gen.send(value)
def throw(self, exc): def throw(self, exc):
return self.gen.throw(exc) return self.gen.throw(exc)
@ -119,9 +158,11 @@ def iscoroutinefunction(func):
return getattr(func, '_is_coroutine', False) return getattr(func, '_is_coroutine', False)
_COROUTINE_TYPES = (CoroWrapper, types.GeneratorType)
def iscoroutine(obj): def iscoroutine(obj):
"""Return True if obj is a coroutine object.""" """Return True if obj is a coroutine object."""
return isinstance(obj, CoroWrapper) or inspect.isgenerator(obj) return isinstance(obj, _COROUTINE_TYPES)
def _format_coroutine(coro): def _format_coroutine(coro):

View File

@ -1621,6 +1621,7 @@ class TaskTests(test_utils.TestCase):
(__file__, (__file__,
lineno, lineno,
'test_task_source_traceback')) 'test_task_source_traceback'))
self.loop.run_until_complete(task)
class GatherTestsBase: class GatherTestsBase: