1
from __future__ import annotations
5
import concurrent.futures
21
from asyncio.base_events import _run_until_complete_cb # type: ignore[attr-defined]
22
from collections import OrderedDict, deque
23
from collections.abc import (
33
from concurrent.futures import Future
34
from contextlib import AbstractContextManager, suppress
35
from contextvars import Context, copy_context
36
from dataclasses import dataclass, field
37
from functools import partial, wraps
44
from os import PathLike
45
from queue import Queue
46
from signal import Signals
47
from socket import AddressFamily, SocketKind
48
from threading import Thread
49
from types import CodeType, TracebackType
59
from weakref import WeakKeyDictionary
62
CapacityLimiterStatistics,
68
from .._core._eventloop import (
70
set_current_async_library,
73
from .._core._exceptions import (
81
from .._core._sockets import convert_ipv6_sockaddr
82
from .._core._streams import create_memory_object_stream
83
from .._core._synchronization import (
84
CapacityLimiter as BaseCapacityLimiter,
86
from .._core._synchronization import Event as BaseEvent
87
from .._core._synchronization import Lock as BaseLock
88
from .._core._synchronization import (
92
from .._core._synchronization import Semaphore as BaseSemaphore
93
from .._core._tasks import CancelScope as BaseCancelScope
94
from .._core._tasks import TaskHandle
100
UNIXDatagramPacketType,
102
from ..abc._tasks import call_for_coroutine, get_callable_name, get_coro_name
103
from ..lowlevel import RunVar, _run_vars
106
from _typeshed import FileDescriptorLike
108
from ..abc._eventloop import StrOrBytesPath
109
from ..streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
111
FileDescriptorLike = object
113
if sys.version_info >= (3, 11):
114
from asyncio import Runner
115
from typing import Self, TypeVarTuple, Unpack
120
from asyncio import coroutines, events, exceptions, tasks
122
from exceptiongroup import BaseExceptionGroup
123
from typing_extensions import Self, TypeVarTuple, Unpack
125
class _State(enum.Enum):
127
INITIALIZED = "initialized"
131
# Copied from CPython 3.11
135
debug: bool | None = None,
136
loop_factory: Callable[[], AbstractEventLoop] | None = None,
138
self._state = _State.CREATED
140
self._loop_factory = loop_factory
141
self._loop: AbstractEventLoop | None = None
143
self._interrupt_count = 0
144
self._set_event_loop = False
146
def __enter__(self) -> Self:
152
exc_type: type[BaseException] | None,
153
exc_val: BaseException | None,
154
exc_tb: TracebackType | None,
158
def close(self) -> None:
159
"""Shutdown and close event loop."""
161
if self._state is not _State.INITIALIZED or loop is None:
164
_cancel_all_tasks(loop)
165
loop.run_until_complete(loop.shutdown_asyncgens())
166
if hasattr(loop, "shutdown_default_executor"):
167
loop.run_until_complete(loop.shutdown_default_executor())
169
loop.run_until_complete(_shutdown_default_executor(loop))
171
if self._set_event_loop:
172
events.set_event_loop(None)
175
self._state = _State.CLOSED
177
def get_loop(self) -> AbstractEventLoop:
178
"""Return embedded event loop."""
182
def run(self, coro: Coroutine[T_Retval], *, context=None) -> T_Retval:
183
"""Run a coroutine inside the embedded event loop."""
184
if not coroutines.iscoroutine(coro):
185
raise ValueError(f"a coroutine was expected, got {coro!r}")
187
if events._get_running_loop() is not None:
188
# fail fast with short traceback
190
"Runner.run() cannot be called from a running event loop"
196
context = self._context
197
task = context.run(self._loop.create_task, coro)
200
threading.current_thread() is threading.main_thread()
201
and signal.getsignal(signal.SIGINT) is signal.default_int_handler
203
sigint_handler = partial(self._on_sigint, main_task=task)
205
signal.signal(signal.SIGINT, sigint_handler)
207
# `signal.signal` may throw if `threading.main_thread` does
208
# not support signals (e.g. embedded interpreter with signals
209
# not registered - see gh-91880)
210
sigint_handler = None
212
sigint_handler = None
214
self._interrupt_count = 0
216
return self._loop.run_until_complete(task)
217
except exceptions.CancelledError:
218
if self._interrupt_count > 0:
219
uncancel = getattr(task, "uncancel", None)
220
if uncancel is not None and uncancel() == 0:
221
raise KeyboardInterrupt # noqa: B904
222
raise # CancelledError
225
sigint_handler is not None
226
and signal.getsignal(signal.SIGINT) is sigint_handler
228
signal.signal(signal.SIGINT, signal.default_int_handler)
230
def _lazy_init(self) -> None:
231
if self._state is _State.CLOSED:
232
raise RuntimeError("Runner is closed")
233
if self._state is _State.INITIALIZED:
235
if self._loop_factory is None:
236
self._loop = events.new_event_loop()
237
if not self._set_event_loop:
238
# Call set_event_loop only once to avoid calling
239
# attach_loop multiple times on child watchers
240
events.set_event_loop(self._loop)
241
self._set_event_loop = True
243
self._loop = self._loop_factory()
244
if self._debug is not None:
245
self._loop.set_debug(self._debug)
246
self._context = contextvars.copy_context()
247
self._state = _State.INITIALIZED
249
def _on_sigint(self, signum, frame, main_task: asyncio.Task) -> None:
250
self._interrupt_count += 1
251
if self._interrupt_count == 1 and not main_task.done():
253
# wakeup loop if it is blocked by select() with long timeout
254
self._loop.call_soon_threadsafe(lambda: None)
256
raise KeyboardInterrupt()
258
def _cancel_all_tasks(loop: AbstractEventLoop) -> None:
259
to_cancel = tasks.all_tasks(loop)
263
for task in to_cancel:
266
loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
268
for task in to_cancel:
271
if task.exception() is not None:
272
loop.call_exception_handler(
274
"message": "unhandled exception during asyncio.run() shutdown",
275
"exception": task.exception(),
280
async def _shutdown_default_executor(loop: AbstractEventLoop) -> None:
281
"""Schedule the shutdown of the default executor."""
283
def _do_shutdown(future: asyncio.futures.Future) -> None:
285
loop._default_executor.shutdown(wait=True) # type: ignore[attr-defined]
286
loop.call_soon_threadsafe(future.set_result, None)
287
except Exception as ex:
288
loop.call_soon_threadsafe(future.set_exception, ex)
290
loop._executor_shutdown_called = True
291
if loop._default_executor is None:
293
future = loop.create_future()
294
thread = threading.Thread(target=_do_shutdown, args=(future,))
302
T_Retval = TypeVar("T_Retval")
303
T_co = TypeVar("T_co", covariant=True)
304
T_contra = TypeVar("T_contra", contravariant=True)
305
PosArgsT = TypeVarTuple("PosArgsT")
308
_root_task: RunVar[asyncio.Task[Any] | None] = RunVar("_root_task")
311
def find_root_task() -> asyncio.Task:
312
root_task = _root_task.get(None)
313
if root_task is not None and not root_task.done():
316
# Look for a task that has been started via run_until_complete()
317
for task in all_tasks():
318
if task._callbacks and not task.done():
319
for cb, context in task._callbacks:
321
cb is _run_until_complete_cb
322
or getattr(cb, "__module__", None) == "uvloop.loop"
326
def _unset(t: asyncio.Task[Any]) -> None:
327
if vars := _run_vars.get(t.get_loop()):
328
vars.pop(_root_task, None)
330
# Register a callback to break the task -> loop -> _run_var[loop][_root_task] -> task cycle
331
# Also run it in its own context to not create another reference.
332
# We can't use RunVar.reset() here since these are called synchronously
333
# and thus lowlevel.current_token() (which RunVar.reset() depends on) fails.
334
task.add_done_callback(_unset, context=context)
337
# Look up the topmost task in the AnyIO task tree, if possible
338
task = cast(asyncio.Task, current_task())
339
state = _task_states.get(task)
341
cancel_scope = state.cancel_scope
342
while cancel_scope and cancel_scope._parent_scope is not None:
343
cancel_scope = cancel_scope._parent_scope
345
if cancel_scope is not None:
346
return cast(asyncio.Task, cancel_scope._host_task)
351
def _task_started(task: asyncio.Task) -> bool:
352
"""Return ``True`` if the task has been started and has not finished."""
353
# The task coro should never be None here, as we never add finished tasks to the
355
coro = task.get_coro()
356
assert coro is not None
357
return getcoroutinestate(coro) in (CORO_RUNNING, CORO_SUSPENDED)
361
# Timeouts and cancellation
365
def is_anyio_cancellation(exc: CancelledError) -> bool:
366
# Sometimes third party frameworks catch a CancelledError and raise a new one, so as
367
# a workaround we have to look at the previous ones in __context__ too for a
368
# matching cancel message
372
and isinstance(exc.args[0], str)
373
and exc.args[0].startswith("Cancelled via cancel scope ")
377
if isinstance(exc.__context__, CancelledError):
378
exc = exc.__context__
384
class CancelScope(BaseCancelScope):
395
"_pending_uncancellations",
401
def __new__(cls, *, deadline: float = math.inf, shield: bool = False) -> Self:
402
return object.__new__(cls)
404
def __init__(self, deadline: float = math.inf, shield: bool = False):
405
self._deadline = deadline
406
self._shield = shield
407
self._parent_scope: CancelScope | None = None
408
self._child_scopes: set[CancelScope] = set()
409
self._cancel_called = False
410
self._cancel_reason: str | None = None
411
self._cancelled_caught = False
413
self._timeout_handle: asyncio.TimerHandle | None = None
414
self._cancel_handle: asyncio.Handle | None = None
415
self._tasks: set[asyncio.Task] = set()
416
self._host_task: asyncio.Task | None = None
417
if sys.version_info >= (3, 11):
418
self._pending_uncancellations: int | None = 0
420
self._pending_uncancellations = None
422
def __enter__(self) -> Self:
425
"Each CancelScope may only be used for a single 'with' block"
428
self._host_task = host_task = cast(asyncio.Task, current_task())
429
self._tasks.add(host_task)
431
task_state = _task_states[host_task]
433
task_state = TaskState(None, self)
434
_task_states[host_task] = task_state
436
self._parent_scope = task_state.cancel_scope
437
task_state.cancel_scope = self
438
if self._parent_scope is not None:
439
# If using an eager task factory, the parent scope may not even contain
441
self._parent_scope._child_scopes.add(self)
442
self._parent_scope._tasks.discard(host_task)
447
# Start cancelling the host task if the scope was cancelled before entering
448
if self._cancel_called:
449
self._deliver_cancellation(self)
455
exc_type: type[BaseException] | None,
456
exc_val: BaseException | None,
457
exc_tb: TracebackType | None,
462
raise RuntimeError("This cancel scope is not active")
463
if current_task() is not self._host_task:
465
"Attempted to exit cancel scope in a different task than it was "
469
assert self._host_task is not None
470
host_task_state = _task_states.get(self._host_task)
471
if host_task_state is None or host_task_state.cancel_scope is not self:
473
"Attempted to exit a cancel scope that isn't the current tasks's "
474
"current cancel scope"
479
if self._timeout_handle:
480
self._timeout_handle.cancel()
481
self._timeout_handle = None
483
self._tasks.remove(self._host_task)
484
if self._parent_scope is not None:
485
self._parent_scope._child_scopes.remove(self)
486
self._parent_scope._tasks.add(self._host_task)
488
host_task_state.cancel_scope = self._parent_scope
490
# Restart the cancellation effort in the closest visible, cancelled parent
492
self._restart_cancellation_in_parent()
494
# We only swallow the exception iff it was an AnyIO CancelledError, either
495
# directly as exc_val or inside an exception group and there are no cancelled
496
# parent cancel scopes visible to us here
497
if self._cancel_called and not self._parent_cancellation_is_visible_to_us:
498
# For each level-cancel() call made on the host task, call uncancel()
499
while self._pending_uncancellations:
500
self._host_task.uncancel()
501
self._pending_uncancellations -= 1
503
# Update cancelled_caught and check for exceptions we must not swallow
504
if isinstance(exc_val, BaseExceptionGroup):
505
cancelleds_caught, remaining = exc_val.split(
507
isinstance(exc, CancelledError)
508
and is_anyio_cancellation(exc)
512
if cancelleds_caught is None:
515
self._cancelled_caught = True
517
if remaining is None:
520
context = remaining.__context__
522
# Preserve __cause__ and __suppress_context__ by avoiding `raise
526
# Preserve __context__
527
remaining.__context__ = context
530
if isinstance(exc_val, CancelledError) and is_anyio_cancellation(
533
self._cancelled_caught = True
538
if self._pending_uncancellations:
539
assert self._parent_scope is not None
540
assert self._parent_scope._pending_uncancellations is not None
541
self._parent_scope._pending_uncancellations += (
542
self._pending_uncancellations
544
self._pending_uncancellations = 0
548
self._host_task = None
552
def _effectively_cancelled(self) -> bool:
553
cancel_scope: CancelScope | None = self
554
while cancel_scope is not None:
555
if cancel_scope._cancel_called:
558
if cancel_scope.shield:
561
cancel_scope = cancel_scope._parent_scope
566
def _parent_cancellation_is_visible_to_us(self) -> bool:
568
self._parent_scope is not None
570
and self._parent_scope._effectively_cancelled
573
def _timeout(self) -> None:
574
if self._deadline != math.inf:
575
loop = get_running_loop()
576
if loop.time() >= self._deadline:
577
self.cancel("deadline exceeded")
579
self._timeout_handle = loop.call_at(self._deadline, self._timeout)
581
def _deliver_cancellation(self, origin: CancelScope) -> bool:
583
Deliver cancellation to directly contained tasks and nested cancel scopes.
585
Schedule another run at the end if we still have tasks eligible for
588
:param origin: the cancel scope that originated the cancellation
589
:return: ``True`` if the delivery needs to be retried on the next cycle
593
current = current_task()
594
for task in self._tasks:
595
# Always skip tasks that are already done (see issue #1111)
600
if task._must_cancel: # type: ignore[attr-defined]
603
# The task is eligible for cancellation if it has started
604
if task is not current and (task is self._host_task or _task_started(task)):
605
waiter = task._fut_waiter # type: ignore[attr-defined]
606
if not isinstance(waiter, asyncio.Future) or not waiter.done():
607
task.cancel(origin._cancel_reason)
609
task is origin._host_task
610
and origin._pending_uncancellations is not None
612
origin._pending_uncancellations += 1
614
# Deliver cancellation to child scopes that aren't shielded or running their own
615
# cancellation callbacks
616
for scope in self._child_scopes:
617
if not scope._shield and not scope.cancel_called:
618
should_retry = scope._deliver_cancellation(origin) or should_retry
620
# Schedule another callback if there are still tasks left
623
self._cancel_handle = get_running_loop().call_soon(
624
self._deliver_cancellation, origin
627
self._cancel_handle = None
631
def _restart_cancellation_in_parent(self) -> None:
633
Restart the cancellation effort in the closest directly cancelled parent scope.
636
scope = self._parent_scope
637
while scope is not None:
638
if scope._cancel_called:
639
if scope._cancel_handle is None:
640
scope._deliver_cancellation(scope)
644
# No point in looking beyond any shielded scope
648
scope = scope._parent_scope
650
def _reparent(self, new_parent: CancelScope) -> None:
652
Move this active scope from its current parent to ``new_parent``.
654
Used by :meth:`TaskGroup.start` to move a task that has just called
655
``task_status.started()`` into the target task group's cancel scope.
657
if self._parent_scope is new_parent:
660
if self._parent_scope is not None:
661
self._parent_scope._child_scopes.discard(self)
663
self._parent_scope = new_parent
664
new_parent._child_scopes.add(self)
666
# If the new parent (or an ancestor) is already cancelled, (re)start the
667
# delivery loop to ensure we're cancelled at next checkpoint like Trio.
668
self._restart_cancellation_in_parent()
670
def cancel(self, reason: str | None = None) -> None:
671
if not self._cancel_called:
672
if self._timeout_handle:
673
self._timeout_handle.cancel()
674
self._timeout_handle = None
676
self._cancel_called = True
677
self._cancel_reason = f"Cancelled via cancel scope {id(self):x}"
678
if task := current_task():
679
self._cancel_reason += f" by {task}"
682
self._cancel_reason += f"; reason: {reason}"
684
if self._host_task is not None:
685
self._deliver_cancellation(self)
688
def deadline(self) -> float:
689
return self._deadline
692
def deadline(self, value: float) -> None:
693
self._deadline = float(value)
694
if self._timeout_handle is not None:
695
self._timeout_handle.cancel()
696
self._timeout_handle = None
698
if self._active and not self._cancel_called:
702
def cancel_called(self) -> bool:
703
return self._cancel_called
706
def cancelled_caught(self) -> bool:
707
return self._cancelled_caught
710
def shield(self) -> bool:
714
def shield(self, value: bool) -> None:
715
if self._shield != value:
718
self._restart_cancellation_in_parent()
728
Encapsulates auxiliary task information that cannot be added to the Task instance
729
itself because there are no guarantees about its implementation.
732
__slots__ = "__weakref__", "cancel_scope", "parent_id"
734
def __init__(self, parent_id: int | None, cancel_scope: CancelScope | None):
735
self.parent_id = parent_id
736
self.cancel_scope = cancel_scope
739
_task_states: WeakKeyDictionary[asyncio.Task, TaskState] = WeakKeyDictionary()
747
class _AsyncioTaskStatus(abc.TaskStatus[T_contra]):
750
future: asyncio.Future,
752
target_scope: CancelScope,
753
spawn_scope: CancelScope,
755
self._future = future
756
self._parent_id = parent_id
757
# The eventual parent scope for this spawn_scope
758
# (after task_status.started() has been called)
759
self._target_scope = target_scope
760
# The task's own cancel scope, also held by its TaskHandle
761
self._spawn_scope = spawn_scope
763
def started(self, value: T_contra | None = None) -> None:
764
task = cast(asyncio.Task, current_task())
765
_task_states[task].parent_id = self._parent_id
766
if self._future.done():
767
if not self._future.cancelled():
768
raise RuntimeError("called 'started' twice on the same task status")
770
# Caller of start() was cancelled, nothing to reparent
773
self._future.set_result(value)
774
self._spawn_scope._reparent(self._target_scope)
777
if sys.version_info >= (3, 12):
778
_eager_task_factory_code: CodeType | None = asyncio.eager_task_factory.__code__
780
_eager_task_factory_code = None
783
class TaskGroup(abc.TaskGroup):
784
def __init__(self) -> None:
785
self.cancel_scope: CancelScope = CancelScope()
786
self._entered = False
787
self._exceptions: list[BaseException] = []
788
self._tasks: set[asyncio.Task] = set()
789
self._on_completed_fut: asyncio.Future[None] | None = None
791
async def __aenter__(self) -> Self:
793
raise RuntimeError("TaskGroup cannot be entered more than once")
797
self.cancel_scope.__enter__()
802
exc_type: type[BaseException] | None,
803
exc_val: BaseException | None,
804
exc_tb: TracebackType | None,
807
if exc_val is not None:
808
self.cancel_scope.cancel()
809
if not isinstance(exc_val, CancelledError):
810
self._exceptions.append(exc_val)
812
loop = get_running_loop()
815
with CancelScope() as wait_scope:
817
self._on_completed_fut = loop.create_future()
820
await self._on_completed_fut
821
except CancelledError as exc:
822
# Shield the scope against further cancellation attempts,
823
# as they're not productive (#695)
824
wait_scope.shield = True
825
self.cancel_scope.cancel()
827
# Set exc_val from the cancellation exception if it was
828
# previously unset. However, we should not replace a native
829
# cancellation exception with one raise by a cancel scope.
830
if exc_val is None or (
831
isinstance(exc_val, CancelledError)
832
and not is_anyio_cancellation(exc)
836
self._on_completed_fut = None
838
# If there are no child tasks to wait on, run at least one checkpoint
840
await AsyncIOBackend.cancel_shielded_checkpoint()
843
# The exception that got us here should already have been
844
# added to self._exceptions so it's ok to break exception
845
# chaining and avoid adding a "During handling of above..."
846
# for each nesting level.
847
raise BaseExceptionGroup(
848
"unhandled errors in a TaskGroup", self._exceptions
852
except BaseException as exc:
853
if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__):
858
return self.cancel_scope.__exit__(exc_type, exc_val, exc_tb)
860
del exc_val, exc_tb, self._exceptions
864
coro: Coroutine[Any, Any, T_co],
866
task_status: _AsyncioTaskStatus | None = None,
867
) -> TaskHandle[T_co]:
868
task_status_future = task_status._future if task_status is not None else None
870
def task_done(_task: asyncio.Task) -> None:
871
if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None:
872
asyncio.future_discard_from_awaited_by(
873
_task, self.cancel_scope._host_task
876
task_state = _task_states[_task]
877
assert task_state.cancel_scope is not None
878
assert _task in task_state.cancel_scope._tasks
879
task_state.cancel_scope._tasks.remove(_task)
880
self._tasks.remove(task)
881
del _task_states[_task]
883
if self._on_completed_fut is not None and not self._tasks:
885
self._on_completed_fut.set_result(None)
886
except asyncio.InvalidStateError:
890
exc = _task.exception()
891
except CancelledError as e:
892
while isinstance(e.__context__, CancelledError):
898
# The future can only be in the cancelled state if the host task was
899
# cancelled, so return immediately instead of adding one more
900
# CancelledError to the exceptions list
901
if task_status_future is not None and task_status_future.cancelled():
904
if task_status_future is None or task_status_future.done():
905
if not isinstance(exc, CancelledError):
906
self._exceptions.append(exc)
908
if not self.cancel_scope._effectively_cancelled:
909
self.cancel_scope.cancel()
911
task_status_future.set_exception(exc)
912
elif task_status_future is not None and not task_status_future.done():
913
task_status_future.set_exception(
914
RuntimeError("Child exited without calling task_status.started()")
917
if task_status_future is not None:
918
parent_id = id(current_task())
919
caller_state = _task_states.get(cast(asyncio.Task, current_task()))
920
if caller_state is not None and caller_state.cancel_scope is not None:
921
initial_scope = caller_state.cancel_scope
923
# The caller is an unmanaged task (no task state)
924
initial_scope = self.cancel_scope
926
parent_id = id(self.cancel_scope._host_task)
927
initial_scope = self.cancel_scope
929
spawn_scope = task_status._spawn_scope if task_status is not None else None
930
handle = TaskHandle(coro, name, cancel_scope=spawn_scope)
931
loop = asyncio.get_running_loop()
932
wrapper_coro = handle._run_coro()
935
(factory := loop.get_task_factory())
936
and getattr(factory, "__code__", None) is _eager_task_factory_code
937
and (closure := getattr(factory, "__closure__", None))
939
custom_task_constructor = closure[0].cell_contents
940
task = custom_task_constructor(
941
wrapper_coro, loop=loop, name=handle.name
944
task = loop.create_task(wrapper_coro, name=handle.name)
945
except BaseException:
946
with suppress(BaseException):
949
with suppress(BaseException):
954
# Make the spawned task inherit the initial cancel scope
955
_task_states[task] = TaskState(parent_id=parent_id, cancel_scope=initial_scope)
956
initial_scope._tasks.add(task)
957
self._tasks.add(task)
958
if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None:
959
asyncio.future_add_to_awaited_by(task, self.cancel_scope._host_task)
961
task.add_done_callback(task_done)
966
coro: Coroutine[Any, Any, T_co],
969
context: Context | None = None,
970
) -> TaskHandle[T_co]:
971
if not isinstance(coro, Coroutine):
972
raise TypeError(f"expected a coroutine, got {coro.__class__.__qualname__}")
974
if not self._entered or not self.cancel_scope._active:
977
"This task group is not active; no new tasks can be started."
980
final_name = get_coro_name(coro, name)
982
if context is not None:
983
return context.run(self._spawn, coro, name=final_name)
985
return self._spawn(coro, name=final_name)
989
func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
990
*args: Unpack[PosArgsT],
992
return_handle: Literal[False, True] = False,
994
if not self._entered or not self.cancel_scope._active:
996
"This task group is not active; no new tasks can be started."
999
# Until task_status.started() is called, a task spawned via start() belongs to
1000
# the *caller's* cancel scope, not the target group's, so cancelling the group
1001
# does not cancel a task that hasn't reported startup yet. The
1002
# task_status.started() call moves the task's cancel scope to this task group's
1005
# The caller may be an unmanaged task (no _task_states entry), in which case
1006
# fall back to the group's own scope.
1007
future: asyncio.Future = asyncio.Future()
1008
final_name = get_callable_name(func, name)
1009
task_status: _AsyncioTaskStatus[Any] = _AsyncioTaskStatus(
1010
future, id(self.cancel_scope._host_task), self.cancel_scope, CancelScope()
1012
coro = call_for_coroutine(func, args, task_status=task_status)
1013
handle = self._spawn(coro, final_name, task_status)
1015
# If the task raises an exception after sending a start value without a switch
1016
# point between, the task group is cancelled and this method never proceeds to
1017
# process the completed future. That's why we have to have a shielded cancel
1021
except BaseException:
1022
if handle.status is TaskHandle.Status.PENDING:
1023
# Cancel the task and wait for it to exit before returning
1025
with CancelScope(shield=True):
1031
handle._start_value = future.result()
1034
return future.result()
1041
_Retval_Queue_Type = tuple[T_Retval | None, BaseException | None]
1044
class WorkerThread(Thread):
1045
MAX_IDLE_TIME = 10 # seconds
1049
root_task: asyncio.Task,
1050
workers: set[WorkerThread],
1051
idle_workers: deque[WorkerThread],
1053
kwargs: dict[str, Any] = {}
1054
if sys.version_info >= (3, 14):
1055
kwargs["context"] = Context()
1057
super().__init__(name="AnyIO worker thread", **kwargs)
1058
self.root_task = root_task
1059
self.workers = workers
1060
self.idle_workers = idle_workers
1061
self.loop = root_task._loop
1063
tuple[Context, Callable, tuple, asyncio.Future, CancelScope] | None
1065
self.idle_since = AsyncIOBackend.current_time()
1066
self.stopping = False
1069
self, future: asyncio.Future, result: Any, exc: BaseException | None
1071
self.idle_since = AsyncIOBackend.current_time()
1072
if not self.stopping:
1073
self.idle_workers.append(self)
1075
if not future.cancelled():
1077
if isinstance(exc, StopIteration):
1078
new_exc = RuntimeError("coroutine raised StopIteration")
1079
new_exc.__cause__ = exc
1082
future.set_exception(exc)
1084
future.set_result(result)
1086
def run(self) -> None:
1087
with claim_worker_thread(AsyncIOBackend, self.loop):
1089
10
29
item = self.queue.get()
1091
# Shutdown command received
1094
context, func, args, future, cancel_scope = item
1095
if not future.cancelled():
1097
exception: BaseException | None = None
1098
threadlocals.current_cancel_scope = cancel_scope
1100
12
14503
result = context.run(func, *args)
1101
except BaseException as exc:
1104
1
1
del threadlocals.current_cancel_scope
1107
7
9
self.loop.call_soon_threadsafe(
1108
self._report_result, future, result, exception
1110
except RuntimeError:
1111
if not self.loop.is_closed():
1114
del result, exception
1116
4
4
self.queue.task_done()
1117
del item, context, func, args, future, cancel_scope
1119
def stop(self, f: asyncio.Task | None = None) -> None:
1120
self.stopping = True
1121
self.queue.put_nowait(None)
1122
self.workers.discard(self)
1124
self.idle_workers.remove(self)
1129
_threadpool_idle_workers: RunVar[deque[WorkerThread]] = RunVar(
1130
"_threadpool_idle_workers"
1132
_threadpool_workers: RunVar[set[WorkerThread]] = RunVar("_threadpool_workers")
1140
@dataclass(eq=False)
1141
class StreamReaderWrapper(abc.ByteReceiveStream):
1142
_stream: asyncio.StreamReader
1144
async def receive(self, max_bytes: int = 65536) -> bytes:
1146
raise ValueError("max_bytes must be a positive integer")
1148
data = await self._stream.read(max_bytes)
1154
async def aclose(self) -> None:
1155
self._stream.set_exception(ClosedResourceError())
1156
await AsyncIOBackend.checkpoint()
1159
@dataclass(eq=False)
1160
class StreamWriterWrapper(abc.ByteSendStream):
1161
_stream: asyncio.StreamWriter
1162
_closed: bool = field(init=False, default=False)
1164
async def send(self, item: bytes) -> None:
1165
await AsyncIOBackend.checkpoint_if_cancelled()
1166
stream_paused = self._stream._protocol._paused # type: ignore[attr-defined]
1168
self._stream.write(item)
1169
await self._stream.drain()
1170
except (ConnectionResetError, BrokenPipeError, RuntimeError) as exc:
1171
# If closed by us and/or the peer:
1172
# * on stdlib, drain() raises ConnectionResetError or BrokenPipeError
1173
# * on uvloop and Winloop, write() eventually starts raising RuntimeError
1175
raise ClosedResourceError from exc
1176
elif self._stream.is_closing():
1177
raise BrokenResourceError from exc
1181
if not stream_paused:
1182
await AsyncIOBackend.cancel_shielded_checkpoint()
1184
async def aclose(self) -> None:
1186
self._stream.close()
1187
await AsyncIOBackend.checkpoint()
1190
@dataclass(eq=False)
1191
class Process(abc.Process):
1192
_process: asyncio.subprocess.Process
1193
_stdin: StreamWriterWrapper | None
1194
_stdout: StreamReaderWrapper | None
1195
_stderr: StreamReaderWrapper | None
1196
_exited: asyncio.Event
1197
_transport: asyncio.SubprocessTransport
1199
async def aclose(self) -> None:
1200
with CancelScope(shield=True) as scope:
1201
# We need to close the underlying pipe_transports as well to allow a
1202
# process blocking on full buffers to receive SIGPIPE and exit.
1204
await self._stdin.aclose()
1205
if pipe := self._transport.get_pipe_transport(0):
1208
await self._stdout.aclose()
1209
if pipe := self._transport.get_pipe_transport(1):
1212
await self._stderr.aclose()
1213
if pipe := self._transport.get_pipe_transport(2):
1216
scope.shield = False
1219
except BaseException:
1221
# Closing the transport on asyncio also handles sending kill
1222
self._transport.close()
1226
async def wait(self) -> int:
1227
await self._exited.wait()
1228
assert self._process.returncode is not None
1229
return self._process.returncode
1231
def terminate(self) -> None:
1232
self._process.terminate()
1234
def kill(self) -> None:
1235
self._process.kill()
1237
def send_signal(self, signal: int) -> None:
1238
self._process.send_signal(signal)
1241
def pid(self) -> int:
1242
return self._process.pid
1245
def returncode(self) -> int | None:
1246
return self._process.returncode
1249
def stdin(self) -> abc.ByteSendStream | None:
1253
def stdout(self) -> abc.ByteReceiveStream | None:
1257
def stderr(self) -> abc.ByteReceiveStream | None:
1261
def _forcibly_shutdown_process_pool_on_exit(
1262
workers: set[Process], _task: object
1265
Forcibly shuts down worker processes belonging to this event loop."""
1266
child_watcher: asyncio.AbstractChildWatcher | None = None # type: ignore[name-defined]
1267
if sys.version_info < (3, 12):
1269
child_watcher = asyncio.get_event_loop_policy().get_child_watcher()
1270
except NotImplementedError:
1273
# Close as much as possible (w/o async/await) to avoid warnings
1274
for process in workers.copy():
1275
if process.returncode is not None:
1278
process._stdin._stream._transport.close() # type: ignore[union-attr]
1279
process._stdout._stream._transport.close() # type: ignore[union-attr]
1280
process._stderr._stream._transport.close() # type: ignore[union-attr]
1283
child_watcher.remove_child_handler(process.pid)
1286
async def _shutdown_process_pool_on_exit(workers: set[abc.Process]) -> None:
1288
Shuts down worker processes belonging to this event loop.
1290
NOTE: this only works when the event loop was started using asyncio.run() or
1294
process: abc.Process
1296
await sleep(math.inf)
1297
except asyncio.CancelledError:
1298
workers = workers.copy()
1299
for process in workers:
1300
if process.returncode is None:
1303
for process in workers:
1304
await process.aclose()
1308
# Sockets and networking
1312
class StreamProtocol(asyncio.Protocol):
1313
read_queue: deque[bytes]
1314
read_event: asyncio.Event
1315
write_event: asyncio.Event
1316
exception: Exception | None = None
1317
is_at_eof: bool = False
1319
def connection_made(self, transport: asyncio.BaseTransport) -> None:
1320
self.read_queue = deque()
1321
self.read_event = asyncio.Event()
1322
self.write_event = asyncio.Event()
1323
self.write_event.set()
1324
cast(asyncio.Transport, transport).set_write_buffer_limits(0)
1326
def connection_lost(self, exc: Exception | None) -> None:
1328
self.exception = exc
1330
self.read_event.set()
1331
self.write_event.set()
1333
def data_received(self, data: bytes) -> None:
1334
# ProactorEventloop sometimes sends bytearray instead of bytes
1335
self.read_queue.append(bytes(data))
1336
self.read_event.set()
1338
def eof_received(self) -> bool | None:
1339
self.is_at_eof = True
1340
self.read_event.set()
1343
def pause_writing(self) -> None:
1344
self.write_event = asyncio.Event()
1346
def resume_writing(self) -> None:
1347
self.write_event.set()
1350
class DatagramProtocol(asyncio.DatagramProtocol):
1351
read_queue: deque[tuple[bytes, IPSockAddrType]]
1352
read_event: asyncio.Event
1353
write_event: asyncio.Event
1354
closed_event: asyncio.Event
1355
exception: Exception | None = None
1357
def connection_made(self, transport: asyncio.BaseTransport) -> None:
1358
self.read_queue = deque(maxlen=100) # arbitrary value
1359
self.read_event = asyncio.Event()
1360
self.write_event = asyncio.Event()
1361
self.closed_event = asyncio.Event()
1362
self.write_event.set()
1364
def connection_lost(self, exc: Exception | None) -> None:
1365
self.read_event.set()
1366
self.write_event.set()
1367
self.closed_event.set()
1369
def datagram_received(self, data: bytes, addr: IPSockAddrType) -> None:
1370
addr = convert_ipv6_sockaddr(addr)
1371
self.read_queue.append((data, addr))
1372
self.read_event.set()
1374
def error_received(self, exc: Exception) -> None:
1375
self.exception = exc
1377
def pause_writing(self) -> None:
1378
self.write_event.clear()
1380
def resume_writing(self) -> None:
1381
self.write_event.set()
1384
class SocketStream(abc.SocketStream):
1385
def __init__(self, transport: asyncio.Transport, protocol: StreamProtocol):
1386
self._transport = transport
1387
self._protocol = protocol
1388
self._receive_guard = ResourceGuard("reading from")
1389
self._send_guard = ResourceGuard("writing to")
1390
self._closed = False
1393
def _raw_socket(self) -> socket.socket:
1394
return self._transport.get_extra_info("socket")
1396
async def receive(self, max_bytes: int = 65536) -> bytes:
1398
raise ValueError("max_bytes must be a positive integer")
1400
with self._receive_guard:
1402
not self._protocol.read_event.is_set()
1403
and not self._transport.is_closing()
1404
and not self._protocol.is_at_eof
1406
self._transport.resume_reading()
1407
await self._protocol.read_event.wait()
1408
self._transport.pause_reading()
1410
await AsyncIOBackend.checkpoint()
1413
chunk = self._protocol.read_queue.popleft()
1416
raise ClosedResourceError from None
1417
elif self._protocol.exception:
1418
raise BrokenResourceError from self._protocol.exception
1420
raise EndOfStream from None
1422
if len(chunk) > max_bytes:
1423
# Split the oversized chunk
1424
chunk, leftover = chunk[:max_bytes], chunk[max_bytes:]
1425
self._protocol.read_queue.appendleft(leftover)
1427
# If the read queue is empty, clear the flag so that the next call will
1428
# block until data is available
1429
if not self._protocol.read_queue:
1430
self._protocol.read_event.clear()
1434
async def send(self, item: bytes) -> None:
1435
with self._send_guard:
1436
await AsyncIOBackend.checkpoint()
1439
raise ClosedResourceError
1440
elif self._protocol.exception is not None:
1441
raise BrokenResourceError from self._protocol.exception
1444
self._transport.write(item)
1445
except RuntimeError as exc:
1446
if self._transport.is_closing():
1447
raise BrokenResourceError from exc
1451
await self._protocol.write_event.wait()
1453
async def send_eof(self) -> None:
1455
self._transport.write_eof()
1459
async def aclose(self) -> None:
1461
if not self._transport.is_closing():
1463
self._transport.write_eof()
1467
self._transport.close()
1469
self._transport.abort()
1472
class _RawSocketMixin:
1473
_receive_future: asyncio.Future | None = None
1474
_send_future: asyncio.Future | None = None
1477
def __init__(self, raw_socket: socket.socket):
1478
self.__raw_socket = raw_socket
1479
self._receive_guard = ResourceGuard("reading from")
1480
self._send_guard = ResourceGuard("writing to")
1483
def _raw_socket(self) -> socket.socket:
1484
return self.__raw_socket
1486
def _wait_until_readable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future:
1487
def callback(f: object) -> None:
1488
del self._receive_future
1489
loop.remove_reader(self.__raw_socket)
1491
f = self._receive_future = asyncio.Future()
1492
loop.add_reader(self.__raw_socket, f.set_result, None)
1493
f.add_done_callback(callback)
1496
def _wait_until_writable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future:
1497
def callback(f: object) -> None:
1498
del self._send_future
1499
loop.remove_writer(self.__raw_socket)
1501
f = self._send_future = asyncio.Future()
1502
loop.add_writer(self.__raw_socket, f.set_result, None)
1503
f.add_done_callback(callback)
1506
async def aclose(self) -> None:
1507
if not self._closing:
1508
self._closing = True
1509
if self.__raw_socket.fileno() != -1:
1510
self.__raw_socket.close()
1512
if self._receive_future and not self._receive_future.done():
1513
self._receive_future.set_result(None)
1514
if self._send_future and not self._send_future.done():
1515
self._send_future.set_result(None)
1518
class UNIXSocketStream(_RawSocketMixin, abc.UNIXSocketStream):
1519
async def send_eof(self) -> None:
1520
with self._send_guard:
1521
self._raw_socket.shutdown(socket.SHUT_WR)
1523
async def receive(self, max_bytes: int = 65536) -> bytes:
1525
raise ValueError("max_bytes must be a positive integer")
1527
loop = get_running_loop()
1528
await AsyncIOBackend.checkpoint()
1529
with self._receive_guard:
1532
data = self._raw_socket.recv(max_bytes)
1533
except BlockingIOError:
1534
await self._wait_until_readable(loop)
1535
except OSError as exc:
1537
raise ClosedResourceError from None
1539
raise BrokenResourceError from exc
1546
async def send(self, item: bytes) -> None:
1547
loop = get_running_loop()
1548
await AsyncIOBackend.checkpoint()
1549
with self._send_guard:
1550
view = memoryview(item)
1553
bytes_sent = self._raw_socket.send(view)
1554
except BlockingIOError:
1555
await self._wait_until_writable(loop)
1556
except OSError as exc:
1558
raise ClosedResourceError from None
1560
raise BrokenResourceError from exc
1562
view = view[bytes_sent:]
1564
async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]:
1565
if not isinstance(msglen, int) or msglen < 0:
1566
raise ValueError("msglen must be a non-negative integer")
1567
if not isinstance(maxfds, int) or maxfds < 1:
1568
raise ValueError("maxfds must be a positive integer")
1570
loop = get_running_loop()
1571
fds = array.array("i")
1572
await AsyncIOBackend.checkpoint()
1573
with self._receive_guard:
1576
message, ancdata, _flags, _addr = self._raw_socket.recvmsg(
1577
msglen, socket.CMSG_LEN(maxfds * fds.itemsize)
1579
except BlockingIOError:
1580
await self._wait_until_readable(loop)
1581
except OSError as exc:
1583
raise ClosedResourceError from None
1585
raise BrokenResourceError from exc
1587
if not message and not ancdata:
1592
for cmsg_level, cmsg_type, cmsg_data in ancdata:
1593
if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS:
1595
f"Received unexpected ancillary data; message = {message!r}, "
1596
f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}"
1599
fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
1601
return message, list(fds)
1603
async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None:
1605
raise ValueError("message must not be empty")
1607
raise ValueError("fds must not be empty")
1609
loop = get_running_loop()
1610
filenos: list[int] = []
1612
if isinstance(fd, int):
1614
elif isinstance(fd, IOBase):
1615
filenos.append(fd.fileno())
1617
fdarray = array.array("i", filenos)
1618
await AsyncIOBackend.checkpoint()
1619
with self._send_guard:
1622
# The ignore can be removed after mypy picks up
1623
# https://github.com/python/typeshed/pull/5545
1624
self._raw_socket.sendmsg(
1625
[message], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fdarray)]
1628
except BlockingIOError:
1629
await self._wait_until_writable(loop)
1630
except OSError as exc:
1632
raise ClosedResourceError from None
1634
raise BrokenResourceError from exc
1637
class TCPSocketListener(abc.SocketListener):
1638
_accept_scope: CancelScope | None = None
1641
def __init__(self, raw_socket: socket.socket):
1642
self.__raw_socket = raw_socket
1643
self._loop = cast(asyncio.BaseEventLoop, get_running_loop())
1644
self._accept_guard = ResourceGuard("accepting connections from")
1647
def _raw_socket(self) -> socket.socket:
1648
return self.__raw_socket
1650
async def accept(self) -> abc.SocketStream:
1652
raise ClosedResourceError
1654
with self._accept_guard:
1655
await AsyncIOBackend.checkpoint()
1656
with CancelScope() as self._accept_scope:
1658
client_sock, _addr = await self._loop.sock_accept(self._raw_socket)
1659
except asyncio.CancelledError:
1660
# Workaround for https://bugs.python.org/issue41317
1662
self._loop.remove_reader(self._raw_socket)
1663
except (ValueError, NotImplementedError):
1667
raise ClosedResourceError from None
1671
self._accept_scope = None
1673
client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1674
transport, protocol = await self._loop.connect_accepted_socket(
1675
StreamProtocol, client_sock
1677
return SocketStream(transport, protocol)
1679
async def aclose(self) -> None:
1684
if self._accept_scope:
1685
# Workaround for https://bugs.python.org/issue41317
1687
self._loop.remove_reader(self._raw_socket)
1688
except (ValueError, NotImplementedError):
1691
self._accept_scope.cancel()
1694
self._raw_socket.close()
1697
class UNIXSocketListener(abc.SocketListener):
1698
def __init__(self, raw_socket: socket.socket):
1699
self.__raw_socket = raw_socket
1700
self._loop = get_running_loop()
1701
self._accept_guard = ResourceGuard("accepting connections from")
1702
self._closed = False
1704
async def accept(self) -> abc.SocketStream:
1705
await AsyncIOBackend.checkpoint()
1706
with self._accept_guard:
1709
client_sock, _ = self.__raw_socket.accept()
1710
client_sock.setblocking(False)
1711
return UNIXSocketStream(client_sock)
1712
except BlockingIOError:
1713
f: asyncio.Future = asyncio.Future()
1714
self._loop.add_reader(self.__raw_socket, f.set_result, None)
1715
f.add_done_callback(
1716
lambda _: self._loop.remove_reader(self.__raw_socket)
1719
except OSError as exc:
1721
raise ClosedResourceError from None
1723
raise BrokenResourceError from exc
1725
async def aclose(self) -> None:
1727
self.__raw_socket.close()
1730
def _raw_socket(self) -> socket.socket:
1731
return self.__raw_socket
1734
class UDPSocket(abc.UDPSocket):
1736
self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol
1738
self._transport = transport
1739
self._protocol = protocol
1740
self._receive_guard = ResourceGuard("reading from")
1741
self._send_guard = ResourceGuard("writing to")
1742
self._closed = False
1745
def _raw_socket(self) -> socket.socket:
1746
return self._transport.get_extra_info("socket")
1748
async def aclose(self) -> None:
1750
if not self._transport.is_closing():
1751
self._transport.close()
1753
await self._protocol.closed_event.wait()
1755
async def receive(self) -> tuple[bytes, IPSockAddrType]:
1756
with self._receive_guard:
1757
await AsyncIOBackend.checkpoint()
1759
# If the buffer is empty, ask for more data
1760
if not self._protocol.read_queue and not self._transport.is_closing():
1761
self._protocol.read_event.clear()
1762
await self._protocol.read_event.wait()
1765
return self._protocol.read_queue.popleft()
1768
raise ClosedResourceError from None
1770
raise BrokenResourceError from None
1772
async def send(self, item: UDPPacketType) -> None:
1773
with self._send_guard:
1774
await AsyncIOBackend.checkpoint()
1775
await self._protocol.write_event.wait()
1777
raise ClosedResourceError
1778
elif self._transport.is_closing():
1779
raise BrokenResourceError
1781
self._transport.sendto(*item)
1784
class ConnectedUDPSocket(abc.ConnectedUDPSocket):
1786
self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol
1788
self._transport = transport
1789
self._protocol = protocol
1790
self._receive_guard = ResourceGuard("reading from")
1791
self._send_guard = ResourceGuard("writing to")
1792
self._closed = False
1795
def _raw_socket(self) -> socket.socket:
1796
return self._transport.get_extra_info("socket")
1798
async def aclose(self) -> None:
1800
if not self._transport.is_closing():
1801
self._transport.close()
1803
await self._protocol.closed_event.wait()
1805
async def receive(self) -> bytes:
1806
with self._receive_guard:
1807
await AsyncIOBackend.checkpoint()
1809
# If the buffer is empty, ask for more data
1810
if not self._protocol.read_queue and not self._transport.is_closing():
1811
self._protocol.read_event.clear()
1812
await self._protocol.read_event.wait()
1815
packet = self._protocol.read_queue.popleft()
1818
raise ClosedResourceError from None
1820
raise BrokenResourceError from None
1824
async def send(self, item: bytes) -> None:
1825
with self._send_guard:
1826
await AsyncIOBackend.checkpoint()
1827
await self._protocol.write_event.wait()
1829
raise ClosedResourceError
1830
elif self._transport.is_closing():
1831
raise BrokenResourceError
1833
self._transport.sendto(item)
1836
class UNIXDatagramSocket(_RawSocketMixin, abc.UNIXDatagramSocket):
1837
async def receive(self) -> UNIXDatagramPacketType:
1838
loop = get_running_loop()
1839
await AsyncIOBackend.checkpoint()
1840
with self._receive_guard:
1843
data = self._raw_socket.recvfrom(65536)
1844
except BlockingIOError:
1845
await self._wait_until_readable(loop)
1846
except OSError as exc:
1848
raise ClosedResourceError from None
1850
raise BrokenResourceError from exc
1854
async def send(self, item: UNIXDatagramPacketType) -> None:
1855
loop = get_running_loop()
1856
await AsyncIOBackend.checkpoint()
1857
with self._send_guard:
1860
self._raw_socket.sendto(*item)
1861
except BlockingIOError:
1862
await self._wait_until_writable(loop)
1863
except OSError as exc:
1865
raise ClosedResourceError from None
1867
raise BrokenResourceError from exc
1872
class ConnectedUNIXDatagramSocket(_RawSocketMixin, abc.ConnectedUNIXDatagramSocket):
1873
async def receive(self) -> bytes:
1874
loop = get_running_loop()
1875
await AsyncIOBackend.checkpoint()
1876
with self._receive_guard:
1879
data = self._raw_socket.recv(65536)
1880
except BlockingIOError:
1881
await self._wait_until_readable(loop)
1882
except OSError as exc:
1884
raise ClosedResourceError from None
1886
raise BrokenResourceError from exc
1890
async def send(self, item: bytes) -> None:
1891
loop = get_running_loop()
1892
await AsyncIOBackend.checkpoint()
1893
with self._send_guard:
1896
self._raw_socket.send(item)
1897
except BlockingIOError:
1898
await self._wait_until_writable(loop)
1899
except OSError as exc:
1901
raise ClosedResourceError from None
1903
raise BrokenResourceError from exc
1908
_read_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("read_events")
1909
_write_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("write_events")
1917
class Event(BaseEvent):
1918
__slots__ = ("_event",)
1920
def __new__(cls) -> Self:
1921
return object.__new__(cls)
1923
def __init__(self) -> None:
1924
self._event = asyncio.Event()
1926
def set(self) -> None:
1929
def is_set(self) -> bool:
1930
return self._event.is_set()
1932
async def wait(self) -> None:
1934
await AsyncIOBackend.checkpoint()
1936
await self._event.wait()
1938
def statistics(self) -> EventStatistics:
1939
return EventStatistics(len(self._event._waiters))
1942
class Lock(BaseLock):
1943
__slots__ = "_fast_acquire", "_owner_task", "_waiters"
1945
def __new__(cls, *, fast_acquire: bool = False) -> Self:
1946
return object.__new__(cls)
1948
def __init__(self, *, fast_acquire: bool = False) -> None:
1949
self._fast_acquire = fast_acquire
1950
self._owner_task: asyncio.Task | None = None
1951
self._waiters: deque[tuple[asyncio.Task, asyncio.Future]] = deque()
1953
async def acquire(self) -> None:
1954
task = cast(asyncio.Task, current_task())
1955
if self._owner_task is None and not self._waiters:
1956
await AsyncIOBackend.checkpoint_if_cancelled()
1957
self._owner_task = task
1959
# Unless on the "fast path", yield control of the event loop so that other
1961
if not self._fast_acquire:
1963
await AsyncIOBackend.cancel_shielded_checkpoint()
1964
except CancelledError:
1970
if self._owner_task == task:
1971
raise RuntimeError("Attempted to acquire an already held Lock")
1973
fut: asyncio.Future[None] = asyncio.Future()
1975
self._waiters.append(item)
1978
except CancelledError:
1981
self._waiters.remove(item)
1989
def acquire_nowait(self) -> None:
1990
task = cast(asyncio.Task, current_task())
1991
if self._owner_task is None and not self._waiters:
1992
self._owner_task = task
1995
if self._owner_task is task:
1996
raise RuntimeError("Attempted to acquire an already held Lock")
2000
def locked(self) -> bool:
2001
return self._owner_task is not None
2003
def release(self) -> None:
2004
if self._owner_task != current_task():
2005
raise RuntimeError("The current task is not holding this lock")
2007
# A cancelled waiter that already received ownership removes itself from
2008
# _waiters before calling release(); any cancelled waiter still queued here
2009
# was cancelled before being woken, so drop it.
2010
while self._waiters:
2011
task, fut = self._waiters.popleft()
2015
self._owner_task = task
2016
fut.set_result(None)
2019
self._owner_task = None
2021
def statistics(self) -> LockStatistics:
2022
task_info = AsyncIOTaskInfo(self._owner_task) if self._owner_task else None
2023
return LockStatistics(self.locked(), task_info, len(self._waiters))
2026
class Semaphore(BaseSemaphore):
2027
__slots__ = "_fast_acquire", "_max_value", "_value", "_waiters"
2033
max_value: int | None = None,
2034
fast_acquire: bool = False,
2036
return object.__new__(cls)
2042
max_value: int | None = None,
2043
fast_acquire: bool = False,
2045
super().__init__(initial_value, max_value=max_value)
2046
self._value = initial_value
2047
self._max_value = max_value
2048
self._fast_acquire = fast_acquire
2049
self._waiters: deque[asyncio.Future[None]] = deque()
2051
async def acquire(self) -> None:
2052
if self._value > 0 and not self._waiters:
2053
await AsyncIOBackend.checkpoint_if_cancelled()
2056
# Unless on the "fast path", yield control of the event loop so that other
2058
if not self._fast_acquire:
2060
await AsyncIOBackend.cancel_shielded_checkpoint()
2061
except CancelledError:
2067
fut: asyncio.Future[None] = asyncio.Future()
2068
self._waiters.append(fut)
2071
except CancelledError:
2074
self._waiters.remove(fut)
2082
def acquire_nowait(self) -> None:
2083
if self._value == 0:
2088
def release(self) -> None:
2089
if self._max_value is not None and self._value == self._max_value:
2090
raise ValueError("semaphore released too many times")
2092
while self._waiters:
2093
fut = self._waiters.popleft()
2097
fut.set_result(None)
2103
def value(self) -> int:
2107
def max_value(self) -> int | None:
2108
return self._max_value
2110
def statistics(self) -> SemaphoreStatistics:
2111
return SemaphoreStatistics(len(self._waiters))
2114
class CapacityLimiter(BaseCapacityLimiter):
2115
__slots__ = "_borrowers", "_total_tokens", "_wait_queue"
2117
def __new__(cls, total_tokens: float) -> Self:
2118
return object.__new__(cls)
2120
def __init__(self, total_tokens: float):
2121
self._total_tokens: float = 0
2122
self._borrowers: set[Any] = set()
2123
self._wait_queue: OrderedDict[Any, asyncio.Event] = OrderedDict()
2124
self.total_tokens = total_tokens
2126
async def __aenter__(self) -> None:
2127
await self.acquire()
2129
async def __aexit__(
2131
exc_type: type[BaseException] | None,
2132
exc_val: BaseException | None,
2133
exc_tb: TracebackType | None,
2138
def total_tokens(self) -> float:
2139
return self._total_tokens
2141
@total_tokens.setter
2142
def total_tokens(self, value: float) -> None:
2143
if not isinstance(value, int) and not math.isinf(value):
2144
raise TypeError("total_tokens must be an int or math.inf")
2147
raise ValueError("total_tokens must be >= 0")
2149
self._total_tokens = value
2151
# Notify waiting tasks that they have acquired the limiter while
2152
# there is spare capacity.
2153
while self._wait_queue and len(self._borrowers) < self._total_tokens:
2154
borrower, event = self._wait_queue.popitem(last=False)
2155
self._borrowers.add(borrower)
2159
def borrowed_tokens(self) -> int:
2160
return len(self._borrowers)
2163
def available_tokens(self) -> float:
2164
return self._total_tokens - len(self._borrowers)
2166
def _notify_next_waiter(self) -> None:
2167
"""Hand a free token to the next task in line, if any."""
2168
if self._wait_queue and len(self._borrowers) < self._total_tokens:
2169
borrower, event = self._wait_queue.popitem(last=False)
2170
self._borrowers.add(borrower)
2173
def acquire_nowait(self) -> None:
2174
self.acquire_on_behalf_of_nowait(current_task())
2176
def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
2177
if borrower in self._borrowers:
2179
"this borrower is already holding one of this CapacityLimiter's tokens"
2182
if self._wait_queue or len(self._borrowers) >= self._total_tokens:
2185
self._borrowers.add(borrower)
2187
async def acquire(self) -> None:
2188
return await self.acquire_on_behalf_of(current_task())
2190
async def acquire_on_behalf_of(self, borrower: object) -> None:
2191
await AsyncIOBackend.checkpoint_if_cancelled()
2193
self.acquire_on_behalf_of_nowait(borrower)
2195
event = asyncio.Event()
2196
self._wait_queue[borrower] = event
2199
except BaseException:
2200
self._wait_queue.pop(borrower, None)
2202
self._borrowers.discard(borrower)
2203
self._notify_next_waiter()
2208
await AsyncIOBackend.cancel_shielded_checkpoint()
2209
except BaseException:
2213
def release(self) -> None:
2214
self.release_on_behalf_of(current_task())
2216
def release_on_behalf_of(self, borrower: object) -> None:
2218
self._borrowers.remove(borrower)
2221
"this borrower isn't holding any of this CapacityLimiter's tokens"
2224
self._notify_next_waiter()
2226
def statistics(self) -> CapacityLimiterStatistics:
2227
return CapacityLimiterStatistics(
2228
self.borrowed_tokens,
2230
tuple(self._borrowers),
2231
len(self._wait_queue),
2235
_default_thread_limiter: RunVar[CapacityLimiter] = RunVar("_default_thread_limiter")
2239
# Operating system signals
2243
class _SignalReceiver:
2244
def __init__(self, signals: tuple[Signals, ...]):
2245
self._signals = signals
2246
self._loop = get_running_loop()
2247
self._signal_queue: deque[Signals] = deque()
2248
self._future: asyncio.Future = asyncio.Future()
2249
self._handled_signals: set[Signals] = set()
2251
def _deliver(self, signum: Signals) -> None:
2252
self._signal_queue.append(signum)
2253
if not self._future.done():
2254
self._future.set_result(None)
2256
def __enter__(self) -> Self:
2257
for sig in set(self._signals):
2258
self._loop.add_signal_handler(sig, self._deliver, sig)
2259
self._handled_signals.add(sig)
2265
exc_type: type[BaseException] | None,
2266
exc_val: BaseException | None,
2267
exc_tb: TracebackType | None,
2269
for sig in self._handled_signals:
2270
self._loop.remove_signal_handler(sig)
2272
def __aiter__(self) -> _SignalReceiver:
2275
async def __anext__(self) -> Signals:
2276
await AsyncIOBackend.checkpoint()
2277
if not self._signal_queue:
2278
self._future = asyncio.Future()
2281
return self._signal_queue.popleft()
2285
# Testing and debugging
2289
class AsyncIOTaskInfo(TaskInfo):
2290
def __init__(self, task: asyncio.Task):
2291
task_state = _task_states.get(task)
2292
if task_state is None:
2295
parent_id = task_state.parent_id
2297
coro = task.get_coro()
2298
assert coro is not None, "created TaskInfo from a completed Task"
2299
super().__init__(id(task), parent_id, task.get_name(), coro)
2300
self._task = weakref.ref(task)
2302
def has_pending_cancellation(self) -> bool:
2303
if not (task := self._task()):
2304
# If the task isn't around anymore, it won't have a pending cancellation
2307
if task._must_cancel or ( # type: ignore[attr-defined]
2308
isinstance(task._fut_waiter, asyncio.Future) # type: ignore[attr-defined]
2309
and task._fut_waiter.cancelled() # type: ignore[attr-defined]
2313
if task_state := _task_states.get(task):
2314
if cancel_scope := task_state.cancel_scope:
2315
return cancel_scope._effectively_cancelled
2320
class TestRunner(abc.TestRunner):
2321
_send_stream: MemoryObjectSendStream[tuple[Awaitable[Any], asyncio.Future[Any]]]
2326
debug: bool | None = None,
2327
use_uvloop: bool = False,
2328
loop_factory: Callable[[], AbstractEventLoop] | None = None,
2330
if use_uvloop and loop_factory is None:
2331
if sys.platform != "win32":
2334
loop_factory = uvloop.new_event_loop
2338
loop_factory = winloop.new_event_loop
2340
self._runner = Runner(debug=debug, loop_factory=loop_factory)
2341
self._exceptions: list[BaseException] = []
2342
self._runner_task: asyncio.Task | None = None
2344
def __enter__(self) -> Self:
2345
self._runner.__enter__()
2346
self.get_loop().set_exception_handler(self._exception_handler)
2351
exc_type: type[BaseException] | None,
2352
exc_val: BaseException | None,
2353
exc_tb: TracebackType | None,
2355
self._runner.__exit__(exc_type, exc_val, exc_tb)
2357
def get_loop(self) -> AbstractEventLoop:
2358
return self._runner.get_loop()
2360
def is_running(self) -> bool:
2362
asyncio.get_running_loop()
2364
except RuntimeError:
2367
def _exception_handler(
2368
self, loop: asyncio.AbstractEventLoop, context: dict[str, Any]
2370
if isinstance(context.get("exception"), Exception):
2371
self._exceptions.append(context["exception"])
2373
loop.default_exception_handler(context)
2375
def _raise_async_exceptions(self) -> None:
2376
# Re-raise any exceptions raised in asynchronous callbacks
2377
if self._exceptions:
2378
exceptions, self._exceptions = self._exceptions, []
2379
if len(exceptions) == 1:
2382
raise BaseExceptionGroup(
2383
"Multiple exceptions occurred in asynchronous callbacks", exceptions
2386
async def _run_tests_and_fixtures(
2388
receive_stream: MemoryObjectReceiveStream[
2389
tuple[Awaitable[T_Retval], asyncio.Future[T_Retval]]
2392
from _pytest.outcomes import OutcomeException
2394
with receive_stream, self._send_stream:
2395
async for coro, future in receive_stream:
2398
except CancelledError as exc:
2399
if not future.cancelled():
2400
future.cancel(*exc.args)
2403
except BaseException as exc:
2404
if not future.cancelled():
2405
future.set_exception(exc)
2407
if not isinstance(exc, (Exception, OutcomeException)):
2410
if not future.cancelled():
2411
future.set_result(retval)
2413
async def _call_in_runner_task(
2415
func: Callable[P, Awaitable[T_Retval]],
2420
if not self._runner_task:
2421
self._send_stream, receive_stream = create_memory_object_stream[
2422
tuple[Awaitable[Any], asyncio.Future]
2424
self._runner_task = self.get_loop().create_task(
2425
self._run_tests_and_fixtures(receive_stream)
2428
coro = func(*args, **kwargs)
2429
future: asyncio.Future[T_Retval] = self.get_loop().create_future()
2430
self._send_stream.send_nowait((coro, future))
2433
def run_asyncgen_fixture(
2435
fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]],
2436
kwargs: dict[str, Any],
2437
) -> Iterable[T_Retval]:
2438
asyncgen = fixture_func(**kwargs)
2439
fixturevalue: T_Retval = self.get_loop().run_until_complete(
2440
self._call_in_runner_task(asyncgen.asend, None)
2442
self._raise_async_exceptions()
2447
self.get_loop().run_until_complete(
2448
self._call_in_runner_task(asyncgen.asend, None)
2450
except StopAsyncIteration:
2451
self._raise_async_exceptions()
2453
self.get_loop().run_until_complete(asyncgen.aclose())
2454
raise RuntimeError("Async generator fixture did not stop")
2458
fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]],
2459
kwargs: dict[str, Any],
2461
retval = self.get_loop().run_until_complete(
2462
self._call_in_runner_task(fixture_func, **kwargs)
2464
self._raise_async_exceptions()
2468
self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any]
2470
from _pytest.outcomes import OutcomeException
2473
self.get_loop().run_until_complete(
2474
self._call_in_runner_task(test_func, **kwargs)
2476
except Exception as exc:
2477
self._exceptions.append(exc)
2478
except OutcomeException:
2480
except BaseException:
2481
# A BaseException (e.g. KeyboardInterrupt, SystemExit) interrupted the event loop before
2482
# the test completed. Cancel _runner_task so it does not resume when the event
2483
# loop is re-entered during async generator fixture teardown.
2484
if self._runner_task is not None and not self._runner_task.done():
2485
self._runner_task.cancel()
2486
self._send_stream.close()
2488
self.get_loop().run_until_complete(self._runner_task)
2489
except CancelledError:
2492
self._runner_task = None
2494
self._raise_async_exceptions()
2497
class _ProcessStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol):
2499
A subprocess protocol that allows us to be notified of ``process_exited``
2501
asyncio's own ``Process.wait()`` only resolves once every pipe transport has
2502
disconnected so to get same semantics as on trio and uvloop we need this.
2505
def __init__(self) -> None:
2506
# Match the standard factory for asyncio.create_process
2507
super().__init__(limit=2**16, loop=asyncio.get_running_loop())
2508
self.exited = asyncio.Event()
2510
def process_exited(self) -> None:
2511
super().process_exited()
2515
class AsyncIOBackend(AsyncBackend):
2519
func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
2520
args: tuple[Unpack[PosArgsT]],
2521
kwargs: dict[str, Any],
2522
options: dict[str, Any],
2525
async def wrapper() -> T_Retval:
2526
task = cast(asyncio.Task, current_task())
2527
task.set_name(get_callable_name(func))
2528
_task_states[task] = TaskState(None, None)
2531
return await func(*args)
2533
del _task_states[task]
2535
debug = options.get("debug", None)
2536
loop_factory = options.get("loop_factory", None)
2537
if loop_factory is None and options.get("use_uvloop", False):
2538
if sys.platform != "win32":
2541
loop_factory = uvloop.new_event_loop
2545
loop_factory = winloop.new_event_loop
2547
with Runner(debug=debug, loop_factory=loop_factory) as runner:
2548
return runner.run(wrapper())
2551
def current_token(cls) -> object:
2552
return get_running_loop()
2555
def current_time(cls) -> float:
2556
return get_running_loop().time()
2559
def cancelled_exception_class(cls) -> type[BaseException]:
2560
return CancelledError
2563
async def checkpoint(cls) -> None:
2567
async def checkpoint_if_cancelled(cls) -> None:
2568
task = current_task()
2573
cancel_scope = _task_states[task].cancel_scope
2578
if cancel_scope.cancel_called:
2580
elif cancel_scope.shield:
2583
cancel_scope = cancel_scope._parent_scope
2586
async def cancel_shielded_checkpoint(cls) -> None:
2587
with CancelScope(shield=True):
2591
async def sleep(cls, delay: float) -> None:
2595
def create_cancel_scope(
2596
cls, *, deadline: float = math.inf, shield: bool = False
2598
return CancelScope(deadline=deadline, shield=shield)
2601
def current_effective_deadline(cls) -> float:
2602
if (task := current_task()) is None:
2606
cancel_scope = _task_states[task].cancel_scope
2612
deadline = min(deadline, cancel_scope.deadline)
2613
if cancel_scope._cancel_called:
2614
deadline = -math.inf
2616
elif cancel_scope.shield:
2619
cancel_scope = cancel_scope._parent_scope
2624
def create_task_group(cls) -> abc.TaskGroup:
2628
def create_event(cls) -> BaseEvent:
2632
def create_lock(cls, *, fast_acquire: bool) -> BaseLock:
2633
return Lock(fast_acquire=fast_acquire)
2636
def create_semaphore(
2640
max_value: int | None = None,
2641
fast_acquire: bool = False,
2643
return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire)
2646
def create_capacity_limiter(cls, total_tokens: float) -> BaseCapacityLimiter:
2647
return CapacityLimiter(total_tokens)
2650
async def run_sync_in_worker_thread( # type: ignore[return]
2652
func: Callable[[Unpack[PosArgsT]], T_Retval],
2653
args: tuple[Unpack[PosArgsT]],
2654
abandon_on_cancel: bool = False,
2655
limiter: BaseCapacityLimiter | None = None,
2657
await cls.checkpoint()
2659
# If this is the first run in this event loop thread, set up the necessary
2662
idle_workers = _threadpool_idle_workers.get()
2663
workers = _threadpool_workers.get()
2665
idle_workers = deque()
2667
_threadpool_idle_workers.set(idle_workers)
2668
_threadpool_workers.set(workers)
2670
async with limiter or cls.current_default_thread_limiter():
2671
with CancelScope(shield=not abandon_on_cancel) as scope:
2672
future = asyncio.Future[T_Retval]()
2673
root_task = find_root_task()
2674
if not idle_workers:
2675
worker = WorkerThread(root_task, workers, idle_workers)
2678
root_task.add_done_callback(worker.stop, context=Context())
2680
worker = idle_workers.pop()
2682
# Prune any other workers that have been idle for MAX_IDLE_TIME
2684
now = cls.current_time()
2687
now - idle_workers[0].idle_since
2688
< WorkerThread.MAX_IDLE_TIME
2692
expired_worker = idle_workers.popleft()
2693
expired_worker.root_task.remove_done_callback(
2696
expired_worker.stop()
2698
context = copy_context()
2699
context.run(set_current_async_library, None)
2700
if abandon_on_cancel or scope._parent_scope is None:
2701
worker_scope = scope
2703
worker_scope = scope._parent_scope
2705
worker.queue.put_nowait((context, func, args, future, worker_scope))
2709
def check_cancelled(cls) -> None:
2710
scope: CancelScope | None = threadlocals.current_cancel_scope
2711
while scope is not None:
2712
if scope.cancel_called:
2713
raise CancelledError(f"Cancelled via cancel scope {id(scope):x}")
2718
scope = scope._parent_scope
2721
def run_async_from_thread(
2723
func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
2724
args: tuple[Unpack[PosArgsT]],
2727
async def task_wrapper() -> T_co:
2728
__tracebackhide__ = True
2729
if scope is not None:
2730
task = cast(asyncio.Task, current_task())
2731
_task_states[task] = TaskState(None, scope)
2732
scope._tasks.add(task)
2734
return await func(*args)
2735
except CancelledError as exc:
2736
raise concurrent.futures.CancelledError(str(exc)) from None
2738
if scope is not None:
2739
scope._tasks.discard(task)
2742
"AbstractEventLoop", token or threadlocals.current_token.native_token
2744
if loop.is_closed():
2745
raise RunFinishedError
2747
context = copy_context()
2748
context.run(set_current_async_library, "asyncio")
2749
scope = getattr(threadlocals, "current_cancel_scope", None)
2750
f: concurrent.futures.Future[T_co] = context.run(
2751
asyncio.run_coroutine_threadsafe, task_wrapper(), loop=loop
2756
def run_sync_from_thread(
2758
func: Callable[[Unpack[PosArgsT]], T_Retval],
2759
args: tuple[Unpack[PosArgsT]],
2763
def wrapper() -> None:
2765
set_current_async_library("asyncio")
2766
f.set_result(func(*args))
2767
except BaseException as exc:
2768
f.set_exception(exc)
2769
if not isinstance(exc, Exception):
2773
"AbstractEventLoop", token or threadlocals.current_token.native_token
2775
if loop.is_closed():
2776
raise RunFinishedError
2778
f: concurrent.futures.Future[T_Retval] = Future()
2779
loop.call_soon_threadsafe(wrapper)
2783
async def open_process(
2785
command: StrOrBytesPath | Sequence[StrOrBytesPath],
2787
stdin: int | IO[Any] | None,
2788
stdout: int | IO[Any] | None,
2789
stderr: int | IO[Any] | None,
2792
await cls.checkpoint()
2793
if isinstance(command, PathLike):
2794
command = os.fspath(command)
2796
# Use loop.subprocess_shell()/subprocess_exec() rather than their
2797
# asyncio.create_subprocess_*() counterparts to get access to
2798
# transport/protocol.
2799
loop = asyncio.get_running_loop()
2800
if isinstance(command, (str, bytes)):
2801
transport, protocol = await loop.subprocess_shell(
2802
_ProcessStreamProtocol,
2810
transport, protocol = await loop.subprocess_exec(
2811
_ProcessStreamProtocol,
2819
process = asyncio.subprocess.Process(transport, protocol, loop)
2820
stdin_stream = StreamWriterWrapper(process.stdin) if process.stdin else None
2821
stdout_stream = StreamReaderWrapper(process.stdout) if process.stdout else None
2822
stderr_stream = StreamReaderWrapper(process.stderr) if process.stderr else None
2833
def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None:
2835
_shutdown_process_pool_on_exit(workers),
2836
name="AnyIO process pool shutdown task",
2838
find_root_task().add_done_callback(
2839
partial(_forcibly_shutdown_process_pool_on_exit, workers) # type:ignore[arg-type]
2843
async def connect_tcp(
2844
cls, host: str, port: int, local_address: IPSockAddrType | None = None
2845
) -> abc.SocketStream:
2846
transport, protocol = cast(
2847
tuple[asyncio.Transport, StreamProtocol],
2848
await get_running_loop().create_connection(
2849
StreamProtocol, host, port, local_addr=local_address
2852
transport.pause_reading()
2853
return SocketStream(transport, protocol)
2856
async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream:
2857
await cls.checkpoint()
2858
loop = get_running_loop()
2859
raw_socket = socket.socket(socket.AF_UNIX)
2860
raw_socket.setblocking(False)
2863
raw_socket.connect(path)
2864
except BlockingIOError:
2865
f: asyncio.Future = asyncio.Future()
2866
loop.add_writer(raw_socket, f.set_result, None)
2867
f.add_done_callback(lambda _: loop.remove_writer(raw_socket))
2869
except BaseException:
2873
return UNIXSocketStream(raw_socket)
2876
def create_tcp_listener(cls, sock: socket.socket) -> SocketListener:
2877
return TCPSocketListener(sock)
2880
def create_unix_listener(cls, sock: socket.socket) -> SocketListener:
2881
return UNIXSocketListener(sock)
2884
async def create_udp_socket(
2886
family: AddressFamily,
2887
local_address: IPSockAddrType | None,
2888
remote_address: IPSockAddrType | None,
2890
) -> UDPSocket | ConnectedUDPSocket:
2891
transport, protocol = await get_running_loop().create_datagram_endpoint(
2893
local_addr=local_address,
2894
remote_addr=remote_address,
2896
reuse_port=reuse_port,
2898
if protocol.exception:
2900
raise protocol.exception
2902
if not remote_address:
2903
return UDPSocket(transport, protocol)
2905
return ConnectedUDPSocket(transport, protocol)
2908
async def create_unix_datagram_socket( # type: ignore[override]
2909
cls, raw_socket: socket.socket, remote_path: str | bytes | None
2910
) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket:
2911
await cls.checkpoint()
2912
loop = get_running_loop()
2917
raw_socket.connect(remote_path)
2918
except BlockingIOError:
2919
f: asyncio.Future = asyncio.Future()
2920
loop.add_writer(raw_socket, f.set_result, None)
2921
f.add_done_callback(lambda _: loop.remove_writer(raw_socket))
2923
except BaseException:
2927
return ConnectedUNIXDatagramSocket(raw_socket)
2929
return UNIXDatagramSocket(raw_socket)
2932
async def getaddrinfo(
2934
host: bytes | str | None,
2935
port: str | int | None,
2937
family: int | AddressFamily = 0,
2938
type: int | SocketKind = 0,
2947
tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes],
2950
return await get_running_loop().getaddrinfo(
2951
host, port, family=family, type=type, proto=proto, flags=flags
2955
async def getnameinfo(
2956
cls, sockaddr: IPSockAddrType, flags: int = 0
2957
) -> tuple[str, str]:
2958
return await get_running_loop().getnameinfo(sockaddr, flags)
2961
async def wait_readable(cls, obj: FileDescriptorLike) -> None:
2963
read_events = _read_events.get()
2966
_read_events.set(read_events)
2968
fd = obj if isinstance(obj, int) else obj.fileno()
2969
if read_events.get(fd):
2970
raise BusyResourceError("reading from")
2972
loop = get_running_loop()
2973
fut: asyncio.Future[bool] = loop.create_future()
2984
fut.set_result(True)
2985
except asyncio.InvalidStateError:
2989
loop.add_reader(fd, cb)
2990
except NotImplementedError:
2991
from anyio._core._asyncio_selector_thread import get_selector
2993
selector = get_selector()
2994
selector.add_reader(fd, cb)
2995
remove_reader = selector.remove_reader
2997
remove_reader = loop.remove_reader
2999
read_events[fd] = fut
3011
raise ClosedResourceError
3014
async def wait_writable(cls, obj: FileDescriptorLike) -> None:
3016
write_events = _write_events.get()
3019
_write_events.set(write_events)
3021
fd = obj if isinstance(obj, int) else obj.fileno()
3022
if write_events.get(fd):
3023
raise BusyResourceError("writing to")
3025
loop = get_running_loop()
3026
fut: asyncio.Future[bool] = loop.create_future()
3030
del write_events[fd]
3037
fut.set_result(True)
3038
except asyncio.InvalidStateError:
3042
loop.add_writer(fd, cb)
3043
except NotImplementedError:
3044
from anyio._core._asyncio_selector_thread import get_selector
3046
selector = get_selector()
3047
selector.add_writer(fd, cb)
3048
remove_writer = selector.remove_writer
3050
remove_writer = loop.remove_writer
3052
write_events[fd] = fut
3057
del write_events[fd]
3064
raise ClosedResourceError
3067
def notify_closing(cls, obj: FileDescriptorLike) -> None:
3068
fd = obj if isinstance(obj, int) else obj.fileno()
3069
loop = get_running_loop()
3072
write_events = _write_events.get()
3077
fut = write_events.pop(fd)
3082
fut.set_result(False)
3083
except asyncio.InvalidStateError:
3087
loop.remove_writer(fd)
3088
except NotImplementedError:
3089
from anyio._core._asyncio_selector_thread import get_selector
3091
get_selector().remove_writer(fd)
3094
read_events = _read_events.get()
3099
fut = read_events.pop(fd)
3104
fut.set_result(False)
3105
except asyncio.InvalidStateError:
3109
loop.remove_reader(fd)
3110
except NotImplementedError:
3111
from anyio._core._asyncio_selector_thread import get_selector
3113
get_selector().remove_reader(fd)
3116
async def wrap_listener_socket(cls, sock: socket.socket) -> SocketListener:
3117
if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX:
3118
return UNIXSocketListener(sock)
3120
return TCPSocketListener(sock)
3123
async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream:
3124
transport, protocol = await get_running_loop().create_connection(
3125
StreamProtocol, sock=sock
3127
return SocketStream(transport, protocol)
3130
async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream:
3131
return UNIXSocketStream(sock)
3134
async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket:
3135
transport, protocol = await get_running_loop().create_datagram_endpoint(
3136
DatagramProtocol, sock=sock
3138
return UDPSocket(transport, protocol)
3141
async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket:
3142
transport, protocol = await get_running_loop().create_datagram_endpoint(
3143
DatagramProtocol, sock=sock
3145
return ConnectedUDPSocket(transport, protocol)
3148
async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket:
3149
return UNIXDatagramSocket(sock)
3152
async def wrap_connected_unix_datagram_socket(
3153
cls, sock: socket.socket
3154
) -> ConnectedUNIXDatagramSocket:
3155
return ConnectedUNIXDatagramSocket(sock)
3158
def current_default_thread_limiter(cls) -> CapacityLimiter:
3160
return _default_thread_limiter.get()
3162
limiter = CapacityLimiter(40)
3163
_default_thread_limiter.set(limiter)
3167
def open_signal_receiver(
3168
cls, *signals: Signals
3169
) -> AbstractContextManager[AsyncIterator[Signals]]:
3170
return _SignalReceiver(signals)
3173
def get_current_task(cls) -> TaskInfo:
3174
return AsyncIOTaskInfo(current_task()) # type: ignore[arg-type]
3177
def get_running_tasks(cls) -> Sequence[TaskInfo]:
3178
return [AsyncIOTaskInfo(task) for task in all_tasks() if not task.done()]
3181
async def wait_all_tasks_blocked(cls) -> None:
3182
await cls.checkpoint()
3183
this_task = current_task()
3185
for task in all_tasks():
3186
if task is this_task:
3189
waiter = task._fut_waiter # type: ignore[attr-defined]
3190
if waiter is None or waiter.done():
3197
def create_test_runner(cls, options: dict[str, Any]) -> TestRunner:
3198
return TestRunner(**options)
3201
backend_class = AsyncIOBackend