Update asyncio from v3.14.2

This commit is contained in:
CPython Developers authored and Jeong, YunWon committed 2026-02-02 00:43:07 +09:00
1 parent fe40dd37c0
commit b83d155235
52 files changed
+4086 -1994

No files matched your search

+27
View File
@@ -10,6 +10,7 @@ from .coroutines import *
from .events import *
from .exceptions import *
from .futures import *
from .graph import *
from .locks import *
from .protocols import *
from .runners import *
@@ -27,6 +28,7 @@ __all__ = (base_events.__all__ +
events.__all__ +
exceptions.__all__ +
futures.__all__ +
graph.__all__ +
locks.__all__ +
protocols.__all__ +
runners.__all__ +
@@ -45,3 +47,28 @@ if sys.platform == 'win32': # pragma: no cover
else:
from .unix_events import * # pragma: no cover
__all__ += unix_events.__all__
def __getattr__(name: str):
import warnings
match name:
case "AbstractEventLoopPolicy":
warnings._deprecated(f"asyncio.{name}", remove=(3, 16))
return events._AbstractEventLoopPolicy
case "DefaultEventLoopPolicy":
warnings._deprecated(f"asyncio.{name}", remove=(3, 16))
if sys.platform == 'win32':
return windows_events._DefaultEventLoopPolicy
return unix_events._DefaultEventLoopPolicy
case "WindowsSelectorEventLoopPolicy":
if sys.platform == 'win32':
warnings._deprecated(f"asyncio.{name}", remove=(3, 16))
return windows_events._WindowsSelectorEventLoopPolicy
# Else fall through to the AttributeError below.
case "WindowsProactorEventLoopPolicy":
if sys.platform == 'win32':
warnings._deprecated(f"asyncio.{name}", remove=(3, 16))
return windows_events._WindowsProactorEventLoopPolicy
# Else fall through to the AttributeError below.
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+37 -3
View File
@@ -1,5 +1,7 @@
import argparse
import ast
import asyncio
import asyncio.tools
import concurrent.futures
import contextvars
import inspect
@@ -10,7 +12,7 @@ import threading
import types
import warnings
from _colorize import can_colorize, ANSIColors # type: ignore[import-not-found]
from _colorize import get_theme
from _pyrepl.console import InteractiveColoredConsole
from . import futures
@@ -102,8 +104,9 @@ class REPLThread(threading.Thread):
exec(startup_code, console.locals)
ps1 = getattr(sys, "ps1", ">>> ")
if can_colorize() and CAN_USE_PYREPL:
ps1 = f"{ANSIColors.BOLD_MAGENTA}{ps1}{ANSIColors.RESET}"
if CAN_USE_PYREPL:
theme = get_theme().syntax
ps1 = f"{theme.prompt}{ps1}{theme.reset}"
console.write(f"{ps1}import asyncio\n")
if CAN_USE_PYREPL:
@@ -141,6 +144,37 @@ class REPLThread(threading.Thread):
if __name__ == '__main__':
parser = argparse.ArgumentParser(
prog="python3 -m asyncio",
description="Interactive asyncio shell and CLI tools",
color=True,
)
subparsers = parser.add_subparsers(help="sub-commands", dest="command")
ps = subparsers.add_parser(
"ps", help="Display a table of all pending tasks in a process"
)
ps.add_argument("pid", type=int, help="Process ID to inspect")
pstree = subparsers.add_parser(
"pstree", help="Display a tree of all pending tasks in a process"
)
pstree.add_argument("pid", type=int, help="Process ID to inspect")
args = parser.parse_args()
match args.command:
case "ps":
asyncio.tools.display_awaited_by_tasks_table(args.pid)
sys.exit(0)
case "pstree":
asyncio.tools.display_awaited_by_tasks_tree(args.pid)
sys.exit(0)
case None:
pass # continue to the interactive shell
case _:
# shouldn't happen as an invalid command-line wouldn't parse
# but let's keep it for the next person adding a command
print(f"error: unhandled command {args.command}", file=sys.stderr)
parser.print_usage(file=sys.stderr)
sys.exit(1)
sys.audit("cpython.run_stdin")
if os.getenv('PYTHON_BASIC_REPL'):
+12 -16
View File
@@ -458,24 +458,18 @@ class BaseEventLoop(events.AbstractEventLoop):
"""Create a Future object attached to the loop."""
return futures.Future(loop=self)
def create_task(self, coro, *, name=None, context=None, **kwargs):
"""Schedule a coroutine object.
def create_task(self, coro, **kwargs):
"""Schedule or begin executing a coroutine object.
Return a task object.
"""
self._check_closed()
if self._task_factory is not None:
if context is not None:
kwargs["context"] = context
task = self._task_factory(self, coro, **kwargs)
task.set_name(name)
else:
task = tasks.Task(coro, loop=self, name=name, context=context, **kwargs)
if task._source_traceback:
del task._source_traceback[-1]
return self._task_factory(self, coro, **kwargs)
task = tasks.Task(coro, loop=self, **kwargs)
if task._source_traceback:
del task._source_traceback[-1]
try:
return task
finally:
@@ -841,7 +835,7 @@ class BaseEventLoop(events.AbstractEventLoop):
def _check_callback(self, callback, method):
if (coroutines.iscoroutine(callback) or
coroutines.iscoroutinefunction(callback)):
coroutines._iscoroutinefunction(callback)):
raise TypeError(
f"coroutines cannot be used with {method}()")
if not callable(callback):
@@ -878,7 +872,10 @@ class BaseEventLoop(events.AbstractEventLoop):
self._check_closed()
if self._debug:
self._check_callback(callback, 'call_soon_threadsafe')
handle = self._call_soon(callback, args, context)
handle = events._ThreadSafeHandle(callback, args, self, context)
self._ready.append(handle)
if handle._source_traceback:
del handle._source_traceback[-1]
if handle._source_traceback:
del handle._source_traceback[-1]
self._write_to_self()
@@ -1677,8 +1674,7 @@ class BaseEventLoop(events.AbstractEventLoop):
raise ValueError(
'ssl_shutdown_timeout is only meaningful with ssl')
if sock is not None:
_check_ssl_socket(sock)
_check_ssl_socket(sock)
transport, protocol = await self._create_connection_transport(
sock, protocol_factory, ssl, '', server_side=True,
+9
View File
@@ -18,7 +18,16 @@ _is_coroutine = object()
def iscoroutinefunction(func):
import warnings
"""Return True if func is a decorated coroutine function."""
warnings._deprecated("asyncio.iscoroutinefunction",
f"{warnings._DEPRECATED_MSG}; "
"use inspect.iscoroutinefunction() instead",
remove=(3,16))
return _iscoroutinefunction(func)
def _iscoroutinefunction(func):
return (inspect.iscoroutinefunction(func) or
getattr(func, '_is_coroutine', None) is _is_coroutine)
+58 -69
View File
@@ -5,14 +5,18 @@
# SPDX-FileCopyrightText: Copyright (c) 2015-2021 MagicStack Inc. http://magic.io
__all__ = (
'AbstractEventLoopPolicy',
'AbstractEventLoop', 'AbstractServer',
'Handle', 'TimerHandle',
'get_event_loop_policy', 'set_event_loop_policy',
'get_event_loop', 'set_event_loop', 'new_event_loop',
'get_child_watcher', 'set_child_watcher',
'_set_running_loop', 'get_running_loop',
'_get_running_loop',
"AbstractEventLoop",
"AbstractServer",
"Handle",
"TimerHandle",
"get_event_loop_policy",
"set_event_loop_policy",
"get_event_loop",
"set_event_loop",
"new_event_loop",
"_set_running_loop",
"get_running_loop",
"_get_running_loop",
)
import contextvars
@@ -22,6 +26,7 @@ import socket
import subprocess
import sys
import threading
import warnings
from . import format_helpers
@@ -104,6 +109,34 @@ class Handle:
self._loop.call_exception_handler(context)
self = None # Needed to break cycles when an exception occurs.
# _ThreadSafeHandle is used for callbacks scheduled with call_soon_threadsafe
# and is thread safe unlike Handle which is not thread safe.
class _ThreadSafeHandle(Handle):
__slots__ = ('_lock',)
def __init__(self, callback, args, loop, context=None):
super().__init__(callback, args, loop, context)
self._lock = threading.RLock()
def cancel(self):
with self._lock:
return super().cancel()
def cancelled(self):
with self._lock:
return super().cancelled()
def _run(self):
# The event loop checks for cancellation without holding the lock
# It is possible that the handle is cancelled after the check
# but before the callback is called so check it again after acquiring
# the lock and return without calling the callback if it is cancelled.
with self._lock:
if self._cancelled:
return
return super()._run()
class TimerHandle(Handle):
"""Object returned by timed callback registration methods."""
@@ -629,7 +662,7 @@ class AbstractEventLoop:
raise NotImplementedError
class AbstractEventLoopPolicy:
class _AbstractEventLoopPolicy:
"""Abstract policy for accessing the event loop."""
def get_event_loop(self):
@@ -652,18 +685,7 @@ class AbstractEventLoopPolicy:
the current context, set_event_loop must be called explicitly."""
raise NotImplementedError
# Child processes handling (Unix only).
def get_child_watcher(self):
"Get the watcher for child processes."
raise NotImplementedError
def set_child_watcher(self, watcher):
"""Set the watcher for child processes."""
raise NotImplementedError
class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
class _BaseDefaultEventLoopPolicy(_AbstractEventLoopPolicy):
"""Default policy implementation for accessing the event loop.
In this policy, each thread has its own event loop. However, we
@@ -680,7 +702,6 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
class _Local(threading.local):
_loop = None
_set_called = False
def __init__(self):
self._local = self._Local()
@@ -690,28 +711,6 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
Returns an instance of EventLoop or raises an exception.
"""
if (self._local._loop is None and
not self._local._set_called and
threading.current_thread() is threading.main_thread()):
stacklevel = 2
try:
f = sys._getframe(1)
except AttributeError:
pass
else:
# Move up the call stack so that the warning is attached
# to the line outside asyncio itself.
while f:
module = f.f_globals.get('__name__')
if not (module == 'asyncio' or module.startswith('asyncio.')):
break
f = f.f_back
stacklevel += 1
import warnings
warnings.warn('There is no current event loop',
DeprecationWarning, stacklevel=stacklevel)
self.set_event_loop(self.new_event_loop())
if self._local._loop is None:
raise RuntimeError('There is no current event loop in thread %r.'
% threading.current_thread().name)
@@ -720,7 +719,6 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
def set_event_loop(self, loop):
"""Set the event loop."""
self._local._set_called = True
if loop is not None and not isinstance(loop, AbstractEventLoop):
raise TypeError(f"loop must be an instance of AbstractEventLoop or None, not '{type(loop).__name__}'")
self._local._loop = loop
@@ -790,34 +788,36 @@ def _init_event_loop_policy():
global _event_loop_policy
with _lock:
if _event_loop_policy is None: # pragma: no branch
from . import DefaultEventLoopPolicy
_event_loop_policy = DefaultEventLoopPolicy()
if sys.platform == 'win32':
from .windows_events import _DefaultEventLoopPolicy
else:
from .unix_events import _DefaultEventLoopPolicy
_event_loop_policy = _DefaultEventLoopPolicy()
def get_event_loop_policy():
def _get_event_loop_policy():
"""Get the current event loop policy."""
if _event_loop_policy is None:
_init_event_loop_policy()
return _event_loop_policy
def get_event_loop_policy():
warnings._deprecated('asyncio.get_event_loop_policy', remove=(3, 16))
return _get_event_loop_policy()
def _set_event_loop_policy(policy):
"""Set the current event loop policy.
If policy is None, the default policy is restored."""
global _event_loop_policy
if policy is not None and not isinstance(policy, AbstractEventLoopPolicy):
if policy is not None and not isinstance(policy, _AbstractEventLoopPolicy):
raise TypeError(f"policy must be an instance of AbstractEventLoopPolicy or None, not '{type(policy).__name__}'")
_event_loop_policy = policy
def set_event_loop_policy(policy):
"""Set the current event loop policy.
If policy is None, the default policy is restored."""
warnings._deprecated('asyncio.set_event_loop_policy', remove=(3,16))
_set_event_loop_policy(policy)
def get_event_loop():
"""Return an asyncio event loop.
@@ -831,28 +831,17 @@ def get_event_loop():
current_loop = _get_running_loop()
if current_loop is not None:
return current_loop
return get_event_loop_policy().get_event_loop()
return _get_event_loop_policy().get_event_loop()
def set_event_loop(loop):
"""Equivalent to calling get_event_loop_policy().set_event_loop(loop)."""
get_event_loop_policy().set_event_loop(loop)
_get_event_loop_policy().set_event_loop(loop)
def new_event_loop():
"""Equivalent to calling get_event_loop_policy().new_event_loop()."""
return get_event_loop_policy().new_event_loop()
def get_child_watcher():
"""Equivalent to calling get_event_loop_policy().get_child_watcher()."""
return get_event_loop_policy().get_child_watcher()
def set_child_watcher(watcher):
"""Equivalent to calling
get_event_loop_policy().set_child_watcher(watcher)."""
return get_event_loop_policy().set_child_watcher(watcher)
return _get_event_loop_policy().new_event_loop()
# Alias pure-Python implementations for testing purposes.
@@ -882,7 +871,7 @@ if hasattr(os, 'fork'):
def on_fork():
# Reset the loop and wakeupfd in the forked child process.
if _event_loop_policy is not None:
_event_loop_policy._local = BaseDefaultEventLoopPolicy._Local()
_event_loop_policy._local = _BaseDefaultEventLoopPolicy._Local()
_set_running_loop(None)
signal.set_wakeup_fd(-1)
+59 -3
View File
@@ -2,6 +2,7 @@
__all__ = (
'Future', 'wrap_future', 'isfuture',
'future_add_to_awaited_by', 'future_discard_from_awaited_by',
)
import concurrent.futures
@@ -43,7 +44,6 @@ class Future:
- This class is not compatible with the wait() and as_completed()
methods in the concurrent.futures package.
(In Python 3.4 or later we may be able to unify the implementations.)
"""
# Class variables serving as defaults for instance variables.
@@ -61,12 +61,15 @@ class Future:
# the Future protocol (i.e. is intended to be duck-type compatible).
# The value must also be not-None, to enable a subclass to declare
# that it is not compatible by setting this to None.
# - It is set by __iter__() below so that Task._step() can tell
# - It is set by __iter__() below so that Task.__step() can tell
# the difference between
# `await Future()` or`yield from Future()` (correct) vs.
# `await Future()` or `yield from Future()` (correct) vs.
# `yield Future()` (incorrect).
_asyncio_future_blocking = False
# Used by the capture_call_stack() API.
__asyncio_awaited_by = None
__log_traceback = False
def __init__(self, *, loop=None):
@@ -116,6 +119,12 @@ class Future:
raise ValueError('_log_traceback can only be set to False')
self.__log_traceback = False
@property
def _asyncio_awaited_by(self):
if self.__asyncio_awaited_by is None:
return None
return frozenset(self.__asyncio_awaited_by)
def get_loop(self):
"""Return the event loop the Future is bound to."""
loop = self._loop
@@ -416,6 +425,49 @@ def wrap_future(future, *, loop=None):
return new_future
def future_add_to_awaited_by(fut, waiter, /):
"""Record that `fut` is awaited on by `waiter`."""
# For the sake of keeping the implementation minimal and assuming
# that most of asyncio users use the built-in Futures and Tasks
# (or their subclasses), we only support native Future objects
# and their subclasses.
#
# Longer version: tracking requires storing the caller-callee
# dependency somewhere. One obvious choice is to store that
# information right in the future itself in a dedicated attribute.
# This means that we'd have to require all duck-type compatible
# futures to implement a specific attribute used by asyncio for
# the book keeping. Another solution would be to store that in
# a global dictionary. The downside here is that that would create
# strong references and any scenario where the "add" call isn't
# followed by a "discard" call would lead to a memory leak.
# Using WeakDict would resolve that issue, but would complicate
# the C code (_asynciomodule.c). The bottom line here is that
# it's not clear that all this work would be worth the effort.
#
# Note that there's an accelerated version of this function
# shadowing this implementation later in this file.
if isinstance(fut, _PyFuture) and isinstance(waiter, _PyFuture):
if fut._Future__asyncio_awaited_by is None:
fut._Future__asyncio_awaited_by = set()
fut._Future__asyncio_awaited_by.add(waiter)
def future_discard_from_awaited_by(fut, waiter, /):
"""Record that `fut` is no longer awaited on by `waiter`."""
# See the comment in "future_add_to_awaited_by()" body for
# details on implementation.
#
# Note that there's an accelerated version of this function
# shadowing this implementation later in this file.
if isinstance(fut, _PyFuture) and isinstance(waiter, _PyFuture):
if fut._Future__asyncio_awaited_by is not None:
fut._Future__asyncio_awaited_by.discard(waiter)
_py_future_add_to_awaited_by = future_add_to_awaited_by
_py_future_discard_from_awaited_by = future_discard_from_awaited_by
try:
import _asyncio
except ImportError:
@@ -423,3 +475,7 @@ except ImportError:
else:
# _CFuture is needed for tests.
Future = _CFuture = _asyncio.Future
future_add_to_awaited_by = _asyncio.future_add_to_awaited_by
future_discard_from_awaited_by = _asyncio.future_discard_from_awaited_by
_c_future_add_to_awaited_by = future_add_to_awaited_by
_c_future_discard_from_awaited_by = future_discard_from_awaited_by
+276
View File
@@ -0,0 +1,276 @@
"""Introspection utils for tasks call graphs."""
import dataclasses
import io
import sys
import types
from . import events
from . import futures
from . import tasks
__all__ = (
'capture_call_graph',
'format_call_graph',
'print_call_graph',
'FrameCallGraphEntry',
'FutureCallGraph',
)
# Sadly, we can't re-use the traceback module's datastructures as those
# are tailored for error reporting, whereas we need to represent an
# async call graph.
#
# Going with pretty verbose names as we'd like to export them to the
# top level asyncio namespace, and want to avoid future name clashes.
@dataclasses.dataclass(frozen=True, slots=True)
class FrameCallGraphEntry:
frame: types.FrameType
@dataclasses.dataclass(frozen=True, slots=True)
class FutureCallGraph:
future: futures.Future
call_stack: tuple["FrameCallGraphEntry", ...]
awaited_by: tuple["FutureCallGraph", ...]
def _build_graph_for_future(
future: futures.Future,
*,
limit: int | None = None,
) -> FutureCallGraph:
if not isinstance(future, futures.Future):
raise TypeError(
f"{future!r} object does not appear to be compatible "
f"with asyncio.Future"
)
coro = None
if get_coro := getattr(future, 'get_coro', None):
coro = get_coro() if limit != 0 else None
st: list[FrameCallGraphEntry] = []
awaited_by: list[FutureCallGraph] = []
while coro is not None:
if hasattr(coro, 'cr_await'):
# A native coroutine or duck-type compatible iterator
st.append(FrameCallGraphEntry(coro.cr_frame))
coro = coro.cr_await
elif hasattr(coro, 'ag_await'):
# A native async generator or duck-type compatible iterator
st.append(FrameCallGraphEntry(coro.cr_frame))
coro = coro.ag_await
else:
break
if future._asyncio_awaited_by:
for parent in future._asyncio_awaited_by:
awaited_by.append(_build_graph_for_future(parent, limit=limit))
if limit is not None:
if limit > 0:
st = st[:limit]
elif limit < 0:
st = st[limit:]
st.reverse()
return FutureCallGraph(future, tuple(st), tuple(awaited_by))
def capture_call_graph(
future: futures.Future | None = None,
/,
*,
depth: int = 1,
limit: int | None = None,
) -> FutureCallGraph | None:
"""Capture the async call graph for the current task or the provided Future.
The graph is represented with three data structures:
* FutureCallGraph(future, call_stack, awaited_by)
Where 'future' is an instance of asyncio.Future or asyncio.Task.
'call_stack' is a tuple of FrameGraphEntry objects.
'awaited_by' is a tuple of FutureCallGraph objects.
* FrameCallGraphEntry(frame)
Where 'frame' is a frame object of a regular Python function
in the call stack.
Receives an optional 'future' argument. If not passed,
the current task will be used. If there's no current task, the function
returns None.
If "capture_call_graph()" is introspecting *the current task*, the
optional keyword-only 'depth' argument can be used to skip the specified
number of frames from top of the stack.
If the optional keyword-only 'limit' argument is provided, each call stack
in the resulting graph is truncated to include at most ``abs(limit)``
entries. If 'limit' is positive, the entries left are the closest to
the invocation point. If 'limit' is negative, the topmost entries are
left. If 'limit' is omitted or None, all entries are present.
If 'limit' is 0, the call stack is not captured at all, only
"awaited by" information is present.
"""
loop = events._get_running_loop()
if future is not None:
# Check if we're in a context of a running event loop;
# if yes - check if the passed future is the currently
# running task or not.
if loop is None or future is not tasks.current_task(loop=loop):
return _build_graph_for_future(future, limit=limit)
# else: future is the current task, move on.
else:
if loop is None:
raise RuntimeError(
'capture_call_graph() is called outside of a running '
'event loop and no *future* to introspect was provided')
future = tasks.current_task(loop=loop)
if future is None:
# This isn't a generic call stack introspection utility. If we
# can't determine the current task and none was provided, we
# just return.
return None
if not isinstance(future, futures.Future):
raise TypeError(
f"{future!r} object does not appear to be compatible "
f"with asyncio.Future"
)
call_stack: list[FrameCallGraphEntry] = []
f = sys._getframe(depth) if limit != 0 else None
try:
while f is not None:
is_async = f.f_generator is not None
call_stack.append(FrameCallGraphEntry(f))
if is_async:
if f.f_back is not None and f.f_back.f_generator is None:
# We've reached the bottom of the coroutine stack, which
# must be the Task that runs it.
break
f = f.f_back
finally:
del f
awaited_by = []
if future._asyncio_awaited_by:
for parent in future._asyncio_awaited_by:
awaited_by.append(_build_graph_for_future(parent, limit=limit))
if limit is not None:
limit *= -1
if limit > 0:
call_stack = call_stack[:limit]
elif limit < 0:
call_stack = call_stack[limit:]
return FutureCallGraph(future, tuple(call_stack), tuple(awaited_by))
def format_call_graph(
future: futures.Future | None = None,
/,
*,
depth: int = 1,
limit: int | None = None,
) -> str:
"""Return the async call graph as a string for `future`.
If `future` is not provided, format the call graph for the current task.
"""
def render_level(st: FutureCallGraph, buf: list[str], level: int) -> None:
def add_line(line: str) -> None:
buf.append(level * ' ' + line)
if isinstance(st.future, tasks.Task):
add_line(
f'* Task(name={st.future.get_name()!r}, id={id(st.future):#x})'
)
else:
add_line(
f'* Future(id={id(st.future):#x})'
)
if st.call_stack:
add_line(
f' + Call stack:'
)
for ste in st.call_stack:
f = ste.frame
if f.f_generator is None:
f = ste.frame
add_line(
f' | File {f.f_code.co_filename!r},'
f' line {f.f_lineno}, in'
f' {f.f_code.co_qualname}()'
)
else:
c = f.f_generator
try:
f = c.cr_frame
code = c.cr_code
tag = 'async'
except AttributeError:
try:
f = c.ag_frame
code = c.ag_code
tag = 'async generator'
except AttributeError:
f = c.gi_frame
code = c.gi_code
tag = 'generator'
add_line(
f' | File {f.f_code.co_filename!r},'
f' line {f.f_lineno}, in'
f' {tag} {code.co_qualname}()'
)
if st.awaited_by:
add_line(
f' + Awaited by:'
)
for fut in st.awaited_by:
render_level(fut, buf, level + 1)
graph = capture_call_graph(future, depth=depth + 1, limit=limit)
if graph is None:
return ""
buf: list[str] = []
try:
render_level(graph, buf, 0)
finally:
# 'graph' has references to frames so we should
# make sure it's GC'ed as soon as we don't need it.
del graph
return '\n'.join(buf)
def print_call_graph(
future: futures.Future | None = None,
/,
*,
file: io.Writer[str] | None = None,
depth: int = 1,
limit: int | None = None,
) -> None:
"""Print the async call graph for the current task or the provided Future."""
print(format_call_graph(future, depth=depth, limit=limit), file=file)
+3 -3
View File
@@ -341,9 +341,9 @@ class Condition(_ContextManagerMixin, mixins._LoopBoundMixin):
fut.set_result(False)
def notify_all(self):
"""Wake up all threads waiting on this condition. This method acts
like notify(), but wakes up all waiting threads instead of one. If the
calling thread has not acquired the lock when this method is called,
"""Wake up all tasks waiting on this condition. This method acts
like notify(), but wakes up all waiting tasks instead of one. If the
calling task has not acquired the lock when this method is called,
a RuntimeError is raised.
"""
self.notify(len(self._waiters))
+13 -4
View File
@@ -3,6 +3,7 @@ __all__ = ('Runner', 'run')
import contextvars
import enum
import functools
import inspect
import threading
import signal
from . import coroutines
@@ -84,10 +85,7 @@ class Runner:
return self._loop
def run(self, coro, *, context=None):
"""Run a coroutine inside the embedded event loop."""
if not coroutines.iscoroutine(coro):
raise ValueError("a coroutine was expected, got {!r}".format(coro))
"""Run code in the embedded event loop."""
if events._get_running_loop() is not None:
# fail fast with short traceback
raise RuntimeError(
@@ -95,8 +93,19 @@ class Runner:
self._lazy_init()
if not coroutines.iscoroutine(coro):
if inspect.isawaitable(coro):
async def _wrap_awaitable(awaitable):
return await awaitable
coro = _wrap_awaitable(coro)
else:
raise TypeError('An asyncio.Future, a coroutine or an '
'awaitable is required')
if context is None:
context = self._context
task = self._loop.create_task(coro, context=context)
if (threading.current_thread() is threading.main_thread()
+8 -4
View File
@@ -173,16 +173,20 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
# listening socket has triggered an EVENT_READ. There may be multiple
# connections waiting for an .accept() so it is called in a loop.
# See https://bugs.python.org/issue27906 for more details.
for _ in range(backlog):
for _ in range(backlog + 1):
try:
conn, addr = sock.accept()
if self._debug:
logger.debug("%r got a new connection from %r: %r",
server, addr, conn)
conn.setblocking(False)
except (BlockingIOError, InterruptedError, ConnectionAbortedError):
# Early exit because the socket accept buffer is empty.
return None
except ConnectionAbortedError:
# Discard connections that were aborted before accept().
continue
except (BlockingIOError, InterruptedError):
# Early exit because of a signal or
# the socket accept buffer is empty.
return
except OSError as exc:
# There's nowhere to send the error, so just log it.
if exc.errno in (errno.EMFILE, errno.ENFILE,
+6 -1
View File
@@ -8,6 +8,7 @@ from . import events
from . import exceptions as exceptions_mod
from . import locks
from . import tasks
from . import futures
async def staggered_race(coro_fns, delay, *, loop=None):
@@ -63,6 +64,7 @@ async def staggered_race(coro_fns, delay, *, loop=None):
"""
# TODO: when we have aiter() and anext(), allow async iterables in coro_fns.
loop = loop or events.get_running_loop()
parent_task = tasks.current_task(loop)
enum_coro_fns = enumerate(coro_fns)
winner_result = None
winner_index = None
@@ -73,6 +75,7 @@ async def staggered_race(coro_fns, delay, *, loop=None):
def task_done(task):
running_tasks.discard(task)
futures.future_discard_from_awaited_by(task, parent_task)
if (
on_completed_fut is not None
and not on_completed_fut.done()
@@ -110,6 +113,7 @@ async def staggered_race(coro_fns, delay, *, loop=None):
this_failed = locks.Event()
next_ok_to_start = locks.Event()
next_task = loop.create_task(run_one_coro(next_ok_to_start, this_failed))
futures.future_add_to_awaited_by(next_task, parent_task)
running_tasks.add(next_task)
next_task.add_done_callback(task_done)
# next_task has been appended to running_tasks so next_task is ok to
@@ -148,6 +152,7 @@ async def staggered_race(coro_fns, delay, *, loop=None):
try:
ok_to_start = locks.Event()
first_task = loop.create_task(run_one_coro(ok_to_start, None))
futures.future_add_to_awaited_by(first_task, parent_task)
running_tasks.add(first_task)
first_task.add_done_callback(task_done)
# first_task has been appended to running_tasks so first_task is ok to start.
@@ -171,4 +176,4 @@ async def staggered_race(coro_fns, delay, *, loop=None):
raise propagate_cancellation_error
return winner_result, winner_index, exceptions
finally:
del exceptions, propagate_cancellation_error, unhandled_exceptions
del exceptions, propagate_cancellation_error, unhandled_exceptions, parent_task
+7 -5
View File
@@ -6,6 +6,7 @@ __all__ = ("TaskGroup",)
from . import events
from . import exceptions
from . import futures
from . import tasks
@@ -178,7 +179,7 @@ class TaskGroup:
exc = None
def create_task(self, coro, *, name=None, context=None):
def create_task(self, coro, **kwargs):
"""Create a new task in this group and return it.
Similar to `asyncio.create_task`.
@@ -192,10 +193,9 @@ class TaskGroup:
if self._aborting:
coro.close()
raise RuntimeError(f"TaskGroup {self!r} is shutting down")
if context is None:
task = self._loop.create_task(coro, name=name)
else:
task = self._loop.create_task(coro, name=name, context=context)
task = self._loop.create_task(coro, **kwargs)
futures.future_add_to_awaited_by(task, self._parent_task)
# Always schedule the done callback even if the task is
# already done (e.g. if the coro was able to complete eagerly),
@@ -228,6 +228,8 @@ class TaskGroup:
def _on_task_done(self, task):
self._tasks.discard(task)
futures.future_discard_from_awaited_by(task, self._parent_task)
if self._on_completed_fut is not None and not self._tasks:
if not self._on_completed_fut.done():
self._on_completed_fut.set_result(True)
+70 -47
View File
@@ -48,23 +48,8 @@ def all_tasks(loop=None):
# capturing the set of eager tasks first, so if an eager task "graduates"
# to a regular task in another thread, we don't risk missing it.
eager_tasks = list(_eager_tasks)
# Looping over the WeakSet isn't safe as it can be updated from another
# thread, therefore we cast it to list prior to filtering. The list cast
# itself requires iteration, so we repeat it several times ignoring
# RuntimeErrors (which are not very likely to occur).
# See issues 34970 and 36607 for details.
scheduled_tasks = None
i = 0
while True:
try:
scheduled_tasks = list(_scheduled_tasks)
except RuntimeError:
i += 1
if i >= 1000:
raise
else:
break
return {t for t in itertools.chain(scheduled_tasks, eager_tasks)
return {t for t in itertools.chain(_scheduled_tasks, eager_tasks)
if futures._get_loop(t) is loop and not t.done()}
@@ -125,7 +110,7 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
self.__eager_start()
else:
self._loop.call_soon(self.__step, context=self._context)
_register_task(self)
_py_register_task(self)
def __del__(self):
if self._state == futures._PENDING and self._log_destroy_pending:
@@ -260,39 +245,39 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
return self._num_cancels_requested
def __eager_start(self):
prev_task = _swap_current_task(self._loop, self)
prev_task = _py_swap_current_task(self._loop, self)
try:
_register_eager_task(self)
_py_register_eager_task(self)
try:
self._context.run(self.__step_run_and_handle_result, None)
finally:
_unregister_eager_task(self)
_py_unregister_eager_task(self)
finally:
try:
curtask = _swap_current_task(self._loop, prev_task)
curtask = _py_swap_current_task(self._loop, prev_task)
assert curtask is self
finally:
if self.done():
self._coro = None
self = None # Needed to break cycles when an exception occurs.
else:
_register_task(self)
_py_register_task(self)
def __step(self, exc=None):
if self.done():
raise exceptions.InvalidStateError(
f'_step(): already done: {self!r}, {exc!r}')
f'__step(): already done: {self!r}, {exc!r}')
if self._must_cancel:
if not isinstance(exc, exceptions.CancelledError):
exc = self._make_cancelled_error()
self._must_cancel = False
self._fut_waiter = None
_enter_task(self._loop, self)
_py_enter_task(self._loop, self)
try:
self.__step_run_and_handle_result(exc)
finally:
_leave_task(self._loop, self)
_py_leave_task(self._loop, self)
self = None # Needed to break cycles when an exception occurs.
def __step_run_and_handle_result(self, exc):
@@ -337,6 +322,7 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
self._loop.call_soon(
self.__step, new_exc, context=self._context)
else:
futures.future_add_to_awaited_by(result, self)
result._asyncio_future_blocking = False
result.add_done_callback(
self.__wakeup, context=self._context)
@@ -371,6 +357,7 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
self = None # Needed to break cycles when an exception occurs.
def __wakeup(self, future):
futures.future_discard_from_awaited_by(future, self)
try:
future.result()
except BaseException as exc:
@@ -379,7 +366,7 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
else:
# Don't pass the value of `future.result()` explicitly,
# as `Future.__iter__` and `Future.__await__` don't need it.
# If we call `_step(value, None)` instead of `_step()`,
# If we call `__step(value, None)` instead of `__step()`,
# Python eval loop would use `.send(value)` method call,
# instead of `__next__()`, which is slower for futures
# that return non-generator iterators from their `__iter__`.
@@ -399,19 +386,13 @@ else:
Task = _CTask = _asyncio.Task
def create_task(coro, *, name=None, context=None):
def create_task(coro, **kwargs):
"""Schedule the execution of a coroutine object in a spawn task.
Return a Task object.
"""
loop = events.get_running_loop()
if context is None:
# Use legacy API if context is not needed
task = loop.create_task(coro, name=name)
else:
task = loop.create_task(coro, name=name, context=context)
return task
return loop.create_task(coro, **kwargs)
# wait() and as_completed() similar to those in PEP 3148.
@@ -517,6 +498,7 @@ async def _wait(fs, timeout, return_when, loop):
if timeout is not None:
timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
counter = len(fs)
cur_task = current_task()
def _on_completion(f):
nonlocal counter
@@ -529,9 +511,11 @@ async def _wait(fs, timeout, return_when, loop):
timeout_handle.cancel()
if not waiter.done():
waiter.set_result(None)
futures.future_discard_from_awaited_by(f, cur_task)
for f in fs:
f.add_done_callback(_on_completion)
futures.future_add_to_awaited_by(f, cur_task)
try:
await waiter
@@ -817,10 +801,19 @@ def gather(*coros_or_futures, return_exceptions=False):
outer.set_result([])
return outer
def _done_callback(fut):
loop = events._get_running_loop()
if loop is not None:
cur_task = current_task(loop)
else:
cur_task = None
def _done_callback(fut, cur_task=cur_task):
nonlocal nfinished
nfinished += 1
if cur_task is not None:
futures.future_discard_from_awaited_by(fut, cur_task)
if outer is None or outer.done():
if not fut.cancelled():
# Mark exception retrieved.
@@ -877,7 +870,6 @@ def gather(*coros_or_futures, return_exceptions=False):
nfuts = 0
nfinished = 0
done_futs = []
loop = None
outer = None # bpo-46672
for arg in coros_or_futures:
if arg not in arg_to_fut:
@@ -890,12 +882,13 @@ def gather(*coros_or_futures, return_exceptions=False):
# can't control it, disable the "destroy pending task"
# warning.
fut._log_destroy_pending = False
nfuts += 1
arg_to_fut[arg] = fut
if fut.done():
done_futs.append(fut)
else:
if cur_task is not None:
futures.future_add_to_awaited_by(fut, cur_task)
fut.add_done_callback(_done_callback)
else:
@@ -915,6 +908,25 @@ def gather(*coros_or_futures, return_exceptions=False):
return outer
def _log_on_exception(fut):
if fut.cancelled():
return
exc = fut.exception()
if exc is None:
return
context = {
'message':
f'{exc.__class__.__name__} exception in shielded future',
'exception': exc,
'future': fut,
}
if fut._source_traceback:
context['source_traceback'] = fut._source_traceback
fut._loop.call_exception_handler(context)
def shield(arg):
"""Wait for a future, shielding it from cancellation.
@@ -955,11 +967,16 @@ def shield(arg):
loop = futures._get_loop(inner)
outer = loop.create_future()
if loop is not None and (cur_task := current_task(loop)) is not None:
futures.future_add_to_awaited_by(inner, cur_task)
else:
cur_task = None
def _clear_awaited_by_callback(inner):
futures.future_discard_from_awaited_by(inner, cur_task)
def _inner_done_callback(inner):
if outer.cancelled():
if not inner.cancelled():
# Mark inner's result as retrieved.
inner.exception()
return
if inner.cancelled():
@@ -971,10 +988,16 @@ def shield(arg):
else:
outer.set_result(inner.result())
def _outer_done_callback(outer):
if not inner.done():
inner.remove_done_callback(_inner_done_callback)
# Keep only one callback to log on cancel
inner.remove_done_callback(_log_on_exception)
inner.add_done_callback(_log_on_exception)
if cur_task is not None:
inner.add_done_callback(_clear_awaited_by_callback)
inner.add_done_callback(_inner_done_callback)
outer.add_done_callback(_outer_done_callback)
@@ -1023,9 +1046,9 @@ def create_eager_task_factory(custom_task_constructor):
used. E.g. `loop.set_task_factory(asyncio.eager_task_factory)`.
"""
def factory(loop, coro, *, name=None, context=None):
def factory(loop, coro, *, eager_start=True, **kwargs):
return custom_task_constructor(
coro, loop=loop, name=name, context=context, eager_start=True)
coro, loop=loop, eager_start=eager_start, **kwargs)
return factory
@@ -1097,14 +1120,13 @@ _py_unregister_eager_task = _unregister_eager_task
_py_enter_task = _enter_task
_py_leave_task = _leave_task
_py_swap_current_task = _swap_current_task
_py_all_tasks = all_tasks
try:
from _asyncio import (_register_task, _register_eager_task,
_unregister_task, _unregister_eager_task,
_enter_task, _leave_task, _swap_current_task,
_scheduled_tasks, _eager_tasks, _current_tasks,
current_task)
current_task, all_tasks)
except ImportError:
pass
else:
@@ -1116,3 +1138,4 @@ else:
_c_enter_task = _enter_task
_c_leave_task = _leave_task
_c_swap_current_task = _swap_current_task
_c_all_tasks = all_tasks
+11 -13
View File
@@ -1,7 +1,6 @@
import enum
from types import TracebackType
from typing import final, Optional, Type
from . import events
from . import exceptions
@@ -23,14 +22,13 @@ class _State(enum.Enum):
EXITED = "finished"
@final
class Timeout:
"""Asynchronous context manager for cancelling overdue coroutines.
Use `timeout()` or `timeout_at()` rather than instantiating this class directly.
"""
def __init__(self, when: Optional[float]) -> None:
def __init__(self, when: float | None) -> None:
"""Schedule a timeout that will trigger at a given loop time.
- If `when` is `None`, the timeout will never trigger.
@@ -39,15 +37,15 @@ class Timeout:
"""
self._state = _State.CREATED
self._timeout_handler: Optional[events.TimerHandle] = None
self._task: Optional[tasks.Task] = None
self._timeout_handler: events.TimerHandle | None = None
self._task: tasks.Task | None = None
self._when = when
def when(self) -> Optional[float]:
def when(self) -> float | None:
"""Return the current deadline."""
return self._when
def reschedule(self, when: Optional[float]) -> None:
def reschedule(self, when: float | None) -> None:
"""Reschedule the timeout."""
if self._state is not _State.ENTERED:
if self._state is _State.CREATED:
@@ -96,10 +94,10 @@ class Timeout:
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> Optional[bool]:
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> bool | None:
assert self._state in (_State.ENTERED, _State.EXPIRING)
if self._timeout_handler is not None:
@@ -142,7 +140,7 @@ class Timeout:
exc_val = exc_val.__context__
def timeout(delay: Optional[float]) -> Timeout:
def timeout(delay: float | None) -> Timeout:
"""Timeout async context manager.
Useful in cases when you want to apply timeout logic around block
@@ -162,7 +160,7 @@ def timeout(delay: Optional[float]) -> Timeout:
return Timeout(loop.time() + delay if delay is not None else None)
def timeout_at(when: Optional[float]) -> Timeout:
def timeout_at(when: float | None) -> Timeout:
"""Schedule the timeout at absolute time.
Like timeout() but argument gives absolute time in the same clock system
+276
View File
@@ -0,0 +1,276 @@
"""Tools to analyze tasks running in asyncio programs."""
from collections import defaultdict, namedtuple
from itertools import count
from enum import Enum
import sys
from _remote_debugging import RemoteUnwinder, FrameInfo
class NodeType(Enum):
COROUTINE = 1
TASK = 2
class CycleFoundException(Exception):
"""Raised when there is a cycle when drawing the call tree."""
def __init__(
self,
cycles: list[list[int]],
id2name: dict[int, str],
) -> None:
super().__init__(cycles, id2name)
self.cycles = cycles
self.id2name = id2name
# ─── indexing helpers ───────────────────────────────────────────
def _format_stack_entry(elem: str|FrameInfo) -> str:
if not isinstance(elem, str):
if elem.lineno == 0 and elem.filename == "":
return f"{elem.funcname}"
else:
return f"{elem.funcname} {elem.filename}:{elem.lineno}"
return elem
def _index(result):
id2name, awaits, task_stacks = {}, [], {}
for awaited_info in result:
for task_info in awaited_info.awaited_by:
task_id = task_info.task_id
task_name = task_info.task_name
id2name[task_id] = task_name
# Store the internal coroutine stack for this task
if task_info.coroutine_stack:
for coro_info in task_info.coroutine_stack:
call_stack = coro_info.call_stack
internal_stack = [_format_stack_entry(frame) for frame in call_stack]
task_stacks[task_id] = internal_stack
# Add the awaited_by relationships (external dependencies)
if task_info.awaited_by:
for coro_info in task_info.awaited_by:
call_stack = coro_info.call_stack
parent_task_id = coro_info.task_name
stack = [_format_stack_entry(frame) for frame in call_stack]
awaits.append((parent_task_id, stack, task_id))
return id2name, awaits, task_stacks
def _build_tree(id2name, awaits, task_stacks):
id2label = {(NodeType.TASK, tid): name for tid, name in id2name.items()}
children = defaultdict(list)
cor_nodes = defaultdict(dict) # Maps parent -> {frame_name: node_key}
next_cor_id = count(1)
def get_or_create_cor_node(parent, frame):
"""Get existing coroutine node or create new one under parent"""
if frame in cor_nodes[parent]:
return cor_nodes[parent][frame]
node_key = (NodeType.COROUTINE, f"c{next(next_cor_id)}")
id2label[node_key] = frame
children[parent].append(node_key)
cor_nodes[parent][frame] = node_key
return node_key
# Build task dependency tree with coroutine frames
for parent_id, stack, child_id in awaits:
cur = (NodeType.TASK, parent_id)
for frame in reversed(stack):
cur = get_or_create_cor_node(cur, frame)
child_key = (NodeType.TASK, child_id)
if child_key not in children[cur]:
children[cur].append(child_key)
# Add coroutine stacks for leaf tasks
awaiting_tasks = {parent_id for parent_id, _, _ in awaits}
for task_id in id2name:
if task_id not in awaiting_tasks and task_id in task_stacks:
cur = (NodeType.TASK, task_id)
for frame in reversed(task_stacks[task_id]):
cur = get_or_create_cor_node(cur, frame)
return id2label, children
def _roots(id2label, children):
all_children = {c for kids in children.values() for c in kids}
return [n for n in id2label if n not in all_children]
# ─── detect cycles in the task-to-task graph ───────────────────────
def _task_graph(awaits):
"""Return {parent_task_id: {child_task_id, …}, …}."""
g = defaultdict(set)
for parent_id, _stack, child_id in awaits:
g[parent_id].add(child_id)
return g
def _find_cycles(graph):
"""
Depth-first search for back-edges.
Returns a list of cycles (each cycle is a list of task-ids) or an
empty list if the graph is acyclic.
"""
WHITE, GREY, BLACK = 0, 1, 2
color = defaultdict(lambda: WHITE)
path, cycles = [], []
def dfs(v):
color[v] = GREY
path.append(v)
for w in graph.get(v, ()):
if color[w] == WHITE:
dfs(w)
elif color[w] == GREY: # back-edge → cycle!
i = path.index(w)
cycles.append(path[i:] + [w]) # make a copy
color[v] = BLACK
path.pop()
for v in list(graph):
if color[v] == WHITE:
dfs(v)
return cycles
# ─── PRINT TREE FUNCTION ───────────────────────────────────────
def get_all_awaited_by(pid):
unwinder = RemoteUnwinder(pid)
return unwinder.get_all_awaited_by()
def build_async_tree(result, task_emoji="(T)", cor_emoji=""):
"""
Build a list of strings for pretty-print an async call tree.
The call tree is produced by `get_all_async_stacks()`, prefixing tasks
with `task_emoji` and coroutine frames with `cor_emoji`.
"""
id2name, awaits, task_stacks = _index(result)
g = _task_graph(awaits)
cycles = _find_cycles(g)
if cycles:
raise CycleFoundException(cycles, id2name)
labels, children = _build_tree(id2name, awaits, task_stacks)
def pretty(node):
flag = task_emoji if node[0] == NodeType.TASK else cor_emoji
return f"{flag} {labels[node]}"
def render(node, prefix="", last=True, buf=None):
if buf is None:
buf = []
buf.append(f"{prefix}{'└── ' if last else '├── '}{pretty(node)}")
new_pref = prefix + (" " if last else "│ ")
kids = children.get(node, [])
for i, kid in enumerate(kids):
render(kid, new_pref, i == len(kids) - 1, buf)
return buf
return [render(root) for root in _roots(labels, children)]
def build_task_table(result):
id2name, _, _ = _index(result)
table = []
for awaited_info in result:
thread_id = awaited_info.thread_id
for task_info in awaited_info.awaited_by:
# Get task info
task_id = task_info.task_id
task_name = task_info.task_name
# Build coroutine stack string
frames = [frame for coro in task_info.coroutine_stack
for frame in coro.call_stack]
coro_stack = " -> ".join(_format_stack_entry(x).split(" ")[0]
for x in frames)
# Handle tasks with no awaiters
if not task_info.awaited_by:
table.append([thread_id, hex(task_id), task_name, coro_stack,
"", "", "0x0"])
continue
# Handle tasks with awaiters
for coro_info in task_info.awaited_by:
parent_id = coro_info.task_name
awaiter_frames = [_format_stack_entry(x).split(" ")[0]
for x in coro_info.call_stack]
awaiter_chain = " -> ".join(awaiter_frames)
awaiter_name = id2name.get(parent_id, "Unknown")
parent_id_str = (hex(parent_id) if isinstance(parent_id, int)
else str(parent_id))
table.append([thread_id, hex(task_id), task_name, coro_stack,
awaiter_chain, awaiter_name, parent_id_str])
return table
def _print_cycle_exception(exception: CycleFoundException):
print("ERROR: await-graph contains cycles - cannot print a tree!", file=sys.stderr)
print("", file=sys.stderr)
for c in exception.cycles:
inames = " → ".join(exception.id2name.get(tid, hex(tid)) for tid in c)
print(f"cycle: {inames}", file=sys.stderr)
def exit_with_permission_help_text():
"""
Prints a message pointing to platform-specific permission help text and exits the program.
This function is called when a PermissionError is encountered while trying
to attach to a process.
"""
print(
"Error: The specified process cannot be attached to due to insufficient permissions.\n"
"See the Python documentation for details on required privileges and troubleshooting:\n"
"https://docs.python.org/3.14/howto/remote_debugging.html#permission-requirements\n"
)
sys.exit(1)
def _get_awaited_by_tasks(pid: int) -> list:
try:
return get_all_awaited_by(pid)
except RuntimeError as e:
while e.__context__ is not None:
e = e.__context__
print(f"Error retrieving tasks: {e}")
sys.exit(1)
except PermissionError as e:
exit_with_permission_help_text()
def display_awaited_by_tasks_table(pid: int) -> None:
"""Build and print a table of all pending tasks under `pid`."""
tasks = _get_awaited_by_tasks(pid)
table = build_task_table(tasks)
# Print the table in a simple tabular format
print(
f"{'tid':<10} {'task id':<20} {'task name':<20} {'coroutine stack':<50} {'awaiter chain':<50} {'awaiter name':<15} {'awaiter id':<15}"
)
print("-" * 180)
for row in table:
print(f"{row[0]:<10} {row[1]:<20} {row[2]:<20} {row[3]:<50} {row[4]:<50} {row[5]:<15} {row[6]:<15}")
def display_awaited_by_tasks_tree(pid: int) -> None:
"""Build and print a tree of all pending tasks under `pid`."""
tasks = _get_awaited_by_tasks(pid)
try:
result = build_async_tree(tasks)
except CycleFoundException as e:
_print_cycle_exception(e)
sys.exit(1)
for tree in result:
print("\n".join(tree))
+26 -590
View File
@@ -28,10 +28,6 @@ from .log import logger
__all__ = (
'SelectorEventLoop',
'AbstractChildWatcher', 'SafeChildWatcher',
'FastChildWatcher', 'PidfdChildWatcher',
'MultiLoopChildWatcher', 'ThreadedChildWatcher',
'DefaultEventLoopPolicy',
'EventLoop',
)
@@ -65,6 +61,10 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
super().__init__(selector)
self._signal_handlers = {}
self._unix_server_sockets = {}
if can_use_pidfd():
self._watcher = _PidfdChildWatcher()
else:
self._watcher = _ThreadedChildWatcher()
def close(self):
super().close()
@@ -94,7 +94,7 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
Raise RuntimeError if there is a problem setting up the handler.
"""
if (coroutines.iscoroutine(callback) or
coroutines.iscoroutinefunction(callback)):
coroutines._iscoroutinefunction(callback)):
raise TypeError("coroutines cannot be used "
"with add_signal_handler()")
self._check_signal(sig)
@@ -197,33 +197,22 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
async def _make_subprocess_transport(self, protocol, args, shell,
stdin, stdout, stderr, bufsize,
extra=None, **kwargs):
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = events.get_child_watcher()
with watcher:
if not watcher.is_active():
# Check early.
# Raising exception before process creation
# prevents subprocess execution if the watcher
# is not ready to handle it.
raise RuntimeError("asyncio.get_child_watcher() is not activated, "
"subprocess support is not installed.")
waiter = self.create_future()
transp = _UnixSubprocessTransport(self, protocol, args, shell,
stdin, stdout, stderr, bufsize,
waiter=waiter, extra=extra,
**kwargs)
watcher.add_child_handler(transp.get_pid(),
self._child_watcher_callback, transp)
try:
await waiter
except (SystemExit, KeyboardInterrupt):
raise
except BaseException:
transp.close()
await transp._wait()
raise
watcher = self._watcher
waiter = self.create_future()
transp = _UnixSubprocessTransport(self, protocol, args, shell,
stdin, stdout, stderr, bufsize,
waiter=waiter, extra=extra,
**kwargs)
watcher.add_child_handler(transp.get_pid(),
self._child_watcher_callback, transp)
try:
await waiter
except (SystemExit, KeyboardInterrupt):
raise
except BaseException:
transp.close()
await transp._wait()
raise
return transp
@@ -865,93 +854,7 @@ class _UnixSubprocessTransport(base_subprocess.BaseSubprocessTransport):
stdin_w.close()
class AbstractChildWatcher:
"""Abstract base class for monitoring child processes.
Objects derived from this class monitor a collection of subprocesses and
report their termination or interruption by a signal.
New callbacks are registered with .add_child_handler(). Starting a new
process must be done within a 'with' block to allow the watcher to suspend
its activity until the new process if fully registered (this is needed to
prevent a race condition in some implementations).
Example:
with watcher:
proc = subprocess.Popen("sleep 1")
watcher.add_child_handler(proc.pid, callback)
Notes:
Implementations of this class must be thread-safe.
Since child watcher objects may catch the SIGCHLD signal and call
waitpid(-1), there should be only one active object per process.
"""
def __init_subclass__(cls) -> None:
if cls.__module__ != __name__:
warnings._deprecated("AbstractChildWatcher",
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.",
remove=(3, 14))
def add_child_handler(self, pid, callback, *args):
"""Register a new child handler.
Arrange for callback(pid, returncode, *args) to be called when
process 'pid' terminates. Specifying another callback for the same
process replaces the previous handler.
Note: callback() must be thread-safe.
"""
raise NotImplementedError()
def remove_child_handler(self, pid):
"""Removes the handler for process 'pid'.
The function returns True if the handler was successfully removed,
False if there was nothing to remove."""
raise NotImplementedError()
def attach_loop(self, loop):
"""Attach the watcher to an event loop.
If the watcher was previously attached to an event loop, then it is
first detached before attaching to the new loop.
Note: loop may be None.
"""
raise NotImplementedError()
def close(self):
"""Close the watcher.
This must be called to make sure that any underlying resource is freed.
"""
raise NotImplementedError()
def is_active(self):
"""Return ``True`` if the watcher is active and is used by the event loop.
Return True if the watcher is installed and ready to handle process exit
notifications.
"""
raise NotImplementedError()
def __enter__(self):
"""Enter the watcher's context and allow starting new processes
This function must return self"""
raise NotImplementedError()
def __exit__(self, a, b, c):
"""Exit the watcher's context"""
raise NotImplementedError()
class PidfdChildWatcher(AbstractChildWatcher):
class _PidfdChildWatcher:
"""Child watcher implementation using Linux's pid file descriptors.
This child watcher polls process file descriptors (pidfds) to await child
@@ -963,21 +866,6 @@ class PidfdChildWatcher(AbstractChildWatcher):
recent (5.3+) kernels.
"""
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
pass
def is_active(self):
return True
def close(self):
pass
def attach_loop(self, loop):
pass
def add_child_handler(self, pid, callback, *args):
loop = events.get_running_loop()
pidfd = os.pidfd_open(pid)
@@ -1002,386 +890,7 @@ class PidfdChildWatcher(AbstractChildWatcher):
os.close(pidfd)
callback(pid, returncode, *args)
def remove_child_handler(self, pid):
# asyncio never calls remove_child_handler() !!!
# The method is no-op but is implemented because
# abstract base classes require it.
return True
class BaseChildWatcher(AbstractChildWatcher):
def __init__(self):
self._loop = None
self._callbacks = {}
def close(self):
self.attach_loop(None)
def is_active(self):
return self._loop is not None and self._loop.is_running()
def _do_waitpid(self, expected_pid):
raise NotImplementedError()
def _do_waitpid_all(self):
raise NotImplementedError()
def attach_loop(self, loop):
assert loop is None or isinstance(loop, events.AbstractEventLoop)
if self._loop is not None and loop is None and self._callbacks:
warnings.warn(
'A loop is being detached '
'from a child watcher with pending handlers',
RuntimeWarning)
if self._loop is not None:
self._loop.remove_signal_handler(signal.SIGCHLD)
self._loop = loop
if loop is not None:
loop.add_signal_handler(signal.SIGCHLD, self._sig_chld)
# Prevent a race condition in case a child terminated
# during the switch.
self._do_waitpid_all()
def _sig_chld(self):
try:
self._do_waitpid_all()
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
# self._loop should always be available here
# as '_sig_chld' is added as a signal handler
# in 'attach_loop'
self._loop.call_exception_handler({
'message': 'Unknown exception in SIGCHLD handler',
'exception': exc,
})
class SafeChildWatcher(BaseChildWatcher):
"""'Safe' child watcher implementation.
This implementation avoids disrupting other code spawning processes by
polling explicitly each process in the SIGCHLD handler instead of calling
os.waitpid(-1).
This is a safe solution but it has a significant overhead when handling a
big number of children (O(n) each time SIGCHLD is raised)
"""
def __init__(self):
super().__init__()
warnings._deprecated("SafeChildWatcher",
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.",
remove=(3, 14))
def close(self):
self._callbacks.clear()
super().close()
def __enter__(self):
return self
def __exit__(self, a, b, c):
pass
def add_child_handler(self, pid, callback, *args):
self._callbacks[pid] = (callback, args)
# Prevent a race condition in case the child is already terminated.
self._do_waitpid(pid)
def remove_child_handler(self, pid):
try:
del self._callbacks[pid]
return True
except KeyError:
return False
def _do_waitpid_all(self):
for pid in list(self._callbacks):
self._do_waitpid(pid)
def _do_waitpid(self, expected_pid):
assert expected_pid > 0
try:
pid, status = os.waitpid(expected_pid, os.WNOHANG)
except ChildProcessError:
# The child process is already reaped
# (may happen if waitpid() is called elsewhere).
pid = expected_pid
returncode = 255
logger.warning(
"Unknown child process pid %d, will report returncode 255",
pid)
else:
if pid == 0:
# The child process is still alive.
return
returncode = waitstatus_to_exitcode(status)
if self._loop.get_debug():
logger.debug('process %s exited with returncode %s',
expected_pid, returncode)
try:
callback, args = self._callbacks.pop(pid)
except KeyError: # pragma: no cover
# May happen if .remove_child_handler() is called
# after os.waitpid() returns.
if self._loop.get_debug():
logger.warning("Child watcher got an unexpected pid: %r",
pid, exc_info=True)
else:
callback(pid, returncode, *args)
class FastChildWatcher(BaseChildWatcher):
"""'Fast' child watcher implementation.
This implementation reaps every terminated processes by calling
os.waitpid(-1) directly, possibly breaking other code spawning processes
and waiting for their termination.
There is no noticeable overhead when handling a big number of children
(O(1) each time a child terminates).
"""
def __init__(self):
super().__init__()
self._lock = threading.Lock()
self._zombies = {}
self._forks = 0
warnings._deprecated("FastChildWatcher",
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.",
remove=(3, 14))
def close(self):
self._callbacks.clear()
self._zombies.clear()
super().close()
def __enter__(self):
with self._lock:
self._forks += 1
return self
def __exit__(self, a, b, c):
with self._lock:
self._forks -= 1
if self._forks or not self._zombies:
return
collateral_victims = str(self._zombies)
self._zombies.clear()
logger.warning(
"Caught subprocesses termination from unknown pids: %s",
collateral_victims)
def add_child_handler(self, pid, callback, *args):
assert self._forks, "Must use the context manager"
with self._lock:
try:
returncode = self._zombies.pop(pid)
except KeyError:
# The child is running.
self._callbacks[pid] = callback, args
return
# The child is dead already. We can fire the callback.
callback(pid, returncode, *args)
def remove_child_handler(self, pid):
try:
del self._callbacks[pid]
return True
except KeyError:
return False
def _do_waitpid_all(self):
# Because of signal coalescing, we must keep calling waitpid() as
# long as we're able to reap a child.
while True:
try:
pid, status = os.waitpid(-1, os.WNOHANG)
except ChildProcessError:
# No more child processes exist.
return
else:
if pid == 0:
# A child process is still alive.
return
returncode = waitstatus_to_exitcode(status)
with self._lock:
try:
callback, args = self._callbacks.pop(pid)
except KeyError:
# unknown child
if self._forks:
# It may not be registered yet.
self._zombies[pid] = returncode
if self._loop.get_debug():
logger.debug('unknown process %s exited '
'with returncode %s',
pid, returncode)
continue
callback = None
else:
if self._loop.get_debug():
logger.debug('process %s exited with returncode %s',
pid, returncode)
if callback is None:
logger.warning(
"Caught subprocess termination from unknown pid: "
"%d -> %d", pid, returncode)
else:
callback(pid, returncode, *args)
class MultiLoopChildWatcher(AbstractChildWatcher):
"""A watcher that doesn't require running loop in the main thread.
This implementation registers a SIGCHLD signal handler on
instantiation (which may conflict with other code that
install own handler for this signal).
The solution is safe but it has a significant overhead when
handling a big number of processes (*O(n)* each time a
SIGCHLD is received).
"""
# Implementation note:
# The class keeps compatibility with AbstractChildWatcher ABC
# To achieve this it has empty attach_loop() method
# and doesn't accept explicit loop argument
# for add_child_handler()/remove_child_handler()
# but retrieves the current loop by get_running_loop()
def __init__(self):
self._callbacks = {}
self._saved_sighandler = None
warnings._deprecated("MultiLoopChildWatcher",
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.",
remove=(3, 14))
def is_active(self):
return self._saved_sighandler is not None
def close(self):
self._callbacks.clear()
if self._saved_sighandler is None:
return
handler = signal.getsignal(signal.SIGCHLD)
if handler != self._sig_chld:
logger.warning("SIGCHLD handler was changed by outside code")
else:
signal.signal(signal.SIGCHLD, self._saved_sighandler)
self._saved_sighandler = None
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def add_child_handler(self, pid, callback, *args):
loop = events.get_running_loop()
self._callbacks[pid] = (loop, callback, args)
# Prevent a race condition in case the child is already terminated.
self._do_waitpid(pid)
def remove_child_handler(self, pid):
try:
del self._callbacks[pid]
return True
except KeyError:
return False
def attach_loop(self, loop):
# Don't save the loop but initialize itself if called first time
# The reason to do it here is that attach_loop() is called from
# unix policy only for the main thread.
# Main thread is required for subscription on SIGCHLD signal
if self._saved_sighandler is not None:
return
self._saved_sighandler = signal.signal(signal.SIGCHLD, self._sig_chld)
if self._saved_sighandler is None:
logger.warning("Previous SIGCHLD handler was set by non-Python code, "
"restore to default handler on watcher close.")
self._saved_sighandler = signal.SIG_DFL
# Set SA_RESTART to limit EINTR occurrences.
signal.siginterrupt(signal.SIGCHLD, False)
def _do_waitpid_all(self):
for pid in list(self._callbacks):
self._do_waitpid(pid)
def _do_waitpid(self, expected_pid):
assert expected_pid > 0
try:
pid, status = os.waitpid(expected_pid, os.WNOHANG)
except ChildProcessError:
# The child process is already reaped
# (may happen if waitpid() is called elsewhere).
pid = expected_pid
returncode = 255
logger.warning(
"Unknown child process pid %d, will report returncode 255",
pid)
debug_log = False
else:
if pid == 0:
# The child process is still alive.
return
returncode = waitstatus_to_exitcode(status)
debug_log = True
try:
loop, callback, args = self._callbacks.pop(pid)
except KeyError: # pragma: no cover
# May happen if .remove_child_handler() is called
# after os.waitpid() returns.
logger.warning("Child watcher got an unexpected pid: %r",
pid, exc_info=True)
else:
if loop.is_closed():
logger.warning("Loop %r that handles pid %r is closed", loop, pid)
else:
if debug_log and loop.get_debug():
logger.debug('process %s exited with returncode %s',
expected_pid, returncode)
loop.call_soon_threadsafe(callback, pid, returncode, *args)
def _sig_chld(self, signum, frame):
try:
self._do_waitpid_all()
except (SystemExit, KeyboardInterrupt):
raise
except BaseException:
logger.warning('Unknown exception in SIGCHLD handler', exc_info=True)
class ThreadedChildWatcher(AbstractChildWatcher):
class _ThreadedChildWatcher:
"""Threaded child watcher implementation.
The watcher uses a thread per process
@@ -1398,18 +907,6 @@ class ThreadedChildWatcher(AbstractChildWatcher):
self._pid_counter = itertools.count(0)
self._threads = {}
def is_active(self):
return True
def close(self):
pass
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def __del__(self, _warn=warnings.warn):
threads = [thread for thread in list(self._threads.values())
if thread.is_alive()]
@@ -1427,15 +924,6 @@ class ThreadedChildWatcher(AbstractChildWatcher):
self._threads[pid] = thread
thread.start()
def remove_child_handler(self, pid):
# asyncio never calls remove_child_handler() !!!
# The method is no-op but is implemented because
# abstract base classes require it.
return True
def attach_loop(self, loop):
pass
def _do_waitpid(self, loop, expected_pid, callback, args):
assert expected_pid > 0
@@ -1474,63 +962,11 @@ def can_use_pidfd():
return True
class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
"""UNIX event loop policy with a watcher for child processes."""
class _UnixDefaultEventLoopPolicy(events._BaseDefaultEventLoopPolicy):
"""UNIX event loop policy"""
_loop_factory = _UnixSelectorEventLoop
def __init__(self):
super().__init__()
self._watcher = None
def _init_watcher(self):
with events._lock:
if self._watcher is None: # pragma: no branch
if can_use_pidfd():
self._watcher = PidfdChildWatcher()
else:
self._watcher = ThreadedChildWatcher()
def set_event_loop(self, loop):
"""Set the event loop.
As a side effect, if a child watcher was set before, then calling
.set_event_loop() from the main thread will call .attach_loop(loop) on
the child watcher.
"""
super().set_event_loop(loop)
if (self._watcher is not None and
threading.current_thread() is threading.main_thread()):
self._watcher.attach_loop(loop)
def get_child_watcher(self):
"""Get the watcher for child processes.
If not yet set, a ThreadedChildWatcher object is automatically created.
"""
if self._watcher is None:
self._init_watcher()
warnings._deprecated("get_child_watcher",
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.", remove=(3, 14))
return self._watcher
def set_child_watcher(self, watcher):
"""Set the watcher for child processes."""
assert watcher is None or isinstance(watcher, AbstractChildWatcher)
if self._watcher is not None:
self._watcher.close()
self._watcher = watcher
warnings._deprecated("set_child_watcher",
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.", remove=(3, 14))
SelectorEventLoop = _UnixSelectorEventLoop
DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy
_DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy
EventLoop = SelectorEventLoop
+5 -5
View File
@@ -29,8 +29,8 @@ from .log import logger
__all__ = (
'SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor',
'DefaultEventLoopPolicy', 'WindowsSelectorEventLoopPolicy',
'WindowsProactorEventLoopPolicy', 'EventLoop',
'_DefaultEventLoopPolicy', '_WindowsSelectorEventLoopPolicy',
'_WindowsProactorEventLoopPolicy', 'EventLoop',
)
@@ -891,13 +891,13 @@ class _WindowsSubprocessTransport(base_subprocess.BaseSubprocessTransport):
SelectorEventLoop = _WindowsSelectorEventLoop
class WindowsSelectorEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
class _WindowsSelectorEventLoopPolicy(events._BaseDefaultEventLoopPolicy):
_loop_factory = SelectorEventLoop
class WindowsProactorEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
class _WindowsProactorEventLoopPolicy(events._BaseDefaultEventLoopPolicy):
_loop_factory = ProactorEventLoop
DefaultEventLoopPolicy = WindowsProactorEventLoopPolicy
_DefaultEventLoopPolicy = _WindowsProactorEventLoopPolicy
EventLoop = ProactorEventLoop
+6 -7
View File
@@ -217,16 +217,15 @@ class TestThreadedServer(SocketThread):
pass
finally:
super().stop()
def run(self):
try:
with self._sock:
self._sock.setblocking(False)
self._run()
finally:
self._sock.close()
self._s1.close()
self._s2.close()
def run(self):
self._sock.setblocking(False)
self._run()
def _run(self):
while self._active:
if self._clients >= self._max_clients:
+4 -6
View File
@@ -29,7 +29,7 @@ class CustomError(Exception):
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
def mock_socket_module():
@@ -1019,8 +1019,7 @@ class BaseEventLoopTests(test_utils.TestCase):
asyncio.create_task(iter_one())
return status
# TODO: RUSTPYTHON - GC doesn't finalize async generators
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - GC doesn't finalize async generators
def test_asyncgen_finalization_by_gc(self):
# Async generators should be finalized when garbage collected.
self.loop._process_events = mock.Mock()
@@ -1036,8 +1035,7 @@ class BaseEventLoopTests(test_utils.TestCase):
test_utils.run_briefly(self.loop)
self.assertTrue(status['finalized'])
# TODO: RUSTPYTHON - GC doesn't finalize async generators
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - GC doesn't finalize async generators
def test_asyncgen_finalization_by_gc_in_other_thread(self):
# Python issue 34769: If garbage collector runs in another
# thread, async generators will not finalize in debug
@@ -1413,7 +1411,7 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
with self.assertRaises(OSError) as cm:
self.loop.run_until_complete(coro)
self.assertTrue(str(cm.exception).startswith('Multiple exceptions: '))
self.assertStartsWith(str(cm.exception), 'Multiple exceptions: ')
self.assertTrue(m_socket.socket.return_value.close.called)
coro = self.loop.create_connection(
+1 -1
View File
@@ -5,7 +5,7 @@ from test.test_asyncio import functional as func_tests
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class ReceiveStuffProto(asyncio.BufferedProtocol):
+1 -1
View File
@@ -4,7 +4,7 @@ import unittest
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
@unittest.skipUnless(decimal.HAVE_CONTEXTVAR, "decimal is built with a thread-local context")
+119 -4
View File
@@ -13,7 +13,7 @@ MOCK_ANY = mock.ANY
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class EagerTaskFactoryLoopTests:
@@ -263,19 +263,62 @@ class EagerTaskFactoryLoopTests:
self.run_coro(run())
def test_eager_start_false(self):
name = None
async def asyncfn():
nonlocal name
name = asyncio.current_task().get_name()
async def main():
t = asyncio.get_running_loop().create_task(
asyncfn(), eager_start=False, name="example"
)
self.assertFalse(t.done())
self.assertIsNone(name)
await t
self.assertEqual(name, "example")
self.run_coro(main())
class PyEagerTaskFactoryLoopTests(EagerTaskFactoryLoopTests, test_utils.TestCase):
Task = tasks._PyTask
def setUp(self):
self._all_tasks = asyncio.all_tasks
self._current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = asyncio.tasks._py_current_task
asyncio.all_tasks = asyncio.tasks.all_tasks = asyncio.tasks._py_all_tasks
return super().setUp()
def tearDown(self):
asyncio.current_task = asyncio.tasks.current_task = self._current_task
asyncio.all_tasks = asyncio.tasks.all_tasks = self._all_tasks
return super().tearDown()
@unittest.skipUnless(hasattr(tasks, '_CTask'),
'requires the C _asyncio module')
class CEagerTaskFactoryLoopTests(EagerTaskFactoryLoopTests, test_utils.TestCase):
Task = getattr(tasks, '_CTask', None)
def setUp(self):
self._current_task = asyncio.current_task
self._all_tasks = asyncio.all_tasks
asyncio.current_task = asyncio.tasks.current_task = asyncio.tasks._c_current_task
asyncio.all_tasks = asyncio.tasks.all_tasks = asyncio.tasks._c_all_tasks
return super().setUp()
def tearDown(self):
asyncio.current_task = asyncio.tasks.current_task = self._current_task
asyncio.all_tasks = asyncio.tasks.all_tasks = self._all_tasks
return super().tearDown()
def test_issue105987(self):
code = """if 1:
from _asyncio import _swap_current_task
from _asyncio import _swap_current_task, _set_running_loop
class DummyTask:
pass
@@ -284,6 +327,7 @@ class CEagerTaskFactoryLoopTests(EagerTaskFactoryLoopTests, test_utils.TestCase)
pass
l = DummyLoop()
_set_running_loop(l)
_swap_current_task(l, DummyTask())
t = _swap_current_task(l, None)
"""
@@ -400,31 +444,102 @@ class BaseEagerTaskFactoryTests(BaseTaskCountingTests):
class NonEagerTests(BaseNonEagerTaskFactoryTests, test_utils.TestCase):
Task = asyncio.Task
Task = asyncio.tasks._CTask
def setUp(self):
self._current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = asyncio.tasks._c_current_task
return super().setUp()
def tearDown(self):
asyncio.current_task = asyncio.tasks.current_task = self._current_task
return super().tearDown()
class EagerTests(BaseEagerTaskFactoryTests, test_utils.TestCase):
Task = asyncio.Task
Task = asyncio.tasks._CTask
def setUp(self):
self._current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = asyncio.tasks._c_current_task
return super().setUp()
def tearDown(self):
asyncio.current_task = asyncio.tasks.current_task = self._current_task
return super().tearDown()
class NonEagerPyTaskTests(BaseNonEagerTaskFactoryTests, test_utils.TestCase):
Task = tasks._PyTask
def setUp(self):
self._current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = asyncio.tasks._py_current_task
return super().setUp()
def tearDown(self):
asyncio.current_task = asyncio.tasks.current_task = self._current_task
return super().tearDown()
class EagerPyTaskTests(BaseEagerTaskFactoryTests, test_utils.TestCase):
Task = tasks._PyTask
def setUp(self):
self._current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = asyncio.tasks._py_current_task
return super().setUp()
def tearDown(self):
asyncio.current_task = asyncio.tasks.current_task = self._current_task
return super().tearDown()
@unittest.skipUnless(hasattr(tasks, '_CTask'),
'requires the C _asyncio module')
class NonEagerCTaskTests(BaseNonEagerTaskFactoryTests, test_utils.TestCase):
Task = getattr(tasks, '_CTask', None)
def setUp(self):
self._current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = asyncio.tasks._c_current_task
return super().setUp()
def tearDown(self):
asyncio.current_task = asyncio.tasks.current_task = self._current_task
return super().tearDown()
@unittest.skipUnless(hasattr(tasks, '_CTask'),
'requires the C _asyncio module')
class EagerCTaskTests(BaseEagerTaskFactoryTests, test_utils.TestCase):
Task = getattr(tasks, '_CTask', None)
def setUp(self):
self._current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = asyncio.tasks._c_current_task
return super().setUp()
def tearDown(self):
asyncio.current_task = asyncio.tasks.current_task = self._current_task
return super().tearDown()
class DefaultTaskFactoryEagerStart(test_utils.TestCase):
def test_eager_start_true_with_default_factory(self):
name = None
async def asyncfn():
nonlocal name
name = asyncio.current_task().get_name()
async def main():
t = asyncio.get_running_loop().create_task(
asyncfn(), eager_start=True, name="example"
)
self.assertTrue(t.done())
self.assertEqual(name, "example")
await t
asyncio.run(main(), loop_factory=asyncio.EventLoop)
if __name__ == '__main__':
unittest.main()
+253 -134
View File
@@ -23,7 +23,6 @@ import errno
import unittest
from unittest import mock
import weakref
import warnings
if sys.platform not in ('win32', 'vxworks'):
import tty
@@ -38,9 +37,8 @@ from test.support import socket_helper
from test.support import threading_helper
from test.support import ALWAYS_EQ, LARGEST, SMALLEST
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
def broken_unix_getsockname():
@@ -275,8 +273,7 @@ class EventLoopTestsMixin:
support.gc_collect()
super().tearDown()
# TODO: RUSTPYTHON - RuntimeWarning for unawaited coroutine not triggered
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - RuntimeWarning for unawaited coroutine not triggered
def test_run_until_complete_nesting(self):
async def coro1():
await asyncio.sleep(0)
@@ -356,6 +353,124 @@ class EventLoopTestsMixin:
t.join()
self.assertEqual(results, ['hello', 'world'])
def test_call_soon_threadsafe_handle_block_check_cancelled(self):
results = []
callback_started = threading.Event()
callback_finished = threading.Event()
def callback(arg):
callback_started.set()
results.append(arg)
time.sleep(1)
callback_finished.set()
def run_in_thread():
handle = self.loop.call_soon_threadsafe(callback, 'hello')
self.assertIsInstance(handle, events._ThreadSafeHandle)
callback_started.wait()
# callback started so it should block checking for cancellation
# until it finishes
self.assertFalse(handle.cancelled())
self.assertTrue(callback_finished.is_set())
self.loop.call_soon_threadsafe(self.loop.stop)
t = threading.Thread(target=run_in_thread)
t.start()
self.loop.run_forever()
t.join()
self.assertEqual(results, ['hello'])
def test_call_soon_threadsafe_handle_block_cancellation(self):
results = []
callback_started = threading.Event()
callback_finished = threading.Event()
def callback(arg):
callback_started.set()
results.append(arg)
time.sleep(1)
callback_finished.set()
def run_in_thread():
handle = self.loop.call_soon_threadsafe(callback, 'hello')
self.assertIsInstance(handle, events._ThreadSafeHandle)
callback_started.wait()
# callback started so it cannot be cancelled from other thread until
# it finishes
handle.cancel()
self.assertTrue(callback_finished.is_set())
self.loop.call_soon_threadsafe(self.loop.stop)
t = threading.Thread(target=run_in_thread)
t.start()
self.loop.run_forever()
t.join()
self.assertEqual(results, ['hello'])
def test_call_soon_threadsafe_handle_cancel_same_thread(self):
results = []
callback_started = threading.Event()
callback_finished = threading.Event()
fut = concurrent.futures.Future()
def callback(arg):
callback_started.set()
handle = fut.result()
handle.cancel()
results.append(arg)
callback_finished.set()
self.loop.stop()
def run_in_thread():
handle = self.loop.call_soon_threadsafe(callback, 'hello')
fut.set_result(handle)
self.assertIsInstance(handle, events._ThreadSafeHandle)
callback_started.wait()
# callback cancels itself from same thread so it has no effect
# it runs to completion
self.assertTrue(handle.cancelled())
self.assertTrue(callback_finished.is_set())
self.loop.call_soon_threadsafe(self.loop.stop)
t = threading.Thread(target=run_in_thread)
t.start()
self.loop.run_forever()
t.join()
self.assertEqual(results, ['hello'])
def test_call_soon_threadsafe_handle_cancel_other_thread(self):
results = []
ev = threading.Event()
callback_finished = threading.Event()
def callback(arg):
results.append(arg)
callback_finished.set()
self.loop.stop()
def run_in_thread():
handle = self.loop.call_soon_threadsafe(callback, 'hello')
# handle can be cancelled from other thread if not started yet
self.assertIsInstance(handle, events._ThreadSafeHandle)
handle.cancel()
self.assertTrue(handle.cancelled())
self.assertFalse(callback_finished.is_set())
ev.set()
self.loop.call_soon_threadsafe(self.loop.stop)
# block the main loop until the callback is added and cancelled in the
# other thread
self.loop.call_soon(ev.wait)
t = threading.Thread(target=run_in_thread)
t.start()
self.loop.run_forever()
t.join()
self.assertEqual(results, [])
self.assertFalse(callback_finished.is_set())
def test_call_soon_threadsafe_same_thread(self):
results = []
@@ -445,8 +560,7 @@ class EventLoopTestsMixin:
r.close()
self.assertEqual(read, data)
# TODO: RUSTPYTHON - signal handler implementation differs
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - signal handler implementation differs
@unittest.skipUnless(hasattr(signal, 'SIGKILL'), 'No SIGKILL')
def test_add_signal_handler(self):
caught = 0
@@ -1174,8 +1288,7 @@ class EventLoopTestsMixin:
server.close()
self.loop.run_until_complete(proto.done)
# TODO: RUSTPYTHON - SSL peer certificate format differs
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - SSL peer certificate format differs
@unittest.skipIf(ssl is None, 'No ssl module')
def test_create_server_ssl_verified(self):
proto = MyProto(loop=self.loop)
@@ -2073,7 +2186,7 @@ class SubprocessTestsMixin:
transp.close()
self.assertEqual(b'OUT:test', proto.data[1])
self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2])
self.assertStartsWith(proto.data[2], b'ERR:test')
self.assertEqual(0, proto.returncode)
@support.requires_subprocess()
@@ -2095,8 +2208,7 @@ class SubprocessTestsMixin:
stdin.write(b'test')
self.loop.run_until_complete(proto.completed)
self.assertTrue(proto.data[1].startswith(b'OUT:testERR:test'),
proto.data[1])
self.assertStartsWith(proto.data[1], b'OUT:testERR:test')
self.assertEqual(b'', proto.data[2])
transp.close()
@@ -2216,24 +2328,8 @@ if sys.platform == 'win32':
else:
import selectors
class UnixEventLoopTestsMixin(EventLoopTestsMixin):
def setUp(self):
super().setUp()
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = asyncio.SafeChildWatcher()
watcher.attach_loop(self.loop)
asyncio.set_child_watcher(watcher)
def tearDown(self):
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
asyncio.set_child_watcher(None)
super().tearDown()
if hasattr(selectors, 'KqueueSelector'):
class KqueueEventLoopTests(UnixEventLoopTestsMixin,
class KqueueEventLoopTests(EventLoopTestsMixin,
SubprocessTestsMixin,
test_utils.TestCase):
@@ -2258,7 +2354,7 @@ else:
super().test_write_pty()
if hasattr(selectors, 'EpollSelector'):
class EPollEventLoopTests(UnixEventLoopTestsMixin,
class EPollEventLoopTests(EventLoopTestsMixin,
SubprocessTestsMixin,
test_utils.TestCase):
@@ -2266,7 +2362,7 @@ else:
return asyncio.SelectorEventLoop(selectors.EpollSelector())
if hasattr(selectors, 'PollSelector'):
class PollEventLoopTests(UnixEventLoopTestsMixin,
class PollEventLoopTests(EventLoopTestsMixin,
SubprocessTestsMixin,
test_utils.TestCase):
@@ -2274,7 +2370,7 @@ else:
return asyncio.SelectorEventLoop(selectors.PollSelector())
# Should always exist.
class SelectEventLoopTests(UnixEventLoopTestsMixin,
class SelectEventLoopTests(EventLoopTestsMixin,
SubprocessTestsMixin,
test_utils.TestCase):
@@ -2420,7 +2516,7 @@ class HandleTests(test_utils.TestCase):
self.assertRegex(repr(h), regex)
def test_handle_source_traceback(self):
loop = asyncio.get_event_loop_policy().new_event_loop()
loop = asyncio.new_event_loop()
loop.set_debug(True)
self.set_event_loop(loop)
@@ -2559,33 +2655,46 @@ class TimerTests(unittest.TestCase):
h1 = asyncio.TimerHandle(when, callback, (), self.loop)
h2 = asyncio.TimerHandle(when, callback, (), self.loop)
# TODO: Use assertLess etc.
self.assertFalse(h1 < h2)
self.assertFalse(h2 < h1)
self.assertTrue(h1 <= h2)
self.assertTrue(h2 <= h1)
self.assertFalse(h1 > h2)
self.assertFalse(h2 > h1)
self.assertTrue(h1 >= h2)
self.assertTrue(h2 >= h1)
self.assertTrue(h1 == h2)
self.assertFalse(h1 != h2)
with self.assertRaises(AssertionError):
self.assertLess(h1, h2)
with self.assertRaises(AssertionError):
self.assertLess(h2, h1)
with self.assertRaises(AssertionError):
self.assertGreater(h1, h2)
with self.assertRaises(AssertionError):
self.assertGreater(h2, h1)
with self.assertRaises(AssertionError):
self.assertNotEqual(h1, h2)
self.assertLessEqual(h1, h2)
self.assertLessEqual(h2, h1)
self.assertGreaterEqual(h1, h2)
self.assertGreaterEqual(h2, h1)
self.assertEqual(h1, h2)
h2.cancel()
self.assertFalse(h1 == h2)
with self.assertRaises(AssertionError):
self.assertEqual(h1, h2)
self.assertNotEqual(h1, h2)
h1 = asyncio.TimerHandle(when, callback, (), self.loop)
h2 = asyncio.TimerHandle(when + 10.0, callback, (), self.loop)
self.assertTrue(h1 < h2)
self.assertFalse(h2 < h1)
self.assertTrue(h1 <= h2)
self.assertFalse(h2 <= h1)
self.assertFalse(h1 > h2)
self.assertTrue(h2 > h1)
self.assertFalse(h1 >= h2)
self.assertTrue(h2 >= h1)
self.assertFalse(h1 == h2)
self.assertTrue(h1 != h2)
with self.assertRaises(AssertionError):
self.assertLess(h2, h1)
with self.assertRaises(AssertionError):
self.assertLessEqual(h2, h1)
with self.assertRaises(AssertionError):
self.assertGreater(h1, h2)
with self.assertRaises(AssertionError):
self.assertGreaterEqual(h1, h2)
with self.assertRaises(AssertionError):
self.assertEqual(h1, h2)
self.assertLess(h1, h2)
self.assertGreater(h2, h1)
self.assertLessEqual(h1, h2)
self.assertGreaterEqual(h2, h1)
self.assertNotEqual(h1, h2)
h3 = asyncio.Handle(callback, (), self.loop)
self.assertIs(NotImplemented, h1.__eq__(h3))
@@ -2599,19 +2708,25 @@ class TimerTests(unittest.TestCase):
h1 <= ()
with self.assertRaises(TypeError):
h1 >= ()
self.assertFalse(h1 == ())
self.assertTrue(h1 != ())
with self.assertRaises(AssertionError):
self.assertEqual(h1, ())
with self.assertRaises(AssertionError):
self.assertNotEqual(h1, ALWAYS_EQ)
with self.assertRaises(AssertionError):
self.assertGreater(h1, LARGEST)
with self.assertRaises(AssertionError):
self.assertGreaterEqual(h1, LARGEST)
with self.assertRaises(AssertionError):
self.assertLess(h1, SMALLEST)
with self.assertRaises(AssertionError):
self.assertLessEqual(h1, SMALLEST)
self.assertTrue(h1 == ALWAYS_EQ)
self.assertFalse(h1 != ALWAYS_EQ)
self.assertTrue(h1 < LARGEST)
self.assertFalse(h1 > LARGEST)
self.assertTrue(h1 <= LARGEST)
self.assertFalse(h1 >= LARGEST)
self.assertFalse(h1 < SMALLEST)
self.assertTrue(h1 > SMALLEST)
self.assertFalse(h1 <= SMALLEST)
self.assertTrue(h1 >= SMALLEST)
self.assertNotEqual(h1, ())
self.assertEqual(h1, ALWAYS_EQ)
self.assertLess(h1, LARGEST)
self.assertLessEqual(h1, LARGEST)
self.assertGreaterEqual(h1, SMALLEST)
self.assertGreater(h1, SMALLEST)
class AbstractEventLoopTests(unittest.TestCase):
@@ -2718,58 +2833,55 @@ class AbstractEventLoopTests(unittest.TestCase):
class PolicyTests(unittest.TestCase):
def test_abstract_event_loop_policy_deprecation(self):
with self.assertWarnsRegex(
DeprecationWarning, "'asyncio.AbstractEventLoopPolicy' is deprecated"):
policy = asyncio.AbstractEventLoopPolicy()
self.assertIsInstance(policy, asyncio.AbstractEventLoopPolicy)
def test_default_event_loop_policy_deprecation(self):
with self.assertWarnsRegex(
DeprecationWarning, "'asyncio.DefaultEventLoopPolicy' is deprecated"):
policy = asyncio.DefaultEventLoopPolicy()
self.assertIsInstance(policy, asyncio.DefaultEventLoopPolicy)
def test_event_loop_policy(self):
policy = asyncio.AbstractEventLoopPolicy()
policy = asyncio.events._AbstractEventLoopPolicy()
self.assertRaises(NotImplementedError, policy.get_event_loop)
self.assertRaises(NotImplementedError, policy.set_event_loop, object())
self.assertRaises(NotImplementedError, policy.new_event_loop)
self.assertRaises(NotImplementedError, policy.get_child_watcher)
self.assertRaises(NotImplementedError, policy.set_child_watcher,
object())
def test_get_event_loop(self):
policy = asyncio.DefaultEventLoopPolicy()
policy = test_utils.DefaultEventLoopPolicy()
self.assertIsNone(policy._local._loop)
with self.assertWarns(DeprecationWarning) as cm:
with self.assertRaises(RuntimeError):
loop = policy.get_event_loop()
self.assertEqual(cm.filename, __file__)
self.assertIsInstance(loop, asyncio.AbstractEventLoop)
self.assertIsNone(policy._local._loop)
self.assertIs(policy._local._loop, loop)
self.assertIs(loop, policy.get_event_loop())
loop.close()
def test_get_event_loop_calls_set_event_loop(self):
policy = asyncio.DefaultEventLoopPolicy()
def test_get_event_loop_does_not_call_set_event_loop(self):
policy = test_utils.DefaultEventLoopPolicy()
with mock.patch.object(
policy, "set_event_loop",
wraps=policy.set_event_loop) as m_set_event_loop:
with self.assertWarns(DeprecationWarning) as cm:
with self.assertRaises(RuntimeError):
loop = policy.get_event_loop()
self.addCleanup(loop.close)
self.assertEqual(cm.filename, __file__)
# policy._local._loop must be set through .set_event_loop()
# (the unix DefaultEventLoopPolicy needs this call to attach
# the child watcher correctly)
m_set_event_loop.assert_called_with(loop)
loop.close()
m_set_event_loop.assert_not_called()
def test_get_event_loop_after_set_none(self):
policy = asyncio.DefaultEventLoopPolicy()
policy = test_utils.DefaultEventLoopPolicy()
policy.set_event_loop(None)
self.assertRaises(RuntimeError, policy.get_event_loop)
# TODO: RUSTPYTHON - mock.patch doesn't work correctly with threading.current_thread
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - mock.patch doesn't work correctly with threading.current_thread
@mock.patch('asyncio.events.threading.current_thread')
def test_get_event_loop_thread(self, m_current_thread):
def f():
policy = asyncio.DefaultEventLoopPolicy()
policy = test_utils.DefaultEventLoopPolicy()
self.assertRaises(RuntimeError, policy.get_event_loop)
th = threading.Thread(target=f)
@@ -2777,14 +2889,14 @@ class PolicyTests(unittest.TestCase):
th.join()
def test_new_event_loop(self):
policy = asyncio.DefaultEventLoopPolicy()
policy = test_utils.DefaultEventLoopPolicy()
loop = policy.new_event_loop()
self.assertIsInstance(loop, asyncio.AbstractEventLoop)
loop.close()
def test_set_event_loop(self):
policy = asyncio.DefaultEventLoopPolicy()
policy = test_utils.DefaultEventLoopPolicy()
old_loop = policy.new_event_loop()
policy.set_event_loop(old_loop)
@@ -2798,20 +2910,31 @@ class PolicyTests(unittest.TestCase):
old_loop.close()
def test_get_event_loop_policy(self):
policy = asyncio.get_event_loop_policy()
self.assertIsInstance(policy, asyncio.AbstractEventLoopPolicy)
self.assertIs(policy, asyncio.get_event_loop_policy())
with self.assertWarnsRegex(
DeprecationWarning, "'asyncio.get_event_loop_policy' is deprecated"):
policy = asyncio.get_event_loop_policy()
self.assertIsInstance(policy, asyncio.events._AbstractEventLoopPolicy)
self.assertIs(policy, asyncio.get_event_loop_policy())
def test_set_event_loop_policy(self):
self.assertRaises(
TypeError, asyncio.set_event_loop_policy, object())
with self.assertWarnsRegex(
DeprecationWarning, "'asyncio.set_event_loop_policy' is deprecated"):
self.assertRaises(
TypeError, asyncio.set_event_loop_policy, object())
old_policy = asyncio.get_event_loop_policy()
with self.assertWarnsRegex(
DeprecationWarning, "'asyncio.get_event_loop_policy' is deprecated"):
old_policy = asyncio.get_event_loop_policy()
policy = asyncio.DefaultEventLoopPolicy()
asyncio.set_event_loop_policy(policy)
self.assertIs(policy, asyncio.get_event_loop_policy())
self.assertIsNot(policy, old_policy)
policy = test_utils.DefaultEventLoopPolicy()
with self.assertWarnsRegex(
DeprecationWarning, "'asyncio.set_event_loop_policy' is deprecated"):
asyncio.set_event_loop_policy(policy)
with self.assertWarnsRegex(
DeprecationWarning, "'asyncio.get_event_loop_policy' is deprecated"):
self.assertIs(policy, asyncio.get_event_loop_policy())
self.assertIsNot(policy, old_policy)
class GetEventLoopTestsMixin:
@@ -2821,11 +2944,16 @@ class GetEventLoopTestsMixin:
get_running_loop_impl = None
get_event_loop_impl = None
Task = None
Future = None
def setUp(self):
self._get_running_loop_saved = events._get_running_loop
self._set_running_loop_saved = events._set_running_loop
self.get_running_loop_saved = events.get_running_loop
self.get_event_loop_saved = events.get_event_loop
self._Task_saved = asyncio.Task
self._Future_saved = asyncio.Future
events._get_running_loop = type(self)._get_running_loop_impl
events._set_running_loop = type(self)._set_running_loop_impl
@@ -2837,25 +2965,15 @@ class GetEventLoopTestsMixin:
asyncio.get_running_loop = type(self).get_running_loop_impl
asyncio.get_event_loop = type(self).get_event_loop_impl
asyncio.Task = asyncio.tasks.Task = type(self).Task
asyncio.Future = asyncio.futures.Future = type(self).Future
super().setUp()
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
if sys.platform != 'win32':
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = asyncio.SafeChildWatcher()
watcher.attach_loop(self.loop)
asyncio.set_child_watcher(watcher)
def tearDown(self):
try:
if sys.platform != 'win32':
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
asyncio.set_child_watcher(None)
super().tearDown()
finally:
self.loop.close()
@@ -2871,8 +2989,10 @@ class GetEventLoopTestsMixin:
asyncio.get_running_loop = self.get_running_loop_saved
asyncio.get_event_loop = self.get_event_loop_saved
if sys.platform != 'win32':
asyncio.Task = asyncio.tasks.Task = self._Task_saved
asyncio.Future = asyncio.futures.Future = self._Future_saved
if sys.platform != 'win32':
def test_get_event_loop_new_process(self):
# bpo-32126: The multiprocessing module used by
# ProcessPoolExecutor is not functional when the
@@ -2918,13 +3038,13 @@ class GetEventLoopTestsMixin:
class TestError(Exception):
pass
class Policy(asyncio.DefaultEventLoopPolicy):
class Policy(test_utils.DefaultEventLoopPolicy):
def get_event_loop(self):
raise TestError
old_policy = asyncio.get_event_loop_policy()
old_policy = asyncio.events._get_event_loop_policy()
try:
asyncio.set_event_loop_policy(Policy())
asyncio.events._set_event_loop_policy(Policy())
loop = asyncio.new_event_loop()
with self.assertRaises(TestError):
@@ -2952,7 +3072,7 @@ class GetEventLoopTestsMixin:
asyncio.get_event_loop()
finally:
asyncio.set_event_loop_policy(old_policy)
asyncio.events._set_event_loop_policy(old_policy)
if loop is not None:
loop.close()
@@ -2962,23 +3082,18 @@ class GetEventLoopTestsMixin:
self.assertIs(asyncio._get_running_loop(), None)
def test_get_event_loop_returns_running_loop2(self):
old_policy = asyncio.get_event_loop_policy()
old_policy = asyncio.events._get_event_loop_policy()
try:
asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())
asyncio.events._set_event_loop_policy(test_utils.DefaultEventLoopPolicy())
loop = asyncio.new_event_loop()
self.addCleanup(loop.close)
with self.assertWarns(DeprecationWarning) as cm:
loop2 = asyncio.get_event_loop()
self.addCleanup(loop2.close)
self.assertEqual(cm.filename, __file__)
asyncio.set_event_loop(None)
with self.assertRaisesRegex(RuntimeError, 'no current'):
asyncio.get_event_loop()
with self.assertRaisesRegex(RuntimeError, 'no running'):
asyncio.get_running_loop()
self.assertIs(asyncio._get_running_loop(), None)
asyncio.set_event_loop(None)
with self.assertRaisesRegex(RuntimeError, 'no current'):
asyncio.get_event_loop()
async def func():
self.assertIs(asyncio.get_event_loop(), loop)
@@ -2995,7 +3110,7 @@ class GetEventLoopTestsMixin:
asyncio.get_event_loop()
finally:
asyncio.set_event_loop_policy(old_policy)
asyncio.events._set_event_loop_policy(old_policy)
if loop is not None:
loop.close()
@@ -3012,6 +3127,8 @@ class TestPyGetEventLoop(GetEventLoopTestsMixin, unittest.TestCase):
get_running_loop_impl = events._py_get_running_loop
get_event_loop_impl = events._py_get_event_loop
Task = asyncio.tasks._PyTask
Future = asyncio.futures._PyFuture
try:
import _asyncio # NoQA
@@ -3026,6 +3143,8 @@ else:
get_running_loop_impl = events._c_get_running_loop
get_event_loop_impl = events._c_get_event_loop
Task = asyncio.tasks._CTask
Future = asyncio.futures._CFuture
class TestServer(unittest.TestCase):
+227
View File
@@ -0,0 +1,227 @@
import asyncio
import threading
import unittest
from threading import Thread
from unittest import TestCase
import weakref
from test import support
from test.support import threading_helper
threading_helper.requires_working_threading(module=True)
class MyException(Exception):
pass
def tearDownModule():
asyncio.events._set_event_loop_policy(None)
class TestFreeThreading:
def test_all_tasks_race(self) -> None:
async def main():
loop = asyncio.get_running_loop()
future = loop.create_future()
async def coro():
await future
tasks = set()
async with asyncio.TaskGroup() as tg:
for _ in range(100):
tasks.add(tg.create_task(coro()))
all_tasks = asyncio.all_tasks(loop)
self.assertEqual(len(all_tasks), 101)
for task in all_tasks:
self.assertEqual(task.get_loop(), loop)
self.assertFalse(task.done())
current = asyncio.current_task()
self.assertEqual(current.get_loop(), loop)
self.assertSetEqual(all_tasks, tasks | {current})
future.set_result(None)
def runner():
with asyncio.Runner() as runner:
loop = runner.get_loop()
loop.set_task_factory(self.factory)
runner.run(main())
threads = []
for _ in range(10):
thread = Thread(target=runner)
threads.append(thread)
with threading_helper.start_threads(threads):
pass
def test_all_tasks_different_thread(self) -> None:
loop = None
started = threading.Event()
done = threading.Event() # used for main task not finishing early
async def coro():
await asyncio.Future()
lock = threading.Lock()
tasks = set()
async def main():
nonlocal tasks, loop
loop = asyncio.get_running_loop()
started.set()
for i in range(1000):
with lock:
asyncio.create_task(coro())
tasks = asyncio.all_tasks(loop)
done.wait()
runner = threading.Thread(target=lambda: asyncio.run(main()))
def check():
started.wait()
with lock:
self.assertSetEqual(tasks & asyncio.all_tasks(loop), tasks)
threads = [threading.Thread(target=check) for _ in range(10)]
runner.start()
with threading_helper.start_threads(threads):
pass
done.set()
runner.join()
def test_task_different_thread_finalized(self) -> None:
task = None
async def func():
nonlocal task
task = asyncio.current_task()
def runner():
with asyncio.Runner() as runner:
loop = runner.get_loop()
loop.set_task_factory(self.factory)
runner.run(func())
thread = Thread(target=runner)
thread.start()
thread.join()
wr = weakref.ref(task)
del thread
del task
# task finalization in different thread shouldn't crash
support.gc_collect()
self.assertIsNone(wr())
def test_run_coroutine_threadsafe(self) -> None:
results = []
def in_thread(loop: asyncio.AbstractEventLoop):
coro = asyncio.sleep(0.1, result=42)
fut = asyncio.run_coroutine_threadsafe(coro, loop)
result = fut.result()
self.assertEqual(result, 42)
results.append(result)
async def main():
loop = asyncio.get_running_loop()
async with asyncio.TaskGroup() as tg:
for _ in range(10):
tg.create_task(asyncio.to_thread(in_thread, loop))
self.assertEqual(results, [42] * 10)
with asyncio.Runner() as r:
loop = r.get_loop()
loop.set_task_factory(self.factory)
r.run(main())
def test_run_coroutine_threadsafe_exception(self) -> None:
async def coro():
await asyncio.sleep(0)
raise MyException("test")
def in_thread(loop: asyncio.AbstractEventLoop):
fut = asyncio.run_coroutine_threadsafe(coro(), loop)
return fut.result()
async def main():
loop = asyncio.get_running_loop()
tasks = []
for _ in range(10):
task = loop.create_task(asyncio.to_thread(in_thread, loop))
tasks.append(task)
results = await asyncio.gather(*tasks, return_exceptions=True)
self.assertEqual(len(results), 10)
for result in results:
self.assertIsInstance(result, MyException)
self.assertEqual(str(result), "test")
with asyncio.Runner() as r:
loop = r.get_loop()
loop.set_task_factory(self.factory)
r.run(main())
class TestPyFreeThreading(TestFreeThreading, TestCase):
def setUp(self):
self._old_current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = asyncio.tasks._py_current_task
self._old_all_tasks = asyncio.all_tasks
asyncio.all_tasks = asyncio.tasks.all_tasks = asyncio.tasks._py_all_tasks
self._old_Task = asyncio.Task
asyncio.Task = asyncio.tasks.Task = asyncio.tasks._PyTask
self._old_Future = asyncio.Future
asyncio.Future = asyncio.futures.Future = asyncio.futures._PyFuture
return super().setUp()
def tearDown(self):
asyncio.current_task = asyncio.tasks.current_task = self._old_current_task
asyncio.all_tasks = asyncio.tasks.all_tasks = self._old_all_tasks
asyncio.Task = asyncio.tasks.Task = self._old_Task
asyncio.Future = asyncio.tasks.Future = self._old_Future
return super().tearDown()
def factory(self, loop, coro, **kwargs):
return asyncio.tasks._PyTask(coro, loop=loop, **kwargs)
@unittest.skipUnless(hasattr(asyncio.tasks, "_c_all_tasks"), "requires _asyncio")
class TestCFreeThreading(TestFreeThreading, TestCase):
def setUp(self):
self._old_current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = asyncio.tasks._c_current_task
self._old_all_tasks = asyncio.all_tasks
asyncio.all_tasks = asyncio.tasks.all_tasks = asyncio.tasks._c_all_tasks
self._old_Task = asyncio.Task
asyncio.Task = asyncio.tasks.Task = asyncio.tasks._CTask
self._old_Future = asyncio.Future
asyncio.Future = asyncio.futures.Future = asyncio.futures._CFuture
return super().setUp()
def tearDown(self):
asyncio.current_task = asyncio.tasks.current_task = self._old_current_task
asyncio.all_tasks = asyncio.tasks.all_tasks = self._old_all_tasks
asyncio.Task = asyncio.tasks.Task = self._old_Task
asyncio.Future = asyncio.futures.Future = self._old_Future
return super().tearDown()
def factory(self, loop, coro, **kwargs):
return asyncio.tasks._CTask(coro, loop=loop, **kwargs)
class TestEagerPyFreeThreading(TestPyFreeThreading):
def factory(self, loop, coro, eager_start=True, **kwargs):
return asyncio.tasks._PyTask(coro, loop=loop, **kwargs, eager_start=eager_start)
@unittest.skipUnless(hasattr(asyncio.tasks, "_c_all_tasks"), "requires _asyncio")
class TestEagerCFreeThreading(TestCFreeThreading, TestCase):
def factory(self, loop, coro, eager_start=True, **kwargs):
return asyncio.tasks._CTask(coro, loop=loop, **kwargs, eager_start=eager_start)
+6 -10
View File
@@ -17,7 +17,7 @@ from test import support
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
def _fakefunc(f):
@@ -242,7 +242,7 @@ class BaseFutureTests:
def test_future_cancel_message_getter(self):
f = self._new_future(loop=self.loop)
self.assertTrue(hasattr(f, '_cancel_message'))
self.assertHasAttr(f, '_cancel_message')
self.assertEqual(f._cancel_message, None)
f.cancel('my message')
@@ -716,14 +716,6 @@ class CFutureTests(BaseFutureTests, test_utils.TestCase):
with self.assertRaises(AttributeError):
del fut._log_traceback
def test_future_iter_get_referents_segfault(self):
# See https://github.com/python/cpython/issues/122695
import _asyncio
it = iter(self._new_future(loop=self.loop))
del it
evil = gc.get_referents(_asyncio)
gc.collect()
def test_callbacks_copy(self):
# See https://github.com/python/cpython/issues/125789
# In C implementation, the `_callbacks` attribute
@@ -742,6 +734,10 @@ class CFutureTests(BaseFutureTests, test_utils.TestCase):
fut.remove_done_callback(f2)
self.assertIsNone(fut._callbacks)
@unittest.expectedFailure # TODO: RUSTPYTHON; - gc.get_referents not implemented
def test_future_iter_get_referents_segfault(self):
return super().test_future_iter_get_referents_segfault()
@unittest.skipUnless(hasattr(futures, '_CFuture'),
'requires the C _asyncio module')
+1 -1
View File
@@ -7,7 +7,7 @@ from asyncio import tasks
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class FutureTests:
+445
View File
@@ -0,0 +1,445 @@
import asyncio
import io
import unittest
# To prevent a warning "test altered the execution environment"
def tearDownModule():
asyncio.events._set_event_loop_policy(None)
def capture_test_stack(*, fut=None, depth=1):
def walk(s):
ret = [
(f"T<{n}>" if '-' not in (n := s.future.get_name()) else 'T<anon>')
if isinstance(s.future, asyncio.Task) else 'F'
]
ret.append(
[
(
f"s {entry.frame.f_code.co_name}"
if entry.frame.f_generator is None else
(
f"a {entry.frame.f_generator.cr_code.co_name}"
if hasattr(entry.frame.f_generator, 'cr_code') else
f"ag {entry.frame.f_generator.ag_code.co_name}"
)
) for entry in s.call_stack
]
)
ret.append(
sorted([
walk(ab) for ab in s.awaited_by
], key=lambda entry: entry[0])
)
return ret
buf = io.StringIO()
asyncio.print_call_graph(fut, file=buf, depth=depth+1)
stack = asyncio.capture_call_graph(fut, depth=depth)
return walk(stack), buf.getvalue()
class CallStackTestBase:
async def test_stack_tgroup(self):
stack_for_c5 = None
def c5():
nonlocal stack_for_c5
stack_for_c5 = capture_test_stack(depth=2)
async def c4():
await asyncio.sleep(0)
c5()
async def c3():
await c4()
async def c2():
await c3()
async def c1(task):
await task
async def main():
async with asyncio.TaskGroup() as tg:
task = tg.create_task(c2(), name="c2_root")
tg.create_task(c1(task), name="sub_main_1")
tg.create_task(c1(task), name="sub_main_2")
await main()
self.assertEqual(stack_for_c5[0], [
# task name
'T<c2_root>',
# call stack
['s c5', 'a c4', 'a c3', 'a c2'],
# awaited by
[
['T<anon>',
['a _aexit', 'a __aexit__', 'a main', 'a test_stack_tgroup'], []
],
['T<sub_main_1>',
['a c1'],
[
['T<anon>',
['a _aexit', 'a __aexit__', 'a main', 'a test_stack_tgroup'], []
]
]
],
['T<sub_main_2>',
['a c1'],
[
['T<anon>',
['a _aexit', 'a __aexit__', 'a main', 'a test_stack_tgroup'], []
]
]
]
]
])
self.assertIn(
' async CallStackTestBase.test_stack_tgroup()',
stack_for_c5[1])
async def test_stack_async_gen(self):
stack_for_gen_nested_call = None
async def gen_nested_call():
nonlocal stack_for_gen_nested_call
stack_for_gen_nested_call = capture_test_stack()
async def gen():
for num in range(2):
yield num
if num == 1:
await gen_nested_call()
async def main():
async for el in gen():
pass
await main()
self.assertEqual(stack_for_gen_nested_call[0], [
'T<anon>',
[
's capture_test_stack',
'a gen_nested_call',
'ag gen',
'a main',
'a test_stack_async_gen'
],
[]
])
self.assertIn(
'async generator CallStackTestBase.test_stack_async_gen.<locals>.gen()',
stack_for_gen_nested_call[1])
async def test_stack_gather(self):
stack_for_deep = None
async def deep():
await asyncio.sleep(0)
nonlocal stack_for_deep
stack_for_deep = capture_test_stack()
async def c1():
await asyncio.sleep(0)
await deep()
async def c2():
await asyncio.sleep(0)
async def main():
await asyncio.gather(c1(), c2())
await main()
self.assertEqual(stack_for_deep[0], [
'T<anon>',
['s capture_test_stack', 'a deep', 'a c1'],
[
['T<anon>', ['a main', 'a test_stack_gather'], []]
]
])
async def test_stack_shield(self):
stack_for_shield = None
async def deep():
await asyncio.sleep(0)
nonlocal stack_for_shield
stack_for_shield = capture_test_stack()
async def c1():
await asyncio.sleep(0)
await deep()
async def main():
await asyncio.shield(c1())
await main()
self.assertEqual(stack_for_shield[0], [
'T<anon>',
['s capture_test_stack', 'a deep', 'a c1'],
[
['T<anon>', ['a main', 'a test_stack_shield'], []]
]
])
async def test_stack_timeout(self):
stack_for_inner = None
async def inner():
await asyncio.sleep(0)
nonlocal stack_for_inner
stack_for_inner = capture_test_stack()
async def c1():
async with asyncio.timeout(1):
await asyncio.sleep(0)
await inner()
async def main():
await asyncio.shield(c1())
await main()
self.assertEqual(stack_for_inner[0], [
'T<anon>',
['s capture_test_stack', 'a inner', 'a c1'],
[
['T<anon>', ['a main', 'a test_stack_timeout'], []]
]
])
async def test_stack_wait(self):
stack_for_inner = None
async def inner():
await asyncio.sleep(0)
nonlocal stack_for_inner
stack_for_inner = capture_test_stack()
async def c1():
async with asyncio.timeout(1):
await asyncio.sleep(0)
await inner()
async def c2():
for i in range(3):
await asyncio.sleep(0)
async def main(t1, t2):
while True:
_, pending = await asyncio.wait([t1, t2])
if not pending:
break
t1 = asyncio.create_task(c1())
t2 = asyncio.create_task(c2())
try:
await main(t1, t2)
finally:
await t1
await t2
self.assertEqual(stack_for_inner[0], [
'T<anon>',
['s capture_test_stack', 'a inner', 'a c1'],
[
['T<anon>',
['a _wait', 'a wait', 'a main', 'a test_stack_wait'],
[]
]
]
])
async def test_stack_task(self):
stack_for_inner = None
async def inner():
await asyncio.sleep(0)
nonlocal stack_for_inner
stack_for_inner = capture_test_stack()
async def c1():
await inner()
async def c2():
await asyncio.create_task(c1(), name='there there')
async def main():
await c2()
await main()
self.assertEqual(stack_for_inner[0], [
'T<there there>',
['s capture_test_stack', 'a inner', 'a c1'],
[['T<anon>', ['a c2', 'a main', 'a test_stack_task'], []]]
])
async def test_stack_future(self):
stack_for_fut = None
async def a2(fut):
await fut
async def a1(fut):
await a2(fut)
async def b1(fut):
await fut
async def main():
nonlocal stack_for_fut
fut = asyncio.Future()
async with asyncio.TaskGroup() as g:
g.create_task(a1(fut), name="task A")
g.create_task(b1(fut), name='task B')
for _ in range(5):
# Do a few iterations to ensure that both a1 and b1
# await on the future
await asyncio.sleep(0)
stack_for_fut = capture_test_stack(fut=fut)
fut.set_result(None)
await main()
self.assertEqual(stack_for_fut[0],
['F',
[],
[
['T<task A>',
['a a2', 'a a1'],
[['T<anon>', ['a test_stack_future'], []]]
],
['T<task B>',
['a b1'],
[['T<anon>', ['a test_stack_future'], []]]
],
]]
)
self.assertTrue(stack_for_fut[1].startswith('* Future(id='))
@unittest.skipIf(
not hasattr(asyncio.futures, "_c_future_add_to_awaited_by"),
"C-accelerated asyncio call graph backend missing",
)
class TestCallStackC(CallStackTestBase, unittest.IsolatedAsyncioTestCase):
def setUp(self):
futures = asyncio.futures
tasks = asyncio.tasks
self._Future = asyncio.Future
asyncio.Future = futures.Future = futures._CFuture
self._Task = asyncio.Task
asyncio.Task = tasks.Task = tasks._CTask
self._future_add_to_awaited_by = asyncio.future_add_to_awaited_by
futures.future_add_to_awaited_by = futures._c_future_add_to_awaited_by
asyncio.future_add_to_awaited_by = futures.future_add_to_awaited_by
self._future_discard_from_awaited_by = asyncio.future_discard_from_awaited_by
futures.future_discard_from_awaited_by = futures._c_future_discard_from_awaited_by
asyncio.future_discard_from_awaited_by = futures.future_discard_from_awaited_by
self._current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = tasks._c_current_task
def tearDown(self):
futures = asyncio.futures
tasks = asyncio.tasks
futures.future_discard_from_awaited_by = self._future_discard_from_awaited_by
asyncio.future_discard_from_awaited_by = self._future_discard_from_awaited_by
del self._future_discard_from_awaited_by
futures.future_add_to_awaited_by = self._future_add_to_awaited_by
asyncio.future_add_to_awaited_by = self._future_add_to_awaited_by
del self._future_add_to_awaited_by
asyncio.Task = self._Task
tasks.Task = self._Task
del self._Task
asyncio.Future = self._Future
futures.Future = self._Future
del self._Future
asyncio.current_task = asyncio.tasks.current_task = self._current_task
@unittest.skipIf(
not hasattr(asyncio.futures, "_py_future_add_to_awaited_by"),
"Pure Python asyncio call graph backend missing",
)
class TestCallStackPy(CallStackTestBase, unittest.IsolatedAsyncioTestCase):
def setUp(self):
futures = asyncio.futures
tasks = asyncio.tasks
self._Future = asyncio.Future
asyncio.Future = futures.Future = futures._PyFuture
self._Task = asyncio.Task
asyncio.Task = tasks.Task = tasks._PyTask
self._future_add_to_awaited_by = asyncio.future_add_to_awaited_by
futures.future_add_to_awaited_by = futures._py_future_add_to_awaited_by
asyncio.future_add_to_awaited_by = futures.future_add_to_awaited_by
self._future_discard_from_awaited_by = asyncio.future_discard_from_awaited_by
futures.future_discard_from_awaited_by = futures._py_future_discard_from_awaited_by
asyncio.future_discard_from_awaited_by = futures.future_discard_from_awaited_by
self._current_task = asyncio.current_task
asyncio.current_task = asyncio.tasks.current_task = tasks._py_current_task
def tearDown(self):
futures = asyncio.futures
tasks = asyncio.tasks
futures.future_discard_from_awaited_by = self._future_discard_from_awaited_by
asyncio.future_discard_from_awaited_by = self._future_discard_from_awaited_by
del self._future_discard_from_awaited_by
futures.future_add_to_awaited_by = self._future_add_to_awaited_by
asyncio.future_add_to_awaited_by = self._future_add_to_awaited_by
del self._future_add_to_awaited_by
asyncio.Task = self._Task
tasks.Task = self._Task
del self._Task
asyncio.Future = self._Future
futures.Future = self._Future
del self._Future
asyncio.current_task = asyncio.tasks.current_task = self._current_task
+2 -3
View File
@@ -126,9 +126,8 @@ class CoroutineTests(BaseTest):
def test_iscoroutinefunction(self):
async def foo(): pass
# TODO: RUSTPYTHON; no DeprecationWarning for iscoroutinefunction
# with self.assertWarns(DeprecationWarning):
self.assertTrue(asyncio.iscoroutinefunction(foo))
with self.assertWarns(DeprecationWarning):
self.assertTrue(asyncio.iscoroutinefunction(foo))
def test_async_def_coroutines(self):
async def bar():
+1 -1
View File
@@ -18,7 +18,7 @@ from test.test_asyncio import utils as test_utils
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
def close_transport(transport):
+6 -6
View File
@@ -7,7 +7,7 @@ import asyncio
def tearDownModule():
# not needed for the test file but added for uniformness with all other
# asyncio test files for the sake of unified cleanup
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class ProtocolsAbsTests(unittest.TestCase):
@@ -19,7 +19,7 @@ class ProtocolsAbsTests(unittest.TestCase):
self.assertIsNone(p.connection_lost(f))
self.assertIsNone(p.pause_writing())
self.assertIsNone(p.resume_writing())
self.assertFalse(hasattr(p, '__dict__'))
self.assertNotHasAttr(p, '__dict__')
def test_protocol(self):
f = mock.Mock()
@@ -30,7 +30,7 @@ class ProtocolsAbsTests(unittest.TestCase):
self.assertIsNone(p.eof_received())
self.assertIsNone(p.pause_writing())
self.assertIsNone(p.resume_writing())
self.assertFalse(hasattr(p, '__dict__'))
self.assertNotHasAttr(p, '__dict__')
def test_buffered_protocol(self):
f = mock.Mock()
@@ -41,7 +41,7 @@ class ProtocolsAbsTests(unittest.TestCase):
self.assertIsNone(p.buffer_updated(150))
self.assertIsNone(p.pause_writing())
self.assertIsNone(p.resume_writing())
self.assertFalse(hasattr(p, '__dict__'))
self.assertNotHasAttr(p, '__dict__')
def test_datagram_protocol(self):
f = mock.Mock()
@@ -50,7 +50,7 @@ class ProtocolsAbsTests(unittest.TestCase):
self.assertIsNone(dp.connection_lost(f))
self.assertIsNone(dp.error_received(f))
self.assertIsNone(dp.datagram_received(f, f))
self.assertFalse(hasattr(dp, '__dict__'))
self.assertNotHasAttr(dp, '__dict__')
def test_subprocess_protocol(self):
f = mock.Mock()
@@ -60,7 +60,7 @@ class ProtocolsAbsTests(unittest.TestCase):
self.assertIsNone(sp.pipe_data_received(1, f))
self.assertIsNone(sp.pipe_connection_lost(1, f))
self.assertIsNone(sp.process_exited())
self.assertFalse(hasattr(sp, '__dict__'))
self.assertNotHasAttr(sp, '__dict__')
if __name__ == '__main__':
+2 -2
View File
@@ -6,7 +6,7 @@ from types import GenericAlias
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class QueueBasicTests(unittest.IsolatedAsyncioTestCase):
@@ -18,7 +18,7 @@ class QueueBasicTests(unittest.IsolatedAsyncioTestCase):
appear in fn(Queue()).
"""
q = asyncio.Queue()
self.assertTrue(fn(q).startswith('<Queue'), fn(q))
self.assertStartsWith(fn(q), '<Queue')
id_is_present = hex(id(q)) in fn(q)
self.assertEqual(expect_id, id_is_present)
+28 -20
View File
@@ -12,14 +12,14 @@ from unittest.mock import patch
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
def interrupt_self():
_thread.interrupt_main()
class TestPolicy(asyncio.AbstractEventLoopPolicy):
class TestPolicy(asyncio.events._AbstractEventLoopPolicy):
def __init__(self, loop_factory):
self.loop_factory = loop_factory
@@ -61,15 +61,15 @@ class BaseTest(unittest.TestCase):
super().setUp()
policy = TestPolicy(self.new_loop)
asyncio.set_event_loop_policy(policy)
asyncio.events._set_event_loop_policy(policy)
def tearDown(self):
policy = asyncio.get_event_loop_policy()
policy = asyncio.events._get_event_loop_policy()
if policy.loop is not None:
self.assertTrue(policy.loop.is_closed())
self.assertTrue(policy.loop.shutdown_ag_run)
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
super().tearDown()
@@ -93,8 +93,8 @@ class RunTests(BaseTest):
def test_asyncio_run_only_coro(self):
for o in {1, lambda: None}:
with self.subTest(obj=o), \
self.assertRaisesRegex(ValueError,
'a coroutine was expected'):
self.assertRaisesRegex(TypeError,
'an awaitable is required'):
asyncio.run(o)
def test_asyncio_run_debug(self):
@@ -208,7 +208,7 @@ class RunTests(BaseTest):
await asyncio.sleep(0)
return 42
policy = asyncio.get_event_loop_policy()
policy = asyncio.events._get_event_loop_policy()
policy.set_event_loop = mock.Mock()
asyncio.run(main())
self.assertTrue(policy.set_event_loop.called)
@@ -259,7 +259,7 @@ class RunTests(BaseTest):
loop.set_task_factory(Task)
return loop
asyncio.set_event_loop_policy(TestPolicy(new_event_loop))
asyncio.events._set_event_loop_policy(TestPolicy(new_event_loop))
with self.assertRaises(asyncio.CancelledError):
asyncio.run(main())
@@ -319,19 +319,28 @@ class RunnerTests(BaseTest):
def test_run_non_coro(self):
with asyncio.Runner() as runner:
with self.assertRaisesRegex(
ValueError,
"a coroutine was expected"
TypeError,
"an awaitable is required"
):
runner.run(123)
def test_run_future(self):
with asyncio.Runner() as runner:
with self.assertRaisesRegex(
ValueError,
"a coroutine was expected"
):
fut = runner.get_loop().create_future()
runner.run(fut)
fut = runner.get_loop().create_future()
fut.set_result('done')
self.assertEqual('done', runner.run(fut))
def test_run_awaitable(self):
class MyAwaitable:
def __await__(self):
return self.run().__await__()
@staticmethod
async def run():
return 'done'
with asyncio.Runner() as runner:
self.assertEqual('done', runner.run(MyAwaitable()))
def test_explicit_close(self):
runner = asyncio.Runner()
@@ -393,8 +402,7 @@ class RunnerTests(BaseTest):
self.assertEqual(2, runner.run(get_context()).get(cvar))
# TODO: RUSTPYTHON - RuntimeWarning for unawaited coroutine not triggered
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - RuntimeWarning for unawaited coroutine not triggered
def test_recursive_run(self):
async def g():
pass
@@ -488,7 +496,7 @@ class RunnerTests(BaseTest):
async def coro():
pass
policy = asyncio.get_event_loop_policy()
policy = asyncio.events._get_event_loop_policy()
policy.set_event_loop = mock.Mock()
runner = asyncio.Runner()
runner.run(coro())
+40 -3
View File
@@ -24,7 +24,7 @@ MOCK_ANY = mock.ANY
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class TestBaseSelectorEventLoop(BaseSelectorEventLoop):
@@ -347,6 +347,18 @@ class BaseSelectorEventLoopTests(test_utils.TestCase):
selectors.EVENT_WRITE)])
self.loop._remove_writer.assert_called_with(1)
def test_accept_connection_zero_one(self):
for backlog in [0, 1]:
sock = mock.Mock()
sock.accept.return_value = (mock.Mock(), mock.Mock())
with self.subTest(backlog):
mock_obj = mock.patch.object
with mock_obj(self.loop, '_accept_connection2') as accept2_mock:
self.loop._accept_connection(
mock.Mock(), sock, backlog=backlog)
self.loop.run_until_complete(asyncio.sleep(0))
self.assertEqual(sock.accept.call_count, backlog + 1)
def test_accept_connection_multiple(self):
sock = mock.Mock()
sock.accept.return_value = (mock.Mock(), mock.Mock())
@@ -362,8 +374,33 @@ class BaseSelectorEventLoopTests(test_utils.TestCase):
self.loop._accept_connection(
mock.Mock(), sock, backlog=backlog)
self.loop.run_until_complete(asyncio.sleep(0))
self.assertEqual(sock.accept.call_count, backlog)
self.assertEqual(sock.accept.call_count, backlog + 1)
def test_accept_connection_skip_connectionabortederror(self):
sock = mock.Mock()
def mock_sock_accept():
# mock accept(2) returning -ECONNABORTED every-other
# time that it's called. This applies most to OpenBSD
# whose sockets generate this errno more reproducibly than
# Linux and other OS.
if sock.accept.call_count % 2 == 0:
raise ConnectionAbortedError
return (mock.Mock(), mock.Mock())
sock.accept.side_effect = mock_sock_accept
backlog = 100
# test that _accept_connection's loop calls sock.accept
# all 100 times, continuing past ConnectionAbortedError
# instead of unnecessarily returning early
mock_obj = mock.patch.object
with mock_obj(self.loop, '_accept_connection2') as accept2_mock:
self.loop._accept_connection(
mock.Mock(), sock, backlog=backlog)
# as in test_accept_connection_multiple avoid task pending
# warnings by using asyncio.sleep(0)
self.loop.run_until_complete(asyncio.sleep(0))
self.assertEqual(sock.accept.call_count, backlog + 1)
class SelectorTransportTests(test_utils.TestCase):
@@ -1077,7 +1114,7 @@ class SelectorSocketTransportTests(test_utils.TestCase):
self.assertFalse(self.protocol.connection_lost.called)
self.loop.writers[7]._run()
# during this ^ run, the _resume_writing mock above was called and added more data
# during this ^ run, the _resume_writing mock above was called and added more data
self.assertEqual(transport.get_write_buffer_size(), 2)
self.loop.writers[7]._run()
+1 -1
View File
@@ -22,7 +22,7 @@ except ImportError:
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class MySendfileProto(asyncio.Protocol):
+1 -1
View File
@@ -11,7 +11,7 @@ from test.test_asyncio import functional as func_tests
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class BaseStartServer(func_tests.FunctionalTestCaseMixin):
+8 -8
View File
@@ -15,7 +15,7 @@ if socket_helper.tcp_blackhole():
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class MyProto(asyncio.Protocol):
@@ -110,7 +110,7 @@ class BaseSockTestsMixin:
self.loop.run_until_complete(
self.loop.sock_recv(sock, 1024))
sock.close()
self.assertTrue(data.startswith(b'HTTP/1.0 200 OK'))
self.assertStartsWith(data, b'HTTP/1.0 200 OK')
def _basetest_sock_recv_into(self, httpd, sock):
# same as _basetest_sock_client_ops, but using sock_recv_into
@@ -127,7 +127,7 @@ class BaseSockTestsMixin:
self.loop.run_until_complete(
self.loop.sock_recv_into(sock, buf[nbytes:]))
sock.close()
self.assertTrue(data.startswith(b'HTTP/1.0 200 OK'))
self.assertStartsWith(data, b'HTTP/1.0 200 OK')
def test_sock_client_ops(self):
with test_utils.run_test_server() as httpd:
@@ -150,7 +150,7 @@ class BaseSockTestsMixin:
# consume data
await self.loop.sock_recv(sock, 1024)
self.assertTrue(data.startswith(b'HTTP/1.0 200 OK'))
self.assertStartsWith(data, b'HTTP/1.0 200 OK')
async def _basetest_sock_recv_into_racing(self, httpd, sock):
sock.setblocking(False)
@@ -168,7 +168,7 @@ class BaseSockTestsMixin:
nbytes = await self.loop.sock_recv_into(sock, buf[:1024])
# consume data
await self.loop.sock_recv_into(sock, buf[nbytes:])
self.assertTrue(data.startswith(b'HTTP/1.0 200 OK'))
self.assertStartsWith(data, b'HTTP/1.0 200 OK')
await task
@@ -217,7 +217,7 @@ class BaseSockTestsMixin:
sock.shutdown(socket.SHUT_WR)
data = await task
# ProactorEventLoop could deliver hello, so endswith is necessary
self.assertTrue(data.endswith(b'world'))
self.assertEndsWith(data, b'world')
# After the first connect attempt before the listener is ready,
# the socket needs time to "recover" to make the next connect call.
@@ -298,7 +298,7 @@ class BaseSockTestsMixin:
data = await self.loop.sock_recv(sock, DATA_SIZE)
# HTTP headers size is less than MTU,
# they are sent by the first packet always
self.assertTrue(data.startswith(b'HTTP/1.0 200 OK'))
self.assertStartsWith(data, b'HTTP/1.0 200 OK')
while data.find(b'\r\n\r\n') == -1:
data += await self.loop.sock_recv(sock, DATA_SIZE)
# Strip headers
@@ -351,7 +351,7 @@ class BaseSockTestsMixin:
data = bytes(buf[:nbytes])
# HTTP headers size is less than MTU,
# they are sent by the first packet always
self.assertTrue(data.startswith(b'HTTP/1.0 200 OK'))
self.assertStartsWith(data, b'HTTP/1.0 200 OK')
while data.find(b'\r\n\r\n') == -1:
nbytes = await self.loop.sock_recv_into(sock, buf)
data = bytes(buf[:nbytes])
+9 -14
View File
@@ -30,7 +30,7 @@ BUF_MULTIPLIER = 1024 if not MACOS else 64
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class MyBaseProto(asyncio.Protocol):
@@ -741,8 +741,7 @@ class TestSSL(test_utils.TestCase):
asyncio.wait_for(client(srv.addr),
timeout=support.SHORT_TIMEOUT))
# TODO: RUSTPYTHON - gc.collect() doesn't release SSLContext properly
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - gc.collect() doesn't release SSLContext properly
def test_create_connection_memory_leak(self):
HELLO_MSG = b'1' * self.PAYLOAD_SIZE
@@ -1624,8 +1623,7 @@ class TestSSL(test_utils.TestCase):
else:
self.fail('Unexpected ResourceWarning: {}'.format(cm.warning))
# TODO: RUSTPYTHON - gc.collect() doesn't release SSLContext properly
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - gc.collect() doesn't release SSLContext properly
def test_handshake_timeout_handler_leak(self):
s = socket.socket(socket.AF_INET)
s.bind(('127.0.0.1', 0))
@@ -1651,8 +1649,7 @@ class TestSSL(test_utils.TestCase):
# SSLProtocol should be DECREF to 0
self.assertIsNone(ctx())
# TODO: RUSTPYTHON - gc.collect() doesn't release SSLContext properly
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - gc.collect() doesn't release SSLContext properly
def test_shutdown_timeout_handler_leak(self):
loop = self.loop
@@ -1857,16 +1854,14 @@ class TestThreadedServer(SocketThread):
pass
finally:
super().stop()
def run(self):
try:
with self._sock:
self._sock.setblocking(False)
self._run()
finally:
self._sock.close()
self._s1.close()
self._s2.close()
def run(self):
self._sock.setblocking(False)
self._run()
def _run(self):
while self._active:
if self._clients >= self._max_clients:
+4 -7
View File
@@ -21,7 +21,7 @@ from test.test_asyncio import functional as func_tests
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
@unittest.skipIf(ssl is None, 'No ssl module')
@@ -282,8 +282,7 @@ class BaseStartTLS(func_tests.FunctionalTestCaseMixin):
with self.assertRaisesRegex(RuntimeError, 'empty buffer'):
protocols._feed_data_to_buffered_proto(proto, b'12345')
# TODO: RUSTPYTHON - gc.collect() doesn't release SSLContext properly
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - gc.collect() doesn't release SSLContext properly
def test_start_tls_client_reg_proto_1(self):
HELLO_MSG = b'1' * self.PAYLOAD_SIZE
@@ -350,8 +349,7 @@ class BaseStartTLS(func_tests.FunctionalTestCaseMixin):
support.gc_collect()
self.assertIsNone(client_context())
# TODO: RUSTPYTHON - gc.collect() doesn't release SSLContext properly
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - gc.collect() doesn't release SSLContext properly
def test_create_connection_memory_leak(self):
HELLO_MSG = b'1' * self.PAYLOAD_SIZE
@@ -670,8 +668,7 @@ class BaseStartTLS(func_tests.FunctionalTestCaseMixin):
self.loop.run_until_complete(main())
# TODO: RUSTPYTHON - gc.collect() doesn't release SSLContext properly
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; - gc.collect() doesn't release SSLContext properly
def test_handshake_timeout(self):
# bpo-29970: Check that a connection is aborted if handshake is not
# completed in timeout period, instead of remaining open indefinitely
+1 -1
View File
@@ -8,7 +8,7 @@ support.requires_working_socket(module=True)
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class StaggeredTests(unittest.IsolatedAsyncioTestCase):
+14 -64
View File
@@ -1,15 +1,12 @@
"""Tests for streams.py."""
import gc
import os
import queue
import pickle
import socket
import sys
import threading
import unittest
from unittest import mock
import warnings
try:
import ssl
except ImportError:
@@ -17,11 +14,11 @@ except ImportError:
import asyncio
from test.test_asyncio import utils as test_utils
from test.support import requires_subprocess, socket_helper
from test.support import socket_helper
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class StreamTests(test_utils.TestCase):
@@ -50,7 +47,7 @@ class StreamTests(test_utils.TestCase):
self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
f = reader.read()
data = self.loop.run_until_complete(f)
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
self.assertEndsWith(data, b'\r\n\r\nTest message')
writer.close()
self.assertEqual(messages, [])
@@ -75,7 +72,7 @@ class StreamTests(test_utils.TestCase):
writer.write(b'GET / HTTP/1.0\r\n\r\n')
f = reader.read()
data = self.loop.run_until_complete(f)
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
self.assertEndsWith(data, b'\r\n\r\nTest message')
writer.close()
self.assertEqual(messages, [])
@@ -822,52 +819,6 @@ class StreamTests(test_utils.TestCase):
self.assertEqual(msg1, b"hello world 1!\n")
self.assertEqual(msg2, b"hello world 2!\n")
@unittest.skipIf(sys.platform == 'win32', "Don't have pipes")
@requires_subprocess()
def test_read_all_from_pipe_reader(self):
# See asyncio issue 168. This test is derived from the example
# subprocess_attach_read_pipe.py, but we configure the
# StreamReader's limit so that twice it is less than the size
# of the data writer. Also we must explicitly attach a child
# watcher to the event loop.
code = """\
import os, sys
fd = int(sys.argv[1])
os.write(fd, b'data')
os.close(fd)
"""
rfd, wfd = os.pipe()
args = [sys.executable, '-c', code, str(wfd)]
pipe = open(rfd, 'rb', 0)
reader = asyncio.StreamReader(loop=self.loop, limit=1)
protocol = asyncio.StreamReaderProtocol(reader, loop=self.loop)
transport, _ = self.loop.run_until_complete(
self.loop.connect_read_pipe(lambda: protocol, pipe))
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = asyncio.SafeChildWatcher()
watcher.attach_loop(self.loop)
try:
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
asyncio.set_child_watcher(watcher)
create = asyncio.create_subprocess_exec(
*args,
pass_fds={wfd},
)
proc = self.loop.run_until_complete(create)
self.loop.run_until_complete(proc.wait())
finally:
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
asyncio.set_child_watcher(None)
os.close(wfd)
data = self.loop.run_until_complete(reader.read(-1))
self.assertEqual(data, b'data')
def test_streamreader_constructor_without_loop(self):
with self.assertRaisesRegex(RuntimeError, 'no current event loop'):
asyncio.StreamReader()
@@ -1048,7 +999,7 @@ os.close(fd)
self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
f = rd.read()
data = self.loop.run_until_complete(f)
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
self.assertEndsWith(data, b'\r\n\r\nTest message')
self.assertFalse(wr.is_closing())
wr.close()
self.assertTrue(wr.is_closing())
@@ -1074,7 +1025,7 @@ os.close(fd)
data = await rd.readline()
self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
data = await rd.read()
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
self.assertEndsWith(data, b'\r\n\r\nTest message')
wr.close()
await wr.wait_closed()
@@ -1094,7 +1045,7 @@ os.close(fd)
data = await rd.readline()
self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
data = await rd.read()
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
self.assertEndsWith(data, b'\r\n\r\nTest message')
wr.close()
with self.assertRaises(ConnectionResetError):
wr.write(b'data')
@@ -1135,12 +1086,12 @@ os.close(fd)
data = await rd.readline()
self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
data = await rd.read()
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
self.assertEndsWith(data, b'\r\n\r\nTest message')
with self.assertWarns(ResourceWarning) as cm:
del wr
gc.collect()
self.assertEqual(len(cm.warnings), 1)
self.assertTrue(str(cm.warnings[0].message).startswith("unclosed <StreamWriter"))
self.assertStartsWith(str(cm.warnings[0].message), "unclosed <StreamWriter")
messages = []
self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))
@@ -1158,7 +1109,7 @@ os.close(fd)
data = await rd.readline()
self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
data = await rd.read()
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
self.assertEndsWith(data, b'\r\n\r\nTest message')
# Make "loop is closed" occur first before "del wr" for this test.
self.loop.stop()
@@ -1190,7 +1141,7 @@ os.close(fd)
del wr
gc.collect()
self.assertEqual(len(cm.warnings), 1)
self.assertTrue(str(cm.warnings[0].message).startswith("unclosed <StreamWriter"))
self.assertStartsWith(str(cm.warnings[0].message), "unclosed <StreamWriter")
async def outer():
srv = await asyncio.start_server(inner, socket_helper.HOSTv4, 0)
@@ -1246,7 +1197,6 @@ os.close(fd)
messages = self._basetest_unhandled_exceptions(handle_echo)
self.assertEqual(messages, [])
@unittest.skip("RUSTPYTHON: gc.get_referrers not implemented")
def test_open_connection_happy_eyeball_refcycles(self):
port = socket_helper.find_unused_port()
async def main():
@@ -1261,9 +1211,9 @@ os.close(fd)
# can't use assertRaises because that clears frames
exc = excs.exceptions[0]
self.assertIsNotNone(exc)
self.assertListEqual(gc.get_referrers(exc), [])
asyncio.run(main())
self.assertListEqual(gc.get_referrers(exc), [main_coro])
main_coro = main()
asyncio.run(main_coro)
if __name__ == '__main__':
+19 -114
View File
@@ -37,7 +37,7 @@ PROGRAM_CAT = [
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class TestSubprocessTransport(base_subprocess.BaseSubprocessTransport):
@@ -631,15 +631,6 @@ class SubprocessMixin:
# the transport was not notified yet
self.assertFalse(killed)
# Unlike SafeChildWatcher, FastChildWatcher does not pop the
# callbacks if waitpid() is called elsewhere. Let's clear them
# manually to avoid a warning when the watcher is detached.
if (sys.platform != 'win32' and
isinstance(self, SubprocessFastWatcherTests)):
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
asyncio.get_child_watcher()._callbacks.clear()
async def _test_popen_error(self, stdin):
if sys.platform == 'win32':
target = 'asyncio.windows_utils.Popen'
@@ -792,7 +783,7 @@ class SubprocessMixin:
def test_subprocess_protocol_events(self):
# gh-108973: Test that all subprocess protocol methods are called.
# The protocol methods are not called in a determistic order.
# The protocol methods are not called in a deterministic order.
# The order depends on the event loop and the operating system.
events = []
fds = [1, 2]
@@ -931,55 +922,31 @@ if sys.platform != 'win32':
# Unix
class SubprocessWatcherMixin(SubprocessMixin):
Watcher = None
def setUp(self):
super().setUp()
policy = asyncio.get_event_loop_policy()
self.loop = policy.new_event_loop()
self.loop = asyncio.new_event_loop()
self.set_event_loop(self.loop)
watcher = self._get_watcher()
watcher.attach_loop(self.loop)
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
policy.set_child_watcher(watcher)
def test_watcher_implementation(self):
loop = self.loop
watcher = loop._watcher
if unix_events.can_use_pidfd():
self.assertIsInstance(watcher, unix_events._PidfdChildWatcher)
else:
self.assertIsInstance(watcher, unix_events._ThreadedChildWatcher)
def tearDown(self):
super().tearDown()
policy = asyncio.get_event_loop_policy()
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = policy.get_child_watcher()
policy.set_child_watcher(None)
watcher.attach_loop(None)
watcher.close()
class SubprocessThreadedWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase):
def setUp(self):
self._original_can_use_pidfd = unix_events.can_use_pidfd
# Force the use of the threaded child watcher
unix_events.can_use_pidfd = mock.Mock(return_value=False)
super().setUp()
def _get_watcher(self):
return unix_events.ThreadedChildWatcher()
class SubprocessSafeWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase):
def _get_watcher(self):
with self.assertWarns(DeprecationWarning):
return unix_events.SafeChildWatcher()
class MultiLoopChildWatcherTests(test_utils.TestCase):
def test_warns(self):
with self.assertWarns(DeprecationWarning):
unix_events.MultiLoopChildWatcher()
class SubprocessFastWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase):
def _get_watcher(self):
with self.assertWarns(DeprecationWarning):
return unix_events.FastChildWatcher()
def tearDown(self):
unix_events.can_use_pidfd = self._original_can_use_pidfd
return super().tearDown()
@unittest.skipUnless(
unix_events.can_use_pidfd(),
@@ -988,70 +955,8 @@ if sys.platform != 'win32':
class SubprocessPidfdWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase):
def _get_watcher(self):
return unix_events.PidfdChildWatcher()
pass
class GenericWatcherTests(test_utils.TestCase):
def test_create_subprocess_fails_with_inactive_watcher(self):
watcher = mock.create_autospec(asyncio.AbstractChildWatcher)
watcher.is_active.return_value = False
async def execute():
asyncio.set_child_watcher(watcher)
with self.assertRaises(RuntimeError):
await subprocess.create_subprocess_exec(
os_helper.FakePath(sys.executable), '-c', 'pass')
watcher.add_child_handler.assert_not_called()
with asyncio.Runner(loop_factory=asyncio.new_event_loop) as runner:
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
self.assertIsNone(runner.run(execute()))
self.assertListEqual(watcher.mock_calls, [
mock.call.__enter__(),
mock.call.is_active(),
mock.call.__exit__(RuntimeError, mock.ANY, mock.ANY),
], watcher.mock_calls)
@unittest.skipUnless(
unix_events.can_use_pidfd(),
"operating system does not support pidfds",
)
def test_create_subprocess_with_pidfd(self):
async def in_thread():
proc = await asyncio.create_subprocess_exec(
*PROGRAM_CAT,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
stdout, stderr = await proc.communicate(b"some data")
return proc.returncode, stdout
async def main():
# asyncio.Runner did not call asyncio.set_event_loop()
with warnings.catch_warnings():
warnings.simplefilter('error', DeprecationWarning)
# get_event_loop() raises DeprecationWarning if
# set_event_loop() was never called and RuntimeError if
# it was called at least once.
with self.assertRaises((RuntimeError, DeprecationWarning)):
asyncio.get_event_loop_policy().get_event_loop()
return await asyncio.to_thread(asyncio.run, in_thread())
with self.assertWarns(DeprecationWarning):
asyncio.set_child_watcher(asyncio.PidfdChildWatcher())
try:
with asyncio.Runner(loop_factory=asyncio.new_event_loop) as runner:
returncode, stdout = runner.run(main())
self.assertEqual(returncode, 0)
self.assertEqual(stdout, b'some data')
finally:
with self.assertWarns(DeprecationWarning):
asyncio.set_child_watcher(None)
else:
# Windows
class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase):
+28 -13
View File
@@ -15,7 +15,7 @@ from test.test_asyncio.utils import await_without_task
# To prevent a warning "test altered the execution environment"
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class MyExc(Exception):
@@ -30,6 +30,15 @@ def get_error_types(eg):
return {type(exc) for exc in eg.exceptions}
def no_other_refs():
# due to gh-124392 coroutines now refer to their locals
coro = asyncio.current_task().get_coro()
frame = sys._getframe(1)
while coro.cr_frame != frame:
coro = coro.cr_await
return [coro]
def set_gc_state(enabled):
was_enabled = gc.isenabled()
if enabled:
@@ -918,7 +927,6 @@ class BaseTestTaskGroup:
await outer()
@unittest.skip("RUSTPYTHON: gc.get_referrers not implemented")
async def test_exception_refcycles_direct(self):
"""Test that TaskGroup doesn't keep a reference to the raised ExceptionGroup"""
tg = asyncio.TaskGroup()
@@ -934,10 +942,9 @@ class BaseTestTaskGroup:
exc = e
self.assertIsNotNone(exc)
self.assertListEqual(gc.get_referrers(exc), [])
self.assertListEqual(gc.get_referrers(exc), no_other_refs())
@unittest.skip("RUSTPYTHON: gc.get_referrers not implemented")
async def test_exception_refcycles_errors(self):
"""Test that TaskGroup deletes self._errors, and __aexit__ args"""
tg = asyncio.TaskGroup()
@@ -953,10 +960,9 @@ class BaseTestTaskGroup:
exc = excs.exceptions[0]
self.assertIsInstance(exc, _Done)
self.assertListEqual(gc.get_referrers(exc), [])
self.assertListEqual(gc.get_referrers(exc), no_other_refs())
@unittest.skip("RUSTPYTHON: gc.get_referrers not implemented")
async def test_exception_refcycles_parent_task(self):
"""Test that TaskGroup deletes self._parent_task"""
tg = asyncio.TaskGroup()
@@ -976,10 +982,9 @@ class BaseTestTaskGroup:
exc = excs.exceptions[0].exceptions[0]
self.assertIsInstance(exc, _Done)
self.assertListEqual(gc.get_referrers(exc), [])
self.assertListEqual(gc.get_referrers(exc), no_other_refs())
@unittest.skip("RUSTPYTHON: gc.get_referrers not implemented")
async def test_exception_refcycles_parent_task_wr(self):
"""Test that TaskGroup deletes self._parent_task and create_task() deletes task"""
tg = asyncio.TaskGroup()
@@ -1001,9 +1006,8 @@ class BaseTestTaskGroup:
self.assertIsNone(task_wr())
self.assertIsInstance(exc, _Done)
self.assertListEqual(gc.get_referrers(exc), [])
self.assertListEqual(gc.get_referrers(exc), no_other_refs())
@unittest.skip("RUSTPYTHON: gc.get_referrers not implemented")
async def test_exception_refcycles_propagate_cancellation_error(self):
"""Test that TaskGroup deletes propagate_cancellation_error"""
tg = asyncio.TaskGroup()
@@ -1017,9 +1021,8 @@ class BaseTestTaskGroup:
exc = e.__cause__
self.assertIsInstance(exc, asyncio.CancelledError)
self.assertListEqual(gc.get_referrers(exc), [])
self.assertListEqual(gc.get_referrers(exc), no_other_refs())
@unittest.skip("RUSTPYTHON: gc.get_referrers not implemented")
async def test_exception_refcycles_base_error(self):
"""Test that TaskGroup deletes self._base_error"""
class MyKeyboardInterrupt(KeyboardInterrupt):
@@ -1035,7 +1038,19 @@ class BaseTestTaskGroup:
exc = e
self.assertIsNotNone(exc)
self.assertListEqual(gc.get_referrers(exc), [])
self.assertListEqual(gc.get_referrers(exc), no_other_refs())
async def test_name(self):
name = None
async def asyncfn():
nonlocal name
name = asyncio.current_task().get_name()
async with asyncio.TaskGroup() as tg:
tg.create_task(asyncfn(), name="example name")
self.assertEqual(name, "example name")
async def test_cancels_task_if_created_during_creation(self):
+175 -48
View File
@@ -20,10 +20,11 @@ from asyncio import tasks
from test.test_asyncio import utils as test_utils
from test import support
from test.support.script_helper import assert_python_ok
from test.support.warnings_helper import ignore_warnings
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
async def coroutine_function():
@@ -86,9 +87,10 @@ class BaseTaskTests:
Task = None
Future = None
all_tasks = None
def new_task(self, loop, coro, name='TestTask', context=None):
return self.__class__.Task(coro, loop=loop, name=name, context=context)
def new_task(self, loop, coro, name='TestTask', context=None, eager_start=None):
return self.__class__.Task(coro, loop=loop, name=name, context=context, eager_start=eager_start)
def new_future(self, loop):
return self.__class__.Future(loop=loop)
@@ -108,7 +110,7 @@ class BaseTaskTests:
async def coro():
pass
t = self.new_task(self.loop, coro())
self.assertTrue(hasattr(t, '_cancel_message'))
self.assertHasAttr(t, '_cancel_message')
self.assertEqual(t._cancel_message, None)
t.cancel('my message')
@@ -544,7 +546,7 @@ class BaseTaskTests:
try:
await asyncio.sleep(10)
except asyncio.CancelledError:
asyncio.current_task().uncancel()
self.current_task().uncancel()
await asyncio.sleep(10)
try:
@@ -596,7 +598,7 @@ class BaseTaskTests:
loop = asyncio.new_event_loop()
async def make_request_with_timeout(*, sleep: float, timeout: float):
task = asyncio.current_task()
task = self.current_task()
loop = task.get_loop()
timed_out = False
@@ -1938,6 +1940,7 @@ class BaseTaskTests:
self.assertFalse(task.cancelled())
self.assertIs(task.exception(), base_exc)
@ignore_warnings(category=DeprecationWarning)
def test_iscoroutinefunction(self):
def fn():
pass
@@ -1955,6 +1958,7 @@ class BaseTaskTests:
self.assertFalse(asyncio.iscoroutinefunction(mock.Mock()))
self.assertTrue(asyncio.iscoroutinefunction(mock.AsyncMock()))
@ignore_warnings(category=DeprecationWarning)
def test_coroutine_non_gen_function(self):
async def func():
return 'test'
@@ -1983,41 +1987,41 @@ class BaseTaskTests:
self.assertIsNone(t2.result())
def test_current_task(self):
self.assertIsNone(asyncio.current_task(loop=self.loop))
self.assertIsNone(self.current_task(loop=self.loop))
async def coro(loop):
self.assertIs(asyncio.current_task(), task)
self.assertIs(self.current_task(), task)
self.assertIs(asyncio.current_task(None), task)
self.assertIs(asyncio.current_task(), task)
self.assertIs(self.current_task(None), task)
self.assertIs(self.current_task(), task)
task = self.new_task(self.loop, coro(self.loop))
self.loop.run_until_complete(task)
self.assertIsNone(asyncio.current_task(loop=self.loop))
self.assertIsNone(self.current_task(loop=self.loop))
def test_current_task_with_interleaving_tasks(self):
self.assertIsNone(asyncio.current_task(loop=self.loop))
self.assertIsNone(self.current_task(loop=self.loop))
fut1 = self.new_future(self.loop)
fut2 = self.new_future(self.loop)
async def coro1(loop):
self.assertTrue(asyncio.current_task() is task1)
self.assertTrue(self.current_task() is task1)
await fut1
self.assertTrue(asyncio.current_task() is task1)
self.assertTrue(self.current_task() is task1)
fut2.set_result(True)
async def coro2(loop):
self.assertTrue(asyncio.current_task() is task2)
self.assertTrue(self.current_task() is task2)
fut1.set_result(True)
await fut2
self.assertTrue(asyncio.current_task() is task2)
self.assertTrue(self.current_task() is task2)
task1 = self.new_task(self.loop, coro1(self.loop))
task2 = self.new_task(self.loop, coro2(self.loop))
self.loop.run_until_complete(asyncio.wait((task1, task2)))
self.assertIsNone(asyncio.current_task(loop=self.loop))
self.assertIsNone(self.current_task(loop=self.loop))
# Some thorough tests for cancellation propagation through
# coroutines, tasks and wait().
@@ -2112,6 +2116,46 @@ class BaseTaskTests:
self.assertTrue(outer.cancelled())
self.assertEqual(0, 0 if outer._callbacks is None else len(outer._callbacks))
def test_shield_cancel_outer_result(self):
mock_handler = mock.Mock()
self.loop.set_exception_handler(mock_handler)
inner = self.new_future(self.loop)
outer = asyncio.shield(inner)
test_utils.run_briefly(self.loop)
outer.cancel()
test_utils.run_briefly(self.loop)
inner.set_result(1)
test_utils.run_briefly(self.loop)
mock_handler.assert_not_called()
def test_shield_cancel_outer_exception(self):
mock_handler = mock.Mock()
self.loop.set_exception_handler(mock_handler)
inner = self.new_future(self.loop)
outer = asyncio.shield(inner)
test_utils.run_briefly(self.loop)
outer.cancel()
test_utils.run_briefly(self.loop)
inner.set_exception(Exception('foo'))
test_utils.run_briefly(self.loop)
mock_handler.assert_called_once()
def test_shield_duplicate_log_once(self):
mock_handler = mock.Mock()
self.loop.set_exception_handler(mock_handler)
inner = self.new_future(self.loop)
outer = asyncio.shield(inner)
test_utils.run_briefly(self.loop)
outer.cancel()
test_utils.run_briefly(self.loop)
outer = asyncio.shield(inner)
test_utils.run_briefly(self.loop)
outer.cancel()
test_utils.run_briefly(self.loop)
inner.set_exception(Exception('foo'))
test_utils.run_briefly(self.loop)
mock_handler.assert_called_once()
def test_shield_shortcut(self):
fut = self.new_future(self.loop)
fut.set_result(42)
@@ -2249,9 +2293,7 @@ class BaseTaskTests:
self.assertRaises(ValueError, self.loop.run_until_complete,
asyncio.wait([]))
@unittest.skip("RUSTPYTHON: requires cyclic garbage collection")
def test_log_destroyed_pending_task(self):
Task = self.__class__.Task
async def kill_me(loop):
future = self.new_future(loop)
@@ -2266,14 +2308,12 @@ class BaseTaskTests:
# schedule the task
coro = kill_me(self.loop)
task = asyncio.ensure_future(coro, loop=self.loop)
task = self.new_task(self.loop, coro)
self.assertEqual(asyncio.all_tasks(loop=self.loop), {task})
asyncio.set_event_loop(None)
self.assertEqual(self.all_tasks(loop=self.loop), {task})
# execute the task so it waits for future
self.loop._run_once()
self.loop.run_until_complete(asyncio.sleep(0))
self.assertEqual(len(self.loop._ready), 0)
coro = None
@@ -2283,14 +2323,36 @@ class BaseTaskTests:
# no more reference to kill_me() task: the task is destroyed by the GC
support.gc_collect()
self.assertEqual(asyncio.all_tasks(loop=self.loop), set())
mock_handler.assert_called_with(self.loop, {
'message': 'Task was destroyed but it is pending!',
'task': mock.ANY,
'source_traceback': source_traceback,
})
mock_handler.reset_mock()
# task got resurrected by the exception handler
support.gc_collect()
self.assertEqual(self.all_tasks(loop=self.loop), set())
def test_task_not_crash_without_finalization(self):
Task = self.__class__.Task
class Subclass(Task):
def __del__(self):
pass
async def corofn():
await asyncio.sleep(0.01)
coro = corofn()
task = Subclass(coro, loop = self.loop)
task._log_destroy_pending = False
del task
support.gc_collect()
coro.close()
@mock.patch('asyncio.base_events.logger')
def test_tb_logger_not_called_after_cancel(self, m_log):
@@ -2432,7 +2494,7 @@ class BaseTaskTests:
message = m_log.error.call_args[0][0]
self.assertIn('Task was destroyed but it is pending', message)
self.assertEqual(asyncio.all_tasks(self.loop), set())
self.assertEqual(self.all_tasks(self.loop), set())
def test_create_task_with_noncoroutine(self):
with self.assertRaisesRegex(TypeError,
@@ -2664,6 +2726,35 @@ class BaseTaskTests:
self.assertEqual([None, 1, 2], ret)
def test_eager_start_true(self):
name = None
async def asyncfn():
nonlocal name
name = self.current_task().get_name()
async def main():
t = self.new_task(coro=asyncfn(), loop=asyncio.get_running_loop(), eager_start=True, name="example")
self.assertTrue(t.done())
self.assertEqual(name, "example")
await t
def test_eager_start_false(self):
name = None
async def asyncfn():
nonlocal name
name = self.current_task().get_name()
async def main():
t = self.new_task(coro=asyncfn(), loop=asyncio.get_running_loop(), eager_start=False, name="example")
self.assertFalse(t.done())
self.assertIsNone(name)
await t
self.assertEqual(name, "example")
asyncio.run(main(), loop_factory=asyncio.EventLoop)
def test_get_coro(self):
loop = asyncio.new_event_loop()
coro = coroutine_function()
@@ -2697,12 +2788,12 @@ class BaseTaskTests:
coro = coroutine_function()
with contextlib.closing(asyncio.EventLoop()) as loop:
task = asyncio.Task.__new__(asyncio.Task)
for _ in range(5):
with self.assertRaisesRegex(RuntimeError, 'break'):
task.__init__(coro, loop=loop, context=obj, name=Break())
coro.close()
task._log_destroy_pending = False
del task
self.assertEqual(sys.getrefcount(obj), initial_refcount)
@@ -2827,6 +2918,8 @@ class CTask_CFuture_Tests(BaseTaskTests, SetMethodsTest,
Task = getattr(tasks, '_CTask', None)
Future = getattr(futures, '_CFuture', None)
all_tasks = getattr(tasks, '_c_all_tasks', None)
current_task = staticmethod(getattr(tasks, '_c_current_task', None))
@support.refcount_test
def test_refleaks_in_task___init__(self):
@@ -2858,6 +2951,8 @@ class CTask_CFuture_SubclassTests(BaseTaskTests, test_utils.TestCase):
Task = getattr(tasks, '_CTask', None)
Future = getattr(futures, '_CFuture', None)
all_tasks = getattr(tasks, '_c_all_tasks', None)
current_task = staticmethod(getattr(tasks, '_c_current_task', None))
@unittest.skipUnless(hasattr(tasks, '_CTask'),
@@ -2867,6 +2962,8 @@ class CTaskSubclass_PyFuture_Tests(BaseTaskTests, test_utils.TestCase):
Task = getattr(tasks, '_CTask', None)
Future = futures._PyFuture
all_tasks = getattr(tasks, '_c_all_tasks', None)
current_task = staticmethod(getattr(tasks, '_c_current_task', None))
@unittest.skipUnless(hasattr(futures, '_CFuture'),
@@ -2876,6 +2973,8 @@ class PyTask_CFutureSubclass_Tests(BaseTaskTests, test_utils.TestCase):
Future = getattr(futures, '_CFuture', None)
Task = tasks._PyTask
all_tasks = staticmethod(tasks._py_all_tasks)
current_task = staticmethod(tasks._py_current_task)
@unittest.skipUnless(hasattr(tasks, '_CTask'),
@@ -2884,6 +2983,8 @@ class CTask_PyFuture_Tests(BaseTaskTests, test_utils.TestCase):
Task = getattr(tasks, '_CTask', None)
Future = futures._PyFuture
all_tasks = getattr(tasks, '_c_all_tasks', None)
current_task = staticmethod(getattr(tasks, '_c_current_task', None))
@unittest.skipUnless(hasattr(futures, '_CFuture'),
@@ -2892,6 +2993,8 @@ class PyTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase):
Task = tasks._PyTask
Future = getattr(futures, '_CFuture', None)
all_tasks = staticmethod(tasks._py_all_tasks)
current_task = staticmethod(tasks._py_current_task)
class PyTask_PyFuture_Tests(BaseTaskTests, SetMethodsTest,
@@ -2899,13 +3002,16 @@ class PyTask_PyFuture_Tests(BaseTaskTests, SetMethodsTest,
Task = tasks._PyTask
Future = futures._PyFuture
all_tasks = staticmethod(tasks._py_all_tasks)
current_task = staticmethod(tasks._py_current_task)
@add_subclass_tests
class PyTask_PyFuture_SubclassTests(BaseTaskTests, test_utils.TestCase):
Task = tasks._PyTask
Future = futures._PyFuture
all_tasks = staticmethod(tasks._py_all_tasks)
current_task = staticmethod(tasks._py_current_task)
@unittest.skipUnless(hasattr(tasks, '_CTask'),
'requires the C _asyncio module')
@@ -2938,6 +3044,7 @@ class BaseTaskIntrospectionTests:
_unregister_task = None
_enter_task = None
_leave_task = None
all_tasks = None
def test__register_task_1(self):
class TaskLike:
@@ -2951,9 +3058,9 @@ class BaseTaskIntrospectionTests:
task = TaskLike()
loop = mock.Mock()
self.assertEqual(asyncio.all_tasks(loop), set())
self.assertEqual(self.all_tasks(loop), set())
self._register_task(task)
self.assertEqual(asyncio.all_tasks(loop), {task})
self.assertEqual(self.all_tasks(loop), {task})
self._unregister_task(task)
def test__register_task_2(self):
@@ -2967,9 +3074,9 @@ class BaseTaskIntrospectionTests:
task = TaskLike()
loop = mock.Mock()
self.assertEqual(asyncio.all_tasks(loop), set())
self.assertEqual(self.all_tasks(loop), set())
self._register_task(task)
self.assertEqual(asyncio.all_tasks(loop), {task})
self.assertEqual(self.all_tasks(loop), {task})
self._unregister_task(task)
def test__register_task_3(self):
@@ -2983,52 +3090,68 @@ class BaseTaskIntrospectionTests:
task = TaskLike()
loop = mock.Mock()
self.assertEqual(asyncio.all_tasks(loop), set())
self.assertEqual(self.all_tasks(loop), set())
self._register_task(task)
self.assertEqual(asyncio.all_tasks(loop), set())
self.assertEqual(self.all_tasks(loop), set())
self._unregister_task(task)
def test__enter_task(self):
task = mock.Mock()
loop = mock.Mock()
self.assertIsNone(asyncio.current_task(loop))
# _enter_task is called by Task.__step while the loop
# is running, so set the loop as the running loop
# for a more realistic test.
asyncio._set_running_loop(loop)
self.assertIsNone(self.current_task(loop))
self._enter_task(loop, task)
self.assertIs(asyncio.current_task(loop), task)
self.assertIs(self.current_task(loop), task)
self._leave_task(loop, task)
asyncio._set_running_loop(None)
def test__enter_task_failure(self):
task1 = mock.Mock()
task2 = mock.Mock()
loop = mock.Mock()
asyncio._set_running_loop(loop)
self._enter_task(loop, task1)
with self.assertRaises(RuntimeError):
self._enter_task(loop, task2)
self.assertIs(asyncio.current_task(loop), task1)
self.assertIs(self.current_task(loop), task1)
self._leave_task(loop, task1)
asyncio._set_running_loop(None)
def test__leave_task(self):
task = mock.Mock()
loop = mock.Mock()
asyncio._set_running_loop(loop)
self._enter_task(loop, task)
self._leave_task(loop, task)
self.assertIsNone(asyncio.current_task(loop))
self.assertIsNone(self.current_task(loop))
asyncio._set_running_loop(None)
def test__leave_task_failure1(self):
task1 = mock.Mock()
task2 = mock.Mock()
loop = mock.Mock()
# _leave_task is called by Task.__step while the loop
# is running, so set the loop as the running loop
# for a more realistic test.
asyncio._set_running_loop(loop)
self._enter_task(loop, task1)
with self.assertRaises(RuntimeError):
self._leave_task(loop, task2)
self.assertIs(asyncio.current_task(loop), task1)
self.assertIs(self.current_task(loop), task1)
self._leave_task(loop, task1)
asyncio._set_running_loop(None)
def test__leave_task_failure2(self):
task = mock.Mock()
loop = mock.Mock()
asyncio._set_running_loop(loop)
with self.assertRaises(RuntimeError):
self._leave_task(loop, task)
self.assertIsNone(asyncio.current_task(loop))
self.assertIsNone(self.current_task(loop))
asyncio._set_running_loop(None)
def test__unregister_task(self):
task = mock.Mock()
@@ -3036,13 +3159,13 @@ class BaseTaskIntrospectionTests:
task.get_loop = lambda: loop
self._register_task(task)
self._unregister_task(task)
self.assertEqual(asyncio.all_tasks(loop), set())
self.assertEqual(self.all_tasks(loop), set())
def test__unregister_task_not_registered(self):
task = mock.Mock()
loop = mock.Mock()
self._unregister_task(task)
self.assertEqual(asyncio.all_tasks(loop), set())
self.assertEqual(self.all_tasks(loop), set())
class PyIntrospectionTests(test_utils.TestCase, BaseTaskIntrospectionTests):
@@ -3050,6 +3173,8 @@ class PyIntrospectionTests(test_utils.TestCase, BaseTaskIntrospectionTests):
_unregister_task = staticmethod(tasks._py_unregister_task)
_enter_task = staticmethod(tasks._py_enter_task)
_leave_task = staticmethod(tasks._py_leave_task)
all_tasks = staticmethod(tasks._py_all_tasks)
current_task = staticmethod(tasks._py_current_task)
@unittest.skipUnless(hasattr(tasks, '_c_register_task'),
@@ -3060,6 +3185,8 @@ class CIntrospectionTests(test_utils.TestCase, BaseTaskIntrospectionTests):
_unregister_task = staticmethod(tasks._c_unregister_task)
_enter_task = staticmethod(tasks._c_enter_task)
_leave_task = staticmethod(tasks._c_leave_task)
all_tasks = staticmethod(tasks._c_all_tasks)
current_task = staticmethod(tasks._c_current_task)
else:
_register_task = _unregister_task = _enter_task = _leave_task = None
@@ -3117,7 +3244,7 @@ class CCurrentLoopTests(BaseCurrentLoopTests, test_utils.TestCase):
class GenericTaskTests(test_utils.TestCase):
def test_future_subclass(self):
self.assertTrue(issubclass(asyncio.Task, asyncio.Future))
self.assertIsSubclass(asyncio.Task, asyncio.Future)
@support.cpython_only
def test_asyncio_module_compiled(self):
@@ -3126,14 +3253,14 @@ class GenericTaskTests(test_utils.TestCase):
# fail on systems where C modules were successfully compiled
# (hence the test for _functools etc), but _asyncio somehow didn't.
try:
import _functools
import _json
import _pickle
import _functools # noqa: F401
import _json # noqa: F401
import _pickle # noqa: F401
except ImportError:
self.skipTest('C modules are not available')
else:
try:
import _asyncio
import _asyncio # noqa: F401
except ImportError:
self.fail('_asyncio module is missing')
+1 -1
View File
@@ -8,7 +8,7 @@ from unittest import mock
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class ToThreadTests(unittest.IsolatedAsyncioTestCase):
+2 -2
View File
@@ -9,7 +9,7 @@ from test.test_asyncio.utils import await_without_task
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class TimeoutTests(unittest.IsolatedAsyncioTestCase):
@@ -220,7 +220,7 @@ class TimeoutTests(unittest.IsolatedAsyncioTestCase):
# Pretend the loop is busy for a while.
time.sleep(0.1)
await asyncio.sleep(1)
# TimeoutError was cought by (2)
# TimeoutError was caught by (2)
await asyncio.sleep(10) # This sleep should be interrupted by (1)
t1 = loop.time()
self.assertTrue(t0 <= t1 <= t0 + 1)
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -10,7 +10,7 @@ from asyncio import transports
def tearDownModule():
# not needed for the test file but added for uniformness with all other
# asyncio test files for the sake of unified cleanup
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class TransportTests(unittest.TestCase):
+42 -703
View File
@@ -10,11 +10,9 @@ import signal
import socket
import stat
import sys
import threading
import time
import unittest
from unittest import mock
import warnings
from test import support
from test.support import os_helper
@@ -27,13 +25,12 @@ if sys.platform == 'win32':
import asyncio
from asyncio import log
from asyncio import unix_events
from test.test_asyncio import utils as test_utils
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
MOCK_ANY = mock.ANY
@@ -1112,697 +1109,6 @@ class UnixWritePipeTransportTests(test_utils.TestCase):
self.assertFalse(self.protocol.connection_lost.called)
class AbstractChildWatcherTests(unittest.TestCase):
def test_warns_on_subclassing(self):
with self.assertWarns(DeprecationWarning):
class MyWatcher(asyncio.AbstractChildWatcher):
pass
def test_not_implemented(self):
f = mock.Mock()
watcher = asyncio.AbstractChildWatcher()
self.assertRaises(
NotImplementedError, watcher.add_child_handler, f, f)
self.assertRaises(
NotImplementedError, watcher.remove_child_handler, f)
self.assertRaises(
NotImplementedError, watcher.attach_loop, f)
self.assertRaises(
NotImplementedError, watcher.close)
self.assertRaises(
NotImplementedError, watcher.is_active)
self.assertRaises(
NotImplementedError, watcher.__enter__)
self.assertRaises(
NotImplementedError, watcher.__exit__, f, f, f)
class BaseChildWatcherTests(unittest.TestCase):
def test_not_implemented(self):
f = mock.Mock()
watcher = unix_events.BaseChildWatcher()
self.assertRaises(
NotImplementedError, watcher._do_waitpid, f)
class ChildWatcherTestsMixin:
ignore_warnings = mock.patch.object(log.logger, "warning")
def setUp(self):
super().setUp()
self.loop = self.new_test_loop()
self.running = False
self.zombies = {}
with mock.patch.object(
self.loop, "add_signal_handler") as self.m_add_signal_handler:
self.watcher = self.create_watcher()
self.watcher.attach_loop(self.loop)
def waitpid(self, pid, flags):
if isinstance(self.watcher, asyncio.SafeChildWatcher) or pid != -1:
self.assertGreater(pid, 0)
try:
if pid < 0:
return self.zombies.popitem()
else:
return pid, self.zombies.pop(pid)
except KeyError:
pass
if self.running:
return 0, 0
else:
raise ChildProcessError()
def add_zombie(self, pid, status):
self.zombies[pid] = status
def waitstatus_to_exitcode(self, status):
if status > 32768:
return status - 32768
elif 32700 < status < 32768:
return status - 32768
else:
return status
def test_create_watcher(self):
self.m_add_signal_handler.assert_called_once_with(
signal.SIGCHLD, self.watcher._sig_chld)
def waitpid_mocks(func):
def wrapped_func(self):
def patch(target, wrapper):
return mock.patch(target, wraps=wrapper,
new_callable=mock.Mock)
with patch('asyncio.unix_events.waitstatus_to_exitcode', self.waitstatus_to_exitcode), \
patch('os.waitpid', self.waitpid) as m_waitpid:
func(self, m_waitpid)
return wrapped_func
@waitpid_mocks
def test_sigchld(self, m_waitpid):
# register a child
callback = mock.Mock()
with self.watcher:
self.running = True
self.watcher.add_child_handler(42, callback, 9, 10, 14)
self.assertFalse(callback.called)
# child is running
self.watcher._sig_chld()
self.assertFalse(callback.called)
# child terminates (returncode 12)
self.running = False
self.add_zombie(42, EXITCODE(12))
self.watcher._sig_chld()
callback.assert_called_once_with(42, 12, 9, 10, 14)
callback.reset_mock()
# ensure that the child is effectively reaped
self.add_zombie(42, EXITCODE(13))
with self.ignore_warnings:
self.watcher._sig_chld()
self.assertFalse(callback.called)
# sigchld called again
self.zombies.clear()
self.watcher._sig_chld()
self.assertFalse(callback.called)
@waitpid_mocks
def test_sigchld_two_children(self, m_waitpid):
callback1 = mock.Mock()
callback2 = mock.Mock()
# register child 1
with self.watcher:
self.running = True
self.watcher.add_child_handler(43, callback1, 7, 8)
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
# register child 2
with self.watcher:
self.watcher.add_child_handler(44, callback2, 147, 18)
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
# children are running
self.watcher._sig_chld()
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
# child 1 terminates (signal 3)
self.add_zombie(43, SIGNAL(3))
self.watcher._sig_chld()
callback1.assert_called_once_with(43, -3, 7, 8)
self.assertFalse(callback2.called)
callback1.reset_mock()
# child 2 still running
self.watcher._sig_chld()
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
# child 2 terminates (code 108)
self.add_zombie(44, EXITCODE(108))
self.running = False
self.watcher._sig_chld()
callback2.assert_called_once_with(44, 108, 147, 18)
self.assertFalse(callback1.called)
callback2.reset_mock()
# ensure that the children are effectively reaped
self.add_zombie(43, EXITCODE(14))
self.add_zombie(44, EXITCODE(15))
with self.ignore_warnings:
self.watcher._sig_chld()
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
# sigchld called again
self.zombies.clear()
self.watcher._sig_chld()
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
@waitpid_mocks
def test_sigchld_two_children_terminating_together(self, m_waitpid):
callback1 = mock.Mock()
callback2 = mock.Mock()
# register child 1
with self.watcher:
self.running = True
self.watcher.add_child_handler(45, callback1, 17, 8)
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
# register child 2
with self.watcher:
self.watcher.add_child_handler(46, callback2, 1147, 18)
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
# children are running
self.watcher._sig_chld()
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
# child 1 terminates (code 78)
# child 2 terminates (signal 5)
self.add_zombie(45, EXITCODE(78))
self.add_zombie(46, SIGNAL(5))
self.running = False
self.watcher._sig_chld()
callback1.assert_called_once_with(45, 78, 17, 8)
callback2.assert_called_once_with(46, -5, 1147, 18)
callback1.reset_mock()
callback2.reset_mock()
# ensure that the children are effectively reaped
self.add_zombie(45, EXITCODE(14))
self.add_zombie(46, EXITCODE(15))
with self.ignore_warnings:
self.watcher._sig_chld()
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
@waitpid_mocks
def test_sigchld_race_condition(self, m_waitpid):
# register a child
callback = mock.Mock()
with self.watcher:
# child terminates before being registered
self.add_zombie(50, EXITCODE(4))
self.watcher._sig_chld()
self.watcher.add_child_handler(50, callback, 1, 12)
callback.assert_called_once_with(50, 4, 1, 12)
callback.reset_mock()
# ensure that the child is effectively reaped
self.add_zombie(50, SIGNAL(1))
with self.ignore_warnings:
self.watcher._sig_chld()
self.assertFalse(callback.called)
@waitpid_mocks
def test_sigchld_replace_handler(self, m_waitpid):
callback1 = mock.Mock()
callback2 = mock.Mock()
# register a child
with self.watcher:
self.running = True
self.watcher.add_child_handler(51, callback1, 19)
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
# register the same child again
with self.watcher:
self.watcher.add_child_handler(51, callback2, 21)
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
# child terminates (signal 8)
self.running = False
self.add_zombie(51, SIGNAL(8))
self.watcher._sig_chld()
callback2.assert_called_once_with(51, -8, 21)
self.assertFalse(callback1.called)
callback2.reset_mock()
# ensure that the child is effectively reaped
self.add_zombie(51, EXITCODE(13))
with self.ignore_warnings:
self.watcher._sig_chld()
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
@waitpid_mocks
def test_sigchld_remove_handler(self, m_waitpid):
callback = mock.Mock()
# register a child
with self.watcher:
self.running = True
self.watcher.add_child_handler(52, callback, 1984)
self.assertFalse(callback.called)
# unregister the child
self.watcher.remove_child_handler(52)
self.assertFalse(callback.called)
# child terminates (code 99)
self.running = False
self.add_zombie(52, EXITCODE(99))
with self.ignore_warnings:
self.watcher._sig_chld()
self.assertFalse(callback.called)
@waitpid_mocks
def test_sigchld_unknown_status(self, m_waitpid):
callback = mock.Mock()
# register a child
with self.watcher:
self.running = True
self.watcher.add_child_handler(53, callback, -19)
self.assertFalse(callback.called)
# terminate with unknown status
self.zombies[53] = 1178
self.running = False
self.watcher._sig_chld()
callback.assert_called_once_with(53, 1178, -19)
callback.reset_mock()
# ensure that the child is effectively reaped
self.add_zombie(53, EXITCODE(101))
with self.ignore_warnings:
self.watcher._sig_chld()
self.assertFalse(callback.called)
@waitpid_mocks
def test_remove_child_handler(self, m_waitpid):
callback1 = mock.Mock()
callback2 = mock.Mock()
callback3 = mock.Mock()
# register children
with self.watcher:
self.running = True
self.watcher.add_child_handler(54, callback1, 1)
self.watcher.add_child_handler(55, callback2, 2)
self.watcher.add_child_handler(56, callback3, 3)
# remove child handler 1
self.assertTrue(self.watcher.remove_child_handler(54))
# remove child handler 2 multiple times
self.assertTrue(self.watcher.remove_child_handler(55))
self.assertFalse(self.watcher.remove_child_handler(55))
self.assertFalse(self.watcher.remove_child_handler(55))
# all children terminate
self.add_zombie(54, EXITCODE(0))
self.add_zombie(55, EXITCODE(1))
self.add_zombie(56, EXITCODE(2))
self.running = False
with self.ignore_warnings:
self.watcher._sig_chld()
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
callback3.assert_called_once_with(56, 2, 3)
@waitpid_mocks
def test_sigchld_unhandled_exception(self, m_waitpid):
callback = mock.Mock()
# register a child
with self.watcher:
self.running = True
self.watcher.add_child_handler(57, callback)
# raise an exception
m_waitpid.side_effect = ValueError
with mock.patch.object(log.logger,
'error') as m_error:
self.assertEqual(self.watcher._sig_chld(), None)
self.assertTrue(m_error.called)
@waitpid_mocks
def test_sigchld_child_reaped_elsewhere(self, m_waitpid):
# register a child
callback = mock.Mock()
with self.watcher:
self.running = True
self.watcher.add_child_handler(58, callback)
self.assertFalse(callback.called)
# child terminates
self.running = False
self.add_zombie(58, EXITCODE(4))
# waitpid is called elsewhere
os.waitpid(58, os.WNOHANG)
m_waitpid.reset_mock()
# sigchld
with self.ignore_warnings:
self.watcher._sig_chld()
if isinstance(self.watcher, asyncio.FastChildWatcher):
# here the FastChildWatcher enters a deadlock
# (there is no way to prevent it)
self.assertFalse(callback.called)
else:
callback.assert_called_once_with(58, 255)
@waitpid_mocks
def test_sigchld_unknown_pid_during_registration(self, m_waitpid):
# register two children
callback1 = mock.Mock()
callback2 = mock.Mock()
with self.ignore_warnings, self.watcher:
self.running = True
# child 1 terminates
self.add_zombie(591, EXITCODE(7))
# an unknown child terminates
self.add_zombie(593, EXITCODE(17))
self.watcher._sig_chld()
self.watcher.add_child_handler(591, callback1)
self.watcher.add_child_handler(592, callback2)
callback1.assert_called_once_with(591, 7)
self.assertFalse(callback2.called)
@waitpid_mocks
def test_set_loop(self, m_waitpid):
# register a child
callback = mock.Mock()
with self.watcher:
self.running = True
self.watcher.add_child_handler(60, callback)
# attach a new loop
old_loop = self.loop
self.loop = self.new_test_loop()
patch = mock.patch.object
with patch(old_loop, "remove_signal_handler") as m_old_remove, \
patch(self.loop, "add_signal_handler") as m_new_add:
self.watcher.attach_loop(self.loop)
m_old_remove.assert_called_once_with(
signal.SIGCHLD)
m_new_add.assert_called_once_with(
signal.SIGCHLD, self.watcher._sig_chld)
# child terminates
self.running = False
self.add_zombie(60, EXITCODE(9))
self.watcher._sig_chld()
callback.assert_called_once_with(60, 9)
@waitpid_mocks
def test_set_loop_race_condition(self, m_waitpid):
# register 3 children
callback1 = mock.Mock()
callback2 = mock.Mock()
callback3 = mock.Mock()
with self.watcher:
self.running = True
self.watcher.add_child_handler(61, callback1)
self.watcher.add_child_handler(62, callback2)
self.watcher.add_child_handler(622, callback3)
# detach the loop
old_loop = self.loop
self.loop = None
with mock.patch.object(
old_loop, "remove_signal_handler") as m_remove_signal_handler:
with self.assertWarnsRegex(
RuntimeWarning, 'A loop is being detached'):
self.watcher.attach_loop(None)
m_remove_signal_handler.assert_called_once_with(
signal.SIGCHLD)
# child 1 & 2 terminate
self.add_zombie(61, EXITCODE(11))
self.add_zombie(62, SIGNAL(5))
# SIGCHLD was not caught
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
self.assertFalse(callback3.called)
# attach a new loop
self.loop = self.new_test_loop()
with mock.patch.object(
self.loop, "add_signal_handler") as m_add_signal_handler:
self.watcher.attach_loop(self.loop)
m_add_signal_handler.assert_called_once_with(
signal.SIGCHLD, self.watcher._sig_chld)
callback1.assert_called_once_with(61, 11) # race condition!
callback2.assert_called_once_with(62, -5) # race condition!
self.assertFalse(callback3.called)
callback1.reset_mock()
callback2.reset_mock()
# child 3 terminates
self.running = False
self.add_zombie(622, EXITCODE(19))
self.watcher._sig_chld()
self.assertFalse(callback1.called)
self.assertFalse(callback2.called)
callback3.assert_called_once_with(622, 19)
@waitpid_mocks
def test_close(self, m_waitpid):
# register two children
callback1 = mock.Mock()
with self.watcher:
self.running = True
# child 1 terminates
self.add_zombie(63, EXITCODE(9))
# other child terminates
self.add_zombie(65, EXITCODE(18))
self.watcher._sig_chld()
self.watcher.add_child_handler(63, callback1)
self.watcher.add_child_handler(64, callback1)
self.assertEqual(len(self.watcher._callbacks), 1)
if isinstance(self.watcher, asyncio.FastChildWatcher):
self.assertEqual(len(self.watcher._zombies), 1)
with mock.patch.object(
self.loop,
"remove_signal_handler") as m_remove_signal_handler:
self.watcher.close()
m_remove_signal_handler.assert_called_once_with(
signal.SIGCHLD)
self.assertFalse(self.watcher._callbacks)
if isinstance(self.watcher, asyncio.FastChildWatcher):
self.assertFalse(self.watcher._zombies)
class SafeChildWatcherTests (ChildWatcherTestsMixin, test_utils.TestCase):
def create_watcher(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return asyncio.SafeChildWatcher()
class FastChildWatcherTests (ChildWatcherTestsMixin, test_utils.TestCase):
def create_watcher(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return asyncio.FastChildWatcher()
class PolicyTests(unittest.TestCase):
def create_policy(self):
return asyncio.DefaultEventLoopPolicy()
@mock.patch('asyncio.unix_events.can_use_pidfd')
def test_get_default_child_watcher(self, m_can_use_pidfd):
m_can_use_pidfd.return_value = False
policy = self.create_policy()
self.assertIsNone(policy._watcher)
with self.assertWarns(DeprecationWarning):
watcher = policy.get_child_watcher()
self.assertIsInstance(watcher, asyncio.ThreadedChildWatcher)
self.assertIs(policy._watcher, watcher)
with self.assertWarns(DeprecationWarning):
self.assertIs(watcher, policy.get_child_watcher())
m_can_use_pidfd.return_value = True
policy = self.create_policy()
self.assertIsNone(policy._watcher)
with self.assertWarns(DeprecationWarning):
watcher = policy.get_child_watcher()
self.assertIsInstance(watcher, asyncio.PidfdChildWatcher)
self.assertIs(policy._watcher, watcher)
with self.assertWarns(DeprecationWarning):
self.assertIs(watcher, policy.get_child_watcher())
def test_get_child_watcher_after_set(self):
policy = self.create_policy()
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
watcher = asyncio.FastChildWatcher()
policy.set_child_watcher(watcher)
self.assertIs(policy._watcher, watcher)
with self.assertWarns(DeprecationWarning):
self.assertIs(watcher, policy.get_child_watcher())
def test_get_child_watcher_thread(self):
def f():
policy.set_event_loop(policy.new_event_loop())
self.assertIsInstance(policy.get_event_loop(),
asyncio.AbstractEventLoop)
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
watcher = policy.get_child_watcher()
self.assertIsInstance(watcher, asyncio.SafeChildWatcher)
self.assertIsNone(watcher._loop)
policy.get_event_loop().close()
policy = self.create_policy()
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
policy.set_child_watcher(asyncio.SafeChildWatcher())
th = threading.Thread(target=f)
th.start()
th.join()
def test_child_watcher_replace_mainloop_existing(self):
policy = self.create_policy()
loop = policy.new_event_loop()
policy.set_event_loop(loop)
# Explicitly setup SafeChildWatcher,
# default ThreadedChildWatcher has no _loop property
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
watcher = asyncio.SafeChildWatcher()
policy.set_child_watcher(watcher)
watcher.attach_loop(loop)
self.assertIs(watcher._loop, loop)
new_loop = policy.new_event_loop()
policy.set_event_loop(new_loop)
self.assertIs(watcher._loop, new_loop)
policy.set_event_loop(None)
self.assertIs(watcher._loop, None)
loop.close()
new_loop.close()
class TestFunctional(unittest.TestCase):
def setUp(self):
@@ -1874,11 +1180,16 @@ class TestFunctional(unittest.TestCase):
@support.requires_fork()
class TestFork(unittest.IsolatedAsyncioTestCase):
class TestFork(unittest.TestCase):
async def test_fork_not_share_event_loop(self):
# The forked process should not share the event loop with the parent
loop = asyncio.get_running_loop()
def test_fork_not_share_current_task(self):
loop = object()
task = object()
asyncio._set_running_loop(loop)
self.addCleanup(asyncio._set_running_loop, None)
asyncio.tasks._enter_task(loop, task)
self.addCleanup(asyncio.tasks._leave_task, loop, task)
self.assertIs(asyncio.current_task(), task)
r, w = os.pipe()
self.addCleanup(os.close, r)
self.addCleanup(os.close, w)
@@ -1886,8 +1197,37 @@ class TestFork(unittest.IsolatedAsyncioTestCase):
if pid == 0:
# child
try:
with self.assertWarns(DeprecationWarning):
loop = asyncio.get_event_loop_policy().get_event_loop()
asyncio._set_running_loop(loop)
current_task = asyncio.current_task()
if current_task is None:
os.write(w, b'NO TASK')
else:
os.write(w, b'TASK:' + str(id(current_task)).encode())
except BaseException as e:
os.write(w, b'ERROR:' + ascii(e).encode())
finally:
asyncio._set_running_loop(None)
os._exit(0)
else:
# parent
result = os.read(r, 100)
self.assertEqual(result, b'NO TASK')
wait_process(pid, exitcode=0)
def test_fork_not_share_event_loop(self):
# The forked process should not share the event loop with the parent
loop = object()
asyncio._set_running_loop(loop)
self.assertIs(asyncio.get_running_loop(), loop)
self.addCleanup(asyncio._set_running_loop, None)
r, w = os.pipe()
self.addCleanup(os.close, r)
self.addCleanup(os.close, w)
pid = os.fork()
if pid == 0:
# child
try:
loop = asyncio.get_event_loop()
os.write(w, b'LOOP:' + str(id(loop)).encode())
except RuntimeError:
os.write(w, b'NO LOOP')
@@ -1898,8 +1238,7 @@ class TestFork(unittest.IsolatedAsyncioTestCase):
else:
# parent
result = os.read(r, 100)
self.assertEqual(result[:5], b'LOOP:', result)
self.assertNotEqual(int(result[5:]), id(loop))
self.assertEqual(result, b'NO LOOP')
wait_process(pid, exitcode=0)
@hashlib_helper.requires_hashdigest('md5')
+1 -1
View File
@@ -5,7 +5,7 @@ from test import support
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
# The following value can be used as a very small timeout:
+16 -12
View File
@@ -19,7 +19,7 @@ from test.test_asyncio import utils as test_utils
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class UpperProto(asyncio.Protocol):
@@ -328,17 +328,18 @@ class WinPolicyTests(WindowsEventsTestCase):
def test_selector_win_policy(self):
async def main():
self.assertIsInstance(
asyncio.get_running_loop(),
asyncio.SelectorEventLoop)
self.assertIsInstance(asyncio.get_running_loop(), asyncio.SelectorEventLoop)
old_policy = asyncio.get_event_loop_policy()
old_policy = asyncio.events._get_event_loop_policy()
try:
asyncio.set_event_loop_policy(
asyncio.WindowsSelectorEventLoopPolicy())
with self.assertWarnsRegex(
DeprecationWarning,
"'asyncio.WindowsSelectorEventLoopPolicy' is deprecated",
):
asyncio.events._set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(main())
finally:
asyncio.set_event_loop_policy(old_policy)
asyncio.events._set_event_loop_policy(old_policy)
def test_proactor_win_policy(self):
async def main():
@@ -346,13 +347,16 @@ class WinPolicyTests(WindowsEventsTestCase):
asyncio.get_running_loop(),
asyncio.ProactorEventLoop)
old_policy = asyncio.get_event_loop_policy()
old_policy = asyncio.events._get_event_loop_policy()
try:
asyncio.set_event_loop_policy(
asyncio.WindowsProactorEventLoopPolicy())
with self.assertWarnsRegex(
DeprecationWarning,
"'asyncio.WindowsProactorEventLoopPolicy' is deprecated",
):
asyncio.events._set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
asyncio.run(main())
finally:
asyncio.set_event_loop_policy(old_policy)
asyncio.events._set_event_loop_policy(old_policy)
if __name__ == '__main__':
+3 -3
View File
@@ -16,7 +16,7 @@ from test import support
def tearDownModule():
asyncio.set_event_loop_policy(None)
asyncio.events._set_event_loop_policy(None)
class PipeTests(unittest.TestCase):
@@ -121,8 +121,8 @@ class PopenTests(unittest.TestCase):
self.assertGreater(len(out), 0)
self.assertGreater(len(err), 0)
# allow for partial reads...
self.assertTrue(msg.upper().rstrip().startswith(out))
self.assertTrue(b"stderr".startswith(err))
self.assertStartsWith(msg.upper().rstrip(), out)
self.assertStartsWith(b"stderr", err)
# The context manager calls wait() and closes resources
with p:
+9 -38
View File
@@ -14,7 +14,7 @@ import sys
import threading
import unittest
import weakref
import warnings
from ast import literal_eval
from unittest import mock
from http.server import HTTPServer
@@ -55,24 +55,8 @@ ONLYCERT = data_file('certdata', 'ssl_cert.pem')
ONLYKEY = data_file('certdata', 'ssl_key.pem')
SIGNED_CERTFILE = data_file('certdata', 'keycert3.pem')
SIGNING_CA = data_file('certdata', 'pycacert.pem')
PEERCERT = {
'OCSP': ('http://testca.pythontest.net/testca/ocsp/',),
'caIssuers': ('http://testca.pythontest.net/testca/pycacert.cer',),
'crlDistributionPoints': ('http://testca.pythontest.net/testca/revocation.crl',),
'issuer': ((('countryName', 'XY'),),
(('organizationName', 'Python Software Foundation CA'),),
(('commonName', 'our-ca-server'),)),
'notAfter': 'Oct 28 14:23:16 2037 GMT',
'notBefore': 'Aug 29 14:23:16 2018 GMT',
'serialNumber': 'CB2D80995A69525C',
'subject': ((('countryName', 'XY'),),
(('localityName', 'Castle Anthrax'),),
(('organizationName', 'Python Software Foundation'),),
(('commonName', 'localhost'),)),
'subjectAltName': (('DNS', 'localhost'),),
'version': 3
}
with open(data_file('certdata', 'keycert3.pem.reference')) as file:
PEERCERT = literal_eval(file.read())
def simple_server_sslcontext():
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
@@ -551,25 +535,6 @@ class TestCase(unittest.TestCase):
loop._default_executor.shutdown(wait=True)
loop.close()
policy = support.maybe_get_event_loop_policy()
if policy is not None:
try:
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = policy.get_child_watcher()
except NotImplementedError:
# watcher is not implemented by EventLoopPolicy, e.g. Windows
pass
else:
if isinstance(watcher, asyncio.ThreadedChildWatcher):
# Wait for subprocess to finish, but not forever
for thread in list(watcher._threads.values()):
thread.join(timeout=support.SHORT_TIMEOUT)
if thread.is_alive():
raise RuntimeError(f"thread {thread} still alive: "
"subprocess still running")
def set_event_loop(self, loop, *, cleanup=True):
if loop is None:
raise AssertionError('loop is None')
@@ -636,3 +601,9 @@ async def await_without_task(coro):
await asyncio.sleep(0)
if exc is not None:
raise exc
if sys.platform == 'win32':
DefaultEventLoopPolicy = asyncio.windows_events._DefaultEventLoopPolicy
else:
DefaultEventLoopPolicy = asyncio.unix_events._DefaultEventLoopPolicy