Tachyon /Users/simon/Documents/Codex/2026-10-12/i-need-a-python-3-15/.venv/lib/python3.15/site-packages/anyio/_backends/_asyncio.py
34
Self Samples
14546
Cumulative
5
Lines Hit
49.97%
% of Total
12
Max Self
14503
Max Total
Intensity:
Cold Hot
Self Time
Total Time
Show All
Hot Only
Heat
Specialization
Line
Self
Total
Code
1
from __future__ import annotations
2
3
import array
4
import asyncio
5
import concurrent.futures
6
import math
7
import os
8
import socket
9
import sys
10
import threading
11
import weakref
12
from asyncio import (
13
AbstractEventLoop,
14
CancelledError,
15
all_tasks,
16
create_task,
17
current_task,
18
get_running_loop,
19
sleep,
20
)
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 (
24
AsyncGenerator,
25
AsyncIterator,
26
Awaitable,
27
Callable,
28
Collection,
29
Coroutine,
30
Iterable,
31
Sequence,
32
)
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
38
from inspect import (
39
CORO_RUNNING,
40
CORO_SUSPENDED,
41
getcoroutinestate,
42
)
43
from io import IOBase
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
50
from typing import (
51
IO,
52
TYPE_CHECKING,
53
Any,
54
Literal,
55
ParamSpec,
56
TypeVar,
57
cast,
58
)
59
from weakref import WeakKeyDictionary
60
61
from .. import (
62
CapacityLimiterStatistics,
63
EventStatistics,
64
LockStatistics,
65
TaskInfo,
66
abc,
67
)
68
from .._core._eventloop import (
69
claim_worker_thread,
70
set_current_async_library,
71
threadlocals,
72
)
73
from .._core._exceptions import (
74
BrokenResourceError,
75
BusyResourceError,
76
ClosedResourceError,
77
EndOfStream,
78
RunFinishedError,
79
WouldBlock,
80
)
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,
85
)
86
from .._core._synchronization import Event as BaseEvent
87
from .._core._synchronization import Lock as BaseLock
88
from .._core._synchronization import (
89
ResourceGuard,
90
SemaphoreStatistics,
91
)
92
from .._core._synchronization import Semaphore as BaseSemaphore
93
from .._core._tasks import CancelScope as BaseCancelScope
94
from .._core._tasks import TaskHandle
95
from ..abc import (
96
AsyncBackend,
97
IPSockAddrType,
98
SocketListener,
99
UDPPacketType,
100
UNIXDatagramPacketType,
101
)
102
from ..abc._tasks import call_for_coroutine, get_callable_name, get_coro_name
103
from ..lowlevel import RunVar, _run_vars
104
105
if TYPE_CHECKING:
106
from _typeshed import FileDescriptorLike
107
108
from ..abc._eventloop import StrOrBytesPath
109
from ..streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
110
else:
111
FileDescriptorLike = object
112
113
if sys.version_info >= (3, 11):
114
from asyncio import Runner
115
from typing import Self, TypeVarTuple, Unpack
116
else:
117
import contextvars
118
import enum
119
import signal
120
from asyncio import coroutines, events, exceptions, tasks
121
122
from exceptiongroup import BaseExceptionGroup
123
from typing_extensions import Self, TypeVarTuple, Unpack
124
125
class _State(enum.Enum):
126
CREATED = "created"
127
INITIALIZED = "initialized"
128
CLOSED = "closed"
129
130
class Runner:
131
# Copied from CPython 3.11
132
def __init__(
133
self,
134
*,
135
debug: bool | None = None,
136
loop_factory: Callable[[], AbstractEventLoop] | None = None,
137
):
138
self._state = _State.CREATED
139
self._debug = debug
140
self._loop_factory = loop_factory
141
self._loop: AbstractEventLoop | None = None
142
self._context = None
143
self._interrupt_count = 0
144
self._set_event_loop = False
145
146
def __enter__(self) -> Self:
147
self._lazy_init()
148
return self
149
150
def __exit__(
151
self,
152
exc_type: type[BaseException] | None,
153
exc_val: BaseException | None,
154
exc_tb: TracebackType | None,
155
) -> None:
156
self.close()
157
158
def close(self) -> None:
159
"""Shutdown and close event loop."""
160
loop = self._loop
161
if self._state is not _State.INITIALIZED or loop is None:
162
return
163
try:
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())
168
else:
169
loop.run_until_complete(_shutdown_default_executor(loop))
170
finally:
171
if self._set_event_loop:
172
events.set_event_loop(None)
173
loop.close()
174
self._loop = None
175
self._state = _State.CLOSED
176
177
def get_loop(self) -> AbstractEventLoop:
178
"""Return embedded event loop."""
179
self._lazy_init()
180
return self._loop
181
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}")
186
187
if events._get_running_loop() is not None:
188
# fail fast with short traceback
189
raise RuntimeError(
190
"Runner.run() cannot be called from a running event loop"
191
)
192
193
self._lazy_init()
194
195
if context is None:
196
context = self._context
197
task = context.run(self._loop.create_task, coro)
198
199
if (
200
threading.current_thread() is threading.main_thread()
201
and signal.getsignal(signal.SIGINT) is signal.default_int_handler
202
):
203
sigint_handler = partial(self._on_sigint, main_task=task)
204
try:
205
signal.signal(signal.SIGINT, sigint_handler)
206
except ValueError:
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
211
else:
212
sigint_handler = None
213
214
self._interrupt_count = 0
215
try:
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
223
finally:
224
if (
225
sigint_handler is not None
226
and signal.getsignal(signal.SIGINT) is sigint_handler
227
):
228
signal.signal(signal.SIGINT, signal.default_int_handler)
229
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:
234
return
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
242
else:
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
248
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():
252
main_task.cancel()
253
# wakeup loop if it is blocked by select() with long timeout
254
self._loop.call_soon_threadsafe(lambda: None)
255
return
256
raise KeyboardInterrupt()
257
258
def _cancel_all_tasks(loop: AbstractEventLoop) -> None:
259
to_cancel = tasks.all_tasks(loop)
260
if not to_cancel:
261
return
262
263
for task in to_cancel:
264
task.cancel()
265
266
loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
267
268
for task in to_cancel:
269
if task.cancelled():
270
continue
271
if task.exception() is not None:
272
loop.call_exception_handler(
273
{
274
"message": "unhandled exception during asyncio.run() shutdown",
275
"exception": task.exception(),
276
"task": task,
277
}
278
)
279
280
async def _shutdown_default_executor(loop: AbstractEventLoop) -> None:
281
"""Schedule the shutdown of the default executor."""
282
283
def _do_shutdown(future: asyncio.futures.Future) -> None:
284
try:
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)
289
290
loop._executor_shutdown_called = True
291
if loop._default_executor is None:
292
return
293
future = loop.create_future()
294
thread = threading.Thread(target=_do_shutdown, args=(future,))
295
thread.start()
296
try:
297
await future
298
finally:
299
thread.join()
300
301
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")
306
P = ParamSpec("P")
307
308
_root_task: RunVar[asyncio.Task[Any] | None] = RunVar("_root_task")
309
310
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():
314
return root_task
315
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:
320
if (
321
cb is _run_until_complete_cb
322
or getattr(cb, "__module__", None) == "uvloop.loop"
323
):
324
_root_task.set(task)
325
326
def _unset(t: asyncio.Task[Any]) -> None:
327
if vars := _run_vars.get(t.get_loop()):
328
vars.pop(_root_task, None)
329
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)
335
return task
336
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)
340
if state:
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
344
345
if cancel_scope is not None:
346
return cast(asyncio.Task, cancel_scope._host_task)
347
348
return task
349
350
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
354
# task list
355
coro = task.get_coro()
356
assert coro is not None
357
return getcoroutinestate(coro) in (CORO_RUNNING, CORO_SUSPENDED)
358
359
360
#
361
# Timeouts and cancellation
362
#
363
364
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
369
while True:
370
if (
371
exc.args
372
and isinstance(exc.args[0], str)
373
and exc.args[0].startswith("Cancelled via cancel scope ")
374
):
375
return True
376
377
if isinstance(exc.__context__, CancelledError):
378
exc = exc.__context__
379
continue
380
381
return False
382
383
384
class CancelScope(BaseCancelScope):
385
__slots__ = (
386
"_active",
387
"_cancel_called",
388
"_cancel_handle",
389
"_cancel_reason",
390
"_cancelled_caught",
391
"_child_scopes",
392
"_deadline",
393
"_host_task",
394
"_parent_scope",
395
"_pending_uncancellations",
396
"_shield",
397
"_tasks",
398
"_timeout_handle",
399
)
400
401
def __new__(cls, *, deadline: float = math.inf, shield: bool = False) -> Self:
402
return object.__new__(cls)
403
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
412
self._active = 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
419
else:
420
self._pending_uncancellations = None
421
422
def __enter__(self) -> Self:
423
if self._active:
424
raise RuntimeError(
425
"Each CancelScope may only be used for a single 'with' block"
426
)
427
428
self._host_task = host_task = cast(asyncio.Task, current_task())
429
self._tasks.add(host_task)
430
try:
431
task_state = _task_states[host_task]
432
except KeyError:
433
task_state = TaskState(None, self)
434
_task_states[host_task] = task_state
435
else:
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
440
# the host task
441
self._parent_scope._child_scopes.add(self)
442
self._parent_scope._tasks.discard(host_task)
443
444
self._timeout()
445
self._active = True
446
447
# Start cancelling the host task if the scope was cancelled before entering
448
if self._cancel_called:
449
self._deliver_cancellation(self)
450
451
return self
452
453
def __exit__(
454
self,
455
exc_type: type[BaseException] | None,
456
exc_val: BaseException | None,
457
exc_tb: TracebackType | None,
458
) -> bool:
459
del exc_tb
460
461
if not self._active:
462
raise RuntimeError("This cancel scope is not active")
463
if current_task() is not self._host_task:
464
raise RuntimeError(
465
"Attempted to exit cancel scope in a different task than it was "
466
"entered in"
467
)
468
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:
472
raise RuntimeError(
473
"Attempted to exit a cancel scope that isn't the current tasks's "
474
"current cancel scope"
475
)
476
477
try:
478
self._active = False
479
if self._timeout_handle:
480
self._timeout_handle.cancel()
481
self._timeout_handle = None
482
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)
487
488
host_task_state.cancel_scope = self._parent_scope
489
490
# Restart the cancellation effort in the closest visible, cancelled parent
491
# scope if necessary
492
self._restart_cancellation_in_parent()
493
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
502
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(
506
lambda exc: (
507
isinstance(exc, CancelledError)
508
and is_anyio_cancellation(exc)
509
)
510
)
511
512
if cancelleds_caught is None:
513
return False
514
515
self._cancelled_caught = True
516
517
if remaining is None:
518
return True
519
520
context = remaining.__context__
521
try:
522
# Preserve __cause__ and __suppress_context__ by avoiding `raise
523
# ... from ...`
524
raise remaining
525
finally:
526
# Preserve __context__
527
remaining.__context__ = context
528
del context
529
else:
530
if isinstance(exc_val, CancelledError) and is_anyio_cancellation(
531
exc_val
532
):
533
self._cancelled_caught = True
534
return True
535
else:
536
return False
537
else:
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
543
)
544
self._pending_uncancellations = 0
545
546
return False
547
finally:
548
self._host_task = None
549
del exc_val
550
551
@property
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:
556
return True
557
558
if cancel_scope.shield:
559
return False
560
561
cancel_scope = cancel_scope._parent_scope
562
563
return False
564
565
@property
566
def _parent_cancellation_is_visible_to_us(self) -> bool:
567
return (
568
self._parent_scope is not None
569
and not self.shield
570
and self._parent_scope._effectively_cancelled
571
)
572
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")
578
else:
579
self._timeout_handle = loop.call_at(self._deadline, self._timeout)
580
581
def _deliver_cancellation(self, origin: CancelScope) -> bool:
582
"""
583
Deliver cancellation to directly contained tasks and nested cancel scopes.
584
585
Schedule another run at the end if we still have tasks eligible for
586
cancellation.
587
588
:param origin: the cancel scope that originated the cancellation
589
:return: ``True`` if the delivery needs to be retried on the next cycle
590
591
"""
592
should_retry = False
593
current = current_task()
594
for task in self._tasks:
595
# Always skip tasks that are already done (see issue #1111)
596
if task.done():
597
continue
598
599
should_retry = True
600
if task._must_cancel: # type: ignore[attr-defined]
601
continue
602
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)
608
if (
609
task is origin._host_task
610
and origin._pending_uncancellations is not None
611
):
612
origin._pending_uncancellations += 1
613
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
619
620
# Schedule another callback if there are still tasks left
621
if origin is self:
622
if should_retry:
623
self._cancel_handle = get_running_loop().call_soon(
624
self._deliver_cancellation, origin
625
)
626
else:
627
self._cancel_handle = None
628
629
return should_retry
630
631
def _restart_cancellation_in_parent(self) -> None:
632
"""
633
Restart the cancellation effort in the closest directly cancelled parent scope.
634
635
"""
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)
641
642
break
643
644
# No point in looking beyond any shielded scope
645
if scope._shield:
646
break
647
648
scope = scope._parent_scope
649
650
def _reparent(self, new_parent: CancelScope) -> None:
651
"""
652
Move this active scope from its current parent to ``new_parent``.
653
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.
656
"""
657
if self._parent_scope is new_parent:
658
return
659
660
if self._parent_scope is not None:
661
self._parent_scope._child_scopes.discard(self)
662
663
self._parent_scope = new_parent
664
new_parent._child_scopes.add(self)
665
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()
669
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
675
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}"
680
681
if reason:
682
self._cancel_reason += f"; reason: {reason}"
683
684
if self._host_task is not None:
685
self._deliver_cancellation(self)
686
687
@property
688
def deadline(self) -> float:
689
return self._deadline
690
691
@deadline.setter
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
697
698
if self._active and not self._cancel_called:
699
self._timeout()
700
701
@property
702
def cancel_called(self) -> bool:
703
return self._cancel_called
704
705
@property
706
def cancelled_caught(self) -> bool:
707
return self._cancelled_caught
708
709
@property
710
def shield(self) -> bool:
711
return self._shield
712
713
@shield.setter
714
def shield(self, value: bool) -> None:
715
if self._shield != value:
716
self._shield = value
717
if not value:
718
self._restart_cancellation_in_parent()
719
720
721
#
722
# Task states
723
#
724
725
726
class TaskState:
727
"""
728
Encapsulates auxiliary task information that cannot be added to the Task instance
729
itself because there are no guarantees about its implementation.
730
"""
731
732
__slots__ = "__weakref__", "cancel_scope", "parent_id"
733
734
def __init__(self, parent_id: int | None, cancel_scope: CancelScope | None):
735
self.parent_id = parent_id
736
self.cancel_scope = cancel_scope
737
738
739
_task_states: WeakKeyDictionary[asyncio.Task, TaskState] = WeakKeyDictionary()
740
741
742
#
743
# Task groups
744
#
745
746
747
class _AsyncioTaskStatus(abc.TaskStatus[T_contra]):
748
def __init__(
749
self,
750
future: asyncio.Future,
751
parent_id: int,
752
target_scope: CancelScope,
753
spawn_scope: CancelScope,
754
):
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
762
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")
769
else:
770
# Caller of start() was cancelled, nothing to reparent
771
return
772
773
self._future.set_result(value)
774
self._spawn_scope._reparent(self._target_scope)
775
776
777
if sys.version_info >= (3, 12):
778
_eager_task_factory_code: CodeType | None = asyncio.eager_task_factory.__code__
779
else:
780
_eager_task_factory_code = None
781
782
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
790
791
async def __aenter__(self) -> Self:
792
if self._entered:
793
raise RuntimeError("TaskGroup cannot be entered more than once")
794
795
self._entered = True
796
797
self.cancel_scope.__enter__()
798
return self
799
800
async def __aexit__(
801
self,
802
exc_type: type[BaseException] | None,
803
exc_val: BaseException | None,
804
exc_tb: TracebackType | None,
805
) -> bool:
806
try:
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)
811
812
loop = get_running_loop()
813
try:
814
if self._tasks:
815
with CancelScope() as wait_scope:
816
while self._tasks:
817
self._on_completed_fut = loop.create_future()
818
819
try:
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()
826
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)
833
):
834
exc_val = exc
835
836
self._on_completed_fut = None
837
else:
838
# If there are no child tasks to wait on, run at least one checkpoint
839
# anyway
840
await AsyncIOBackend.cancel_shielded_checkpoint()
841
842
if self._exceptions:
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
849
) from None
850
elif exc_val:
851
raise exc_val
852
except BaseException as exc:
853
if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__):
854
return True
855
856
raise
857
858
return self.cancel_scope.__exit__(exc_type, exc_val, exc_tb)
859
finally:
860
del exc_val, exc_tb, self._exceptions
861
862
def _spawn(
863
self,
864
coro: Coroutine[Any, Any, T_co],
865
name: object,
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
869
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
874
)
875
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]
882
883
if self._on_completed_fut is not None and not self._tasks:
884
try:
885
self._on_completed_fut.set_result(None)
886
except asyncio.InvalidStateError:
887
pass
888
889
try:
890
exc = _task.exception()
891
except CancelledError as e:
892
while isinstance(e.__context__, CancelledError):
893
e = e.__context__
894
895
exc = e
896
897
if exc is not None:
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():
902
return
903
904
if task_status_future is None or task_status_future.done():
905
if not isinstance(exc, CancelledError):
906
self._exceptions.append(exc)
907
908
if not self.cancel_scope._effectively_cancelled:
909
self.cancel_scope.cancel()
910
else:
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()")
915
)
916
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
922
else:
923
# The caller is an unmanaged task (no task state)
924
initial_scope = self.cancel_scope
925
else:
926
parent_id = id(self.cancel_scope._host_task)
927
initial_scope = self.cancel_scope
928
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()
933
try:
934
if (
935
(factory := loop.get_task_factory())
936
and getattr(factory, "__code__", None) is _eager_task_factory_code
937
and (closure := getattr(factory, "__closure__", None))
938
):
939
custom_task_constructor = closure[0].cell_contents
940
task = custom_task_constructor(
941
wrapper_coro, loop=loop, name=handle.name
942
)
943
else:
944
task = loop.create_task(wrapper_coro, name=handle.name)
945
except BaseException:
946
with suppress(BaseException):
947
wrapper_coro.close()
948
949
with suppress(BaseException):
950
coro.close()
951
952
raise
953
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)
960
961
task.add_done_callback(task_done)
962
return handle
963
964
def create_task(
965
self,
966
coro: Coroutine[Any, Any, T_co],
967
*,
968
name: object = None,
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__}")
973
974
if not self._entered or not self.cancel_scope._active:
975
coro.close()
976
raise RuntimeError(
977
"This task group is not active; no new tasks can be started."
978
)
979
980
final_name = get_coro_name(coro, name)
981
982
if context is not None:
983
return context.run(self._spawn, coro, name=final_name)
984
else:
985
return self._spawn(coro, name=final_name)
986
987
async def start(
988
self,
989
func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
990
*args: Unpack[PosArgsT],
991
name: object = None,
992
return_handle: Literal[False, True] = False,
993
) -> Any:
994
if not self._entered or not self.cancel_scope._active:
995
raise RuntimeError(
996
"This task group is not active; no new tasks can be started."
997
)
998
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
1003
# cancel scope.
1004
#
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()
1011
)
1012
coro = call_for_coroutine(func, args, task_status=task_status)
1013
handle = self._spawn(coro, final_name, task_status)
1014
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
1018
# scope here.
1019
try:
1020
await future
1021
except BaseException:
1022
if handle.status is TaskHandle.Status.PENDING:
1023
# Cancel the task and wait for it to exit before returning
1024
handle.cancel()
1025
with CancelScope(shield=True):
1026
await handle.wait()
1027
1028
raise
1029
1030
if return_handle:
1031
handle._start_value = future.result()
1032
return handle
1033
else:
1034
return future.result()
1035
1036
1037
#
1038
# Threads
1039
#
1040
1041
_Retval_Queue_Type = tuple[T_Retval | None, BaseException | None]
1042
1043
1044
class WorkerThread(Thread):
1045
MAX_IDLE_TIME = 10 # seconds
1046
1047
def __init__(
1048
self,
1049
root_task: asyncio.Task,
1050
workers: set[WorkerThread],
1051
idle_workers: deque[WorkerThread],
1052
):
1053
kwargs: dict[str, Any] = {}
1054
if sys.version_info >= (3, 14):
1055
kwargs["context"] = Context()
1056
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
1062
self.queue: Queue[
1063
tuple[Context, Callable, tuple, asyncio.Future, CancelScope] | None
1064
] = Queue(2)
1065
self.idle_since = AsyncIOBackend.current_time()
1066
self.stopping = False
1067
1068
def _report_result(
1069
self, future: asyncio.Future, result: Any, exc: BaseException | None
1070
) -> None:
1071
self.idle_since = AsyncIOBackend.current_time()
1072
if not self.stopping:
1073
self.idle_workers.append(self)
1074
1075
if not future.cancelled():
1076
if exc is not None:
1077
if isinstance(exc, StopIteration):
1078
new_exc = RuntimeError("coroutine raised StopIteration")
1079
new_exc.__cause__ = exc
1080
exc = new_exc
1081
1082
future.set_exception(exc)
1083
else:
1084
future.set_result(result)
1085
1086
def run(self) -> None:
1087
with claim_worker_thread(AsyncIOBackend, self.loop):
1088
while True:
1089
10
29
item = self.queue.get()
1090
if item is None:
1091
# Shutdown command received
1092
return
1093
1094
context, func, args, future, cancel_scope = item
1095
if not future.cancelled():
1096
result = None
1097
exception: BaseException | None = None
1098
threadlocals.current_cancel_scope = cancel_scope
1099
try:
1100
12
14503
result = context.run(func, *args)
1101
except BaseException as exc:
1102
exception = exc
1103
finally:
1104
1
1
del threadlocals.current_cancel_scope
1105
1106
try:
1107
7
9
self.loop.call_soon_threadsafe(
1108
self._report_result, future, result, exception
1109
)
1110
except RuntimeError:
1111
if not self.loop.is_closed():
1112
raise
1113
1114
del result, exception
1115
1116
4
4
self.queue.task_done()
1117
del item, context, func, args, future, cancel_scope
1118
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)
1123
try:
1124
self.idle_workers.remove(self)
1125
except ValueError:
1126
pass
1127
1128
1129
_threadpool_idle_workers: RunVar[deque[WorkerThread]] = RunVar(
1130
"_threadpool_idle_workers"
1131
)
1132
_threadpool_workers: RunVar[set[WorkerThread]] = RunVar("_threadpool_workers")
1133
1134
1135
#
1136
# Subprocesses
1137
#
1138
1139
1140
@dataclass(eq=False)
1141
class StreamReaderWrapper(abc.ByteReceiveStream):
1142
_stream: asyncio.StreamReader
1143
1144
async def receive(self, max_bytes: int = 65536) -> bytes:
1145
if max_bytes < 1:
1146
raise ValueError("max_bytes must be a positive integer")
1147
1148
data = await self._stream.read(max_bytes)
1149
if data:
1150
return data
1151
else:
1152
raise EndOfStream
1153
1154
async def aclose(self) -> None:
1155
self._stream.set_exception(ClosedResourceError())
1156
await AsyncIOBackend.checkpoint()
1157
1158
1159
@dataclass(eq=False)
1160
class StreamWriterWrapper(abc.ByteSendStream):
1161
_stream: asyncio.StreamWriter
1162
_closed: bool = field(init=False, default=False)
1163
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]
1167
try:
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
1174
if self._closed:
1175
raise ClosedResourceError from exc
1176
elif self._stream.is_closing():
1177
raise BrokenResourceError from exc
1178
1179
raise
1180
1181
if not stream_paused:
1182
await AsyncIOBackend.cancel_shielded_checkpoint()
1183
1184
async def aclose(self) -> None:
1185
self._closed = True
1186
self._stream.close()
1187
await AsyncIOBackend.checkpoint()
1188
1189
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
1198
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.
1203
if self._stdin:
1204
await self._stdin.aclose()
1205
if pipe := self._transport.get_pipe_transport(0):
1206
pipe.close()
1207
if self._stdout:
1208
await self._stdout.aclose()
1209
if pipe := self._transport.get_pipe_transport(1):
1210
pipe.close()
1211
if self._stderr:
1212
await self._stderr.aclose()
1213
if pipe := self._transport.get_pipe_transport(2):
1214
pipe.close()
1215
1216
scope.shield = False
1217
try:
1218
await self.wait()
1219
except BaseException:
1220
scope.shield = True
1221
# Closing the transport on asyncio also handles sending kill
1222
self._transport.close()
1223
await self.wait()
1224
raise
1225
1226
async def wait(self) -> int:
1227
await self._exited.wait()
1228
assert self._process.returncode is not None
1229
return self._process.returncode
1230
1231
def terminate(self) -> None:
1232
self._process.terminate()
1233
1234
def kill(self) -> None:
1235
self._process.kill()
1236
1237
def send_signal(self, signal: int) -> None:
1238
self._process.send_signal(signal)
1239
1240
@property
1241
def pid(self) -> int:
1242
return self._process.pid
1243
1244
@property
1245
def returncode(self) -> int | None:
1246
return self._process.returncode
1247
1248
@property
1249
def stdin(self) -> abc.ByteSendStream | None:
1250
return self._stdin
1251
1252
@property
1253
def stdout(self) -> abc.ByteReceiveStream | None:
1254
return self._stdout
1255
1256
@property
1257
def stderr(self) -> abc.ByteReceiveStream | None:
1258
return self._stderr
1259
1260
1261
def _forcibly_shutdown_process_pool_on_exit(
1262
workers: set[Process], _task: object
1263
) -> None:
1264
"""
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):
1268
try:
1269
child_watcher = asyncio.get_event_loop_policy().get_child_watcher()
1270
except NotImplementedError:
1271
pass
1272
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:
1276
continue
1277
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]
1281
process.kill()
1282
if child_watcher:
1283
child_watcher.remove_child_handler(process.pid)
1284
1285
1286
async def _shutdown_process_pool_on_exit(workers: set[abc.Process]) -> None:
1287
"""
1288
Shuts down worker processes belonging to this event loop.
1289
1290
NOTE: this only works when the event loop was started using asyncio.run() or
1291
anyio.run().
1292
1293
"""
1294
process: abc.Process
1295
try:
1296
await sleep(math.inf)
1297
except asyncio.CancelledError:
1298
workers = workers.copy()
1299
for process in workers:
1300
if process.returncode is None:
1301
process.kill()
1302
1303
for process in workers:
1304
await process.aclose()
1305
1306
1307
#
1308
# Sockets and networking
1309
#
1310
1311
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
1318
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)
1325
1326
def connection_lost(self, exc: Exception | None) -> None:
1327
if exc:
1328
self.exception = exc
1329
1330
self.read_event.set()
1331
self.write_event.set()
1332
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()
1337
1338
def eof_received(self) -> bool | None:
1339
self.is_at_eof = True
1340
self.read_event.set()
1341
return True
1342
1343
def pause_writing(self) -> None:
1344
self.write_event = asyncio.Event()
1345
1346
def resume_writing(self) -> None:
1347
self.write_event.set()
1348
1349
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
1356
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()
1363
1364
def connection_lost(self, exc: Exception | None) -> None:
1365
self.read_event.set()
1366
self.write_event.set()
1367
self.closed_event.set()
1368
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()
1373
1374
def error_received(self, exc: Exception) -> None:
1375
self.exception = exc
1376
1377
def pause_writing(self) -> None:
1378
self.write_event.clear()
1379
1380
def resume_writing(self) -> None:
1381
self.write_event.set()
1382
1383
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
1391
1392
@property
1393
def _raw_socket(self) -> socket.socket:
1394
return self._transport.get_extra_info("socket")
1395
1396
async def receive(self, max_bytes: int = 65536) -> bytes:
1397
if max_bytes < 1:
1398
raise ValueError("max_bytes must be a positive integer")
1399
1400
with self._receive_guard:
1401
if (
1402
not self._protocol.read_event.is_set()
1403
and not self._transport.is_closing()
1404
and not self._protocol.is_at_eof
1405
):
1406
self._transport.resume_reading()
1407
await self._protocol.read_event.wait()
1408
self._transport.pause_reading()
1409
else:
1410
await AsyncIOBackend.checkpoint()
1411
1412
try:
1413
chunk = self._protocol.read_queue.popleft()
1414
except IndexError:
1415
if self._closed:
1416
raise ClosedResourceError from None
1417
elif self._protocol.exception:
1418
raise BrokenResourceError from self._protocol.exception
1419
else:
1420
raise EndOfStream from None
1421
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)
1426
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()
1431
1432
return chunk
1433
1434
async def send(self, item: bytes) -> None:
1435
with self._send_guard:
1436
await AsyncIOBackend.checkpoint()
1437
1438
if self._closed:
1439
raise ClosedResourceError
1440
elif self._protocol.exception is not None:
1441
raise BrokenResourceError from self._protocol.exception
1442
1443
try:
1444
self._transport.write(item)
1445
except RuntimeError as exc:
1446
if self._transport.is_closing():
1447
raise BrokenResourceError from exc
1448
else:
1449
raise
1450
1451
await self._protocol.write_event.wait()
1452
1453
async def send_eof(self) -> None:
1454
try:
1455
self._transport.write_eof()
1456
except OSError:
1457
pass
1458
1459
async def aclose(self) -> None:
1460
self._closed = True
1461
if not self._transport.is_closing():
1462
try:
1463
self._transport.write_eof()
1464
except OSError:
1465
pass
1466
1467
self._transport.close()
1468
await sleep(0)
1469
self._transport.abort()
1470
1471
1472
class _RawSocketMixin:
1473
_receive_future: asyncio.Future | None = None
1474
_send_future: asyncio.Future | None = None
1475
_closing = False
1476
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")
1481
1482
@property
1483
def _raw_socket(self) -> socket.socket:
1484
return self.__raw_socket
1485
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)
1490
1491
f = self._receive_future = asyncio.Future()
1492
loop.add_reader(self.__raw_socket, f.set_result, None)
1493
f.add_done_callback(callback)
1494
return f
1495
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)
1500
1501
f = self._send_future = asyncio.Future()
1502
loop.add_writer(self.__raw_socket, f.set_result, None)
1503
f.add_done_callback(callback)
1504
return f
1505
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()
1511
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)
1516
1517
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)
1522
1523
async def receive(self, max_bytes: int = 65536) -> bytes:
1524
if max_bytes < 1:
1525
raise ValueError("max_bytes must be a positive integer")
1526
1527
loop = get_running_loop()
1528
await AsyncIOBackend.checkpoint()
1529
with self._receive_guard:
1530
while True:
1531
try:
1532
data = self._raw_socket.recv(max_bytes)
1533
except BlockingIOError:
1534
await self._wait_until_readable(loop)
1535
except OSError as exc:
1536
if self._closing:
1537
raise ClosedResourceError from None
1538
else:
1539
raise BrokenResourceError from exc
1540
else:
1541
if not data:
1542
raise EndOfStream
1543
1544
return data
1545
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)
1551
while view:
1552
try:
1553
bytes_sent = self._raw_socket.send(view)
1554
except BlockingIOError:
1555
await self._wait_until_writable(loop)
1556
except OSError as exc:
1557
if self._closing:
1558
raise ClosedResourceError from None
1559
else:
1560
raise BrokenResourceError from exc
1561
else:
1562
view = view[bytes_sent:]
1563
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")
1569
1570
loop = get_running_loop()
1571
fds = array.array("i")
1572
await AsyncIOBackend.checkpoint()
1573
with self._receive_guard:
1574
while True:
1575
try:
1576
message, ancdata, _flags, _addr = self._raw_socket.recvmsg(
1577
msglen, socket.CMSG_LEN(maxfds * fds.itemsize)
1578
)
1579
except BlockingIOError:
1580
await self._wait_until_readable(loop)
1581
except OSError as exc:
1582
if self._closing:
1583
raise ClosedResourceError from None
1584
else:
1585
raise BrokenResourceError from exc
1586
else:
1587
if not message and not ancdata:
1588
raise EndOfStream
1589
1590
break
1591
1592
for cmsg_level, cmsg_type, cmsg_data in ancdata:
1593
if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS:
1594
raise RuntimeError(
1595
f"Received unexpected ancillary data; message = {message!r}, "
1596
f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}"
1597
)
1598
1599
fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
1600
1601
return message, list(fds)
1602
1603
async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None:
1604
if not message:
1605
raise ValueError("message must not be empty")
1606
if not fds:
1607
raise ValueError("fds must not be empty")
1608
1609
loop = get_running_loop()
1610
filenos: list[int] = []
1611
for fd in fds:
1612
if isinstance(fd, int):
1613
filenos.append(fd)
1614
elif isinstance(fd, IOBase):
1615
filenos.append(fd.fileno())
1616
1617
fdarray = array.array("i", filenos)
1618
await AsyncIOBackend.checkpoint()
1619
with self._send_guard:
1620
while True:
1621
try:
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)]
1626
)
1627
break
1628
except BlockingIOError:
1629
await self._wait_until_writable(loop)
1630
except OSError as exc:
1631
if self._closing:
1632
raise ClosedResourceError from None
1633
else:
1634
raise BrokenResourceError from exc
1635
1636
1637
class TCPSocketListener(abc.SocketListener):
1638
_accept_scope: CancelScope | None = None
1639
_closed = False
1640
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")
1645
1646
@property
1647
def _raw_socket(self) -> socket.socket:
1648
return self.__raw_socket
1649
1650
async def accept(self) -> abc.SocketStream:
1651
if self._closed:
1652
raise ClosedResourceError
1653
1654
with self._accept_guard:
1655
await AsyncIOBackend.checkpoint()
1656
with CancelScope() as self._accept_scope:
1657
try:
1658
client_sock, _addr = await self._loop.sock_accept(self._raw_socket)
1659
except asyncio.CancelledError:
1660
# Workaround for https://bugs.python.org/issue41317
1661
try:
1662
self._loop.remove_reader(self._raw_socket)
1663
except (ValueError, NotImplementedError):
1664
pass
1665
1666
if self._closed:
1667
raise ClosedResourceError from None
1668
1669
raise
1670
finally:
1671
self._accept_scope = None
1672
1673
client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1674
transport, protocol = await self._loop.connect_accepted_socket(
1675
StreamProtocol, client_sock
1676
)
1677
return SocketStream(transport, protocol)
1678
1679
async def aclose(self) -> None:
1680
if self._closed:
1681
return
1682
1683
self._closed = True
1684
if self._accept_scope:
1685
# Workaround for https://bugs.python.org/issue41317
1686
try:
1687
self._loop.remove_reader(self._raw_socket)
1688
except (ValueError, NotImplementedError):
1689
pass
1690
1691
self._accept_scope.cancel()
1692
await sleep(0)
1693
1694
self._raw_socket.close()
1695
1696
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
1703
1704
async def accept(self) -> abc.SocketStream:
1705
await AsyncIOBackend.checkpoint()
1706
with self._accept_guard:
1707
while True:
1708
try:
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)
1717
)
1718
await f
1719
except OSError as exc:
1720
if self._closed:
1721
raise ClosedResourceError from None
1722
else:
1723
raise BrokenResourceError from exc
1724
1725
async def aclose(self) -> None:
1726
self._closed = True
1727
self.__raw_socket.close()
1728
1729
@property
1730
def _raw_socket(self) -> socket.socket:
1731
return self.__raw_socket
1732
1733
1734
class UDPSocket(abc.UDPSocket):
1735
def __init__(
1736
self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol
1737
):
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
1743
1744
@property
1745
def _raw_socket(self) -> socket.socket:
1746
return self._transport.get_extra_info("socket")
1747
1748
async def aclose(self) -> None:
1749
self._closed = True
1750
if not self._transport.is_closing():
1751
self._transport.close()
1752
1753
await self._protocol.closed_event.wait()
1754
1755
async def receive(self) -> tuple[bytes, IPSockAddrType]:
1756
with self._receive_guard:
1757
await AsyncIOBackend.checkpoint()
1758
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()
1763
1764
try:
1765
return self._protocol.read_queue.popleft()
1766
except IndexError:
1767
if self._closed:
1768
raise ClosedResourceError from None
1769
else:
1770
raise BrokenResourceError from None
1771
1772
async def send(self, item: UDPPacketType) -> None:
1773
with self._send_guard:
1774
await AsyncIOBackend.checkpoint()
1775
await self._protocol.write_event.wait()
1776
if self._closed:
1777
raise ClosedResourceError
1778
elif self._transport.is_closing():
1779
raise BrokenResourceError
1780
else:
1781
self._transport.sendto(*item)
1782
1783
1784
class ConnectedUDPSocket(abc.ConnectedUDPSocket):
1785
def __init__(
1786
self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol
1787
):
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
1793
1794
@property
1795
def _raw_socket(self) -> socket.socket:
1796
return self._transport.get_extra_info("socket")
1797
1798
async def aclose(self) -> None:
1799
self._closed = True
1800
if not self._transport.is_closing():
1801
self._transport.close()
1802
1803
await self._protocol.closed_event.wait()
1804
1805
async def receive(self) -> bytes:
1806
with self._receive_guard:
1807
await AsyncIOBackend.checkpoint()
1808
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()
1813
1814
try:
1815
packet = self._protocol.read_queue.popleft()
1816
except IndexError:
1817
if self._closed:
1818
raise ClosedResourceError from None
1819
else:
1820
raise BrokenResourceError from None
1821
1822
return packet[0]
1823
1824
async def send(self, item: bytes) -> None:
1825
with self._send_guard:
1826
await AsyncIOBackend.checkpoint()
1827
await self._protocol.write_event.wait()
1828
if self._closed:
1829
raise ClosedResourceError
1830
elif self._transport.is_closing():
1831
raise BrokenResourceError
1832
else:
1833
self._transport.sendto(item)
1834
1835
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:
1841
while True:
1842
try:
1843
data = self._raw_socket.recvfrom(65536)
1844
except BlockingIOError:
1845
await self._wait_until_readable(loop)
1846
except OSError as exc:
1847
if self._closing:
1848
raise ClosedResourceError from None
1849
else:
1850
raise BrokenResourceError from exc
1851
else:
1852
return data
1853
1854
async def send(self, item: UNIXDatagramPacketType) -> None:
1855
loop = get_running_loop()
1856
await AsyncIOBackend.checkpoint()
1857
with self._send_guard:
1858
while True:
1859
try:
1860
self._raw_socket.sendto(*item)
1861
except BlockingIOError:
1862
await self._wait_until_writable(loop)
1863
except OSError as exc:
1864
if self._closing:
1865
raise ClosedResourceError from None
1866
else:
1867
raise BrokenResourceError from exc
1868
else:
1869
return
1870
1871
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:
1877
while True:
1878
try:
1879
data = self._raw_socket.recv(65536)
1880
except BlockingIOError:
1881
await self._wait_until_readable(loop)
1882
except OSError as exc:
1883
if self._closing:
1884
raise ClosedResourceError from None
1885
else:
1886
raise BrokenResourceError from exc
1887
else:
1888
return data
1889
1890
async def send(self, item: bytes) -> None:
1891
loop = get_running_loop()
1892
await AsyncIOBackend.checkpoint()
1893
with self._send_guard:
1894
while True:
1895
try:
1896
self._raw_socket.send(item)
1897
except BlockingIOError:
1898
await self._wait_until_writable(loop)
1899
except OSError as exc:
1900
if self._closing:
1901
raise ClosedResourceError from None
1902
else:
1903
raise BrokenResourceError from exc
1904
else:
1905
return
1906
1907
1908
_read_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("read_events")
1909
_write_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("write_events")
1910
1911
1912
#
1913
# Synchronization
1914
#
1915
1916
1917
class Event(BaseEvent):
1918
__slots__ = ("_event",)
1919
1920
def __new__(cls) -> Self:
1921
return object.__new__(cls)
1922
1923
def __init__(self) -> None:
1924
self._event = asyncio.Event()
1925
1926
def set(self) -> None:
1927
self._event.set()
1928
1929
def is_set(self) -> bool:
1930
return self._event.is_set()
1931
1932
async def wait(self) -> None:
1933
if self.is_set():
1934
await AsyncIOBackend.checkpoint()
1935
else:
1936
await self._event.wait()
1937
1938
def statistics(self) -> EventStatistics:
1939
return EventStatistics(len(self._event._waiters))
1940
1941
1942
class Lock(BaseLock):
1943
__slots__ = "_fast_acquire", "_owner_task", "_waiters"
1944
1945
def __new__(cls, *, fast_acquire: bool = False) -> Self:
1946
return object.__new__(cls)
1947
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()
1952
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
1958
1959
# Unless on the "fast path", yield control of the event loop so that other
1960
# tasks can run too
1961
if not self._fast_acquire:
1962
try:
1963
await AsyncIOBackend.cancel_shielded_checkpoint()
1964
except CancelledError:
1965
self.release()
1966
raise
1967
1968
return
1969
1970
if self._owner_task == task:
1971
raise RuntimeError("Attempted to acquire an already held Lock")
1972
1973
fut: asyncio.Future[None] = asyncio.Future()
1974
item = task, fut
1975
self._waiters.append(item)
1976
try:
1977
await fut
1978
except CancelledError:
1979
if fut.cancelled():
1980
try:
1981
self._waiters.remove(item)
1982
except ValueError:
1983
pass
1984
else:
1985
self.release()
1986
1987
raise
1988
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
1993
return
1994
1995
if self._owner_task is task:
1996
raise RuntimeError("Attempted to acquire an already held Lock")
1997
1998
raise WouldBlock
1999
2000
def locked(self) -> bool:
2001
return self._owner_task is not None
2002
2003
def release(self) -> None:
2004
if self._owner_task != current_task():
2005
raise RuntimeError("The current task is not holding this lock")
2006
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()
2012
if fut.cancelled():
2013
continue
2014
2015
self._owner_task = task
2016
fut.set_result(None)
2017
return
2018
2019
self._owner_task = None
2020
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))
2024
2025
2026
class Semaphore(BaseSemaphore):
2027
__slots__ = "_fast_acquire", "_max_value", "_value", "_waiters"
2028
2029
def __new__(
2030
cls,
2031
initial_value: int,
2032
*,
2033
max_value: int | None = None,
2034
fast_acquire: bool = False,
2035
) -> Self:
2036
return object.__new__(cls)
2037
2038
def __init__(
2039
self,
2040
initial_value: int,
2041
*,
2042
max_value: int | None = None,
2043
fast_acquire: bool = False,
2044
):
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()
2050
2051
async def acquire(self) -> None:
2052
if self._value > 0 and not self._waiters:
2053
await AsyncIOBackend.checkpoint_if_cancelled()
2054
self._value -= 1
2055
2056
# Unless on the "fast path", yield control of the event loop so that other
2057
# tasks can run too
2058
if not self._fast_acquire:
2059
try:
2060
await AsyncIOBackend.cancel_shielded_checkpoint()
2061
except CancelledError:
2062
self.release()
2063
raise
2064
2065
return
2066
2067
fut: asyncio.Future[None] = asyncio.Future()
2068
self._waiters.append(fut)
2069
try:
2070
await fut
2071
except CancelledError:
2072
if fut.cancelled():
2073
try:
2074
self._waiters.remove(fut)
2075
except ValueError:
2076
pass
2077
else:
2078
self.release()
2079
2080
raise
2081
2082
def acquire_nowait(self) -> None:
2083
if self._value == 0:
2084
raise WouldBlock
2085
2086
self._value -= 1
2087
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")
2091
2092
while self._waiters:
2093
fut = self._waiters.popleft()
2094
if fut.cancelled():
2095
continue
2096
2097
fut.set_result(None)
2098
return
2099
2100
self._value += 1
2101
2102
@property
2103
def value(self) -> int:
2104
return self._value
2105
2106
@property
2107
def max_value(self) -> int | None:
2108
return self._max_value
2109
2110
def statistics(self) -> SemaphoreStatistics:
2111
return SemaphoreStatistics(len(self._waiters))
2112
2113
2114
class CapacityLimiter(BaseCapacityLimiter):
2115
__slots__ = "_borrowers", "_total_tokens", "_wait_queue"
2116
2117
def __new__(cls, total_tokens: float) -> Self:
2118
return object.__new__(cls)
2119
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
2125
2126
async def __aenter__(self) -> None:
2127
await self.acquire()
2128
2129
async def __aexit__(
2130
self,
2131
exc_type: type[BaseException] | None,
2132
exc_val: BaseException | None,
2133
exc_tb: TracebackType | None,
2134
) -> None:
2135
self.release()
2136
2137
@property
2138
def total_tokens(self) -> float:
2139
return self._total_tokens
2140
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")
2145
2146
if value < 0:
2147
raise ValueError("total_tokens must be >= 0")
2148
2149
self._total_tokens = value
2150
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)
2156
event.set()
2157
2158
@property
2159
def borrowed_tokens(self) -> int:
2160
return len(self._borrowers)
2161
2162
@property
2163
def available_tokens(self) -> float:
2164
return self._total_tokens - len(self._borrowers)
2165
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)
2171
event.set()
2172
2173
def acquire_nowait(self) -> None:
2174
self.acquire_on_behalf_of_nowait(current_task())
2175
2176
def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
2177
if borrower in self._borrowers:
2178
raise RuntimeError(
2179
"this borrower is already holding one of this CapacityLimiter's tokens"
2180
)
2181
2182
if self._wait_queue or len(self._borrowers) >= self._total_tokens:
2183
raise WouldBlock
2184
2185
self._borrowers.add(borrower)
2186
2187
async def acquire(self) -> None:
2188
return await self.acquire_on_behalf_of(current_task())
2189
2190
async def acquire_on_behalf_of(self, borrower: object) -> None:
2191
await AsyncIOBackend.checkpoint_if_cancelled()
2192
try:
2193
self.acquire_on_behalf_of_nowait(borrower)
2194
except WouldBlock:
2195
event = asyncio.Event()
2196
self._wait_queue[borrower] = event
2197
try:
2198
await event.wait()
2199
except BaseException:
2200
self._wait_queue.pop(borrower, None)
2201
if event.is_set():
2202
self._borrowers.discard(borrower)
2203
self._notify_next_waiter()
2204
2205
raise
2206
else:
2207
try:
2208
await AsyncIOBackend.cancel_shielded_checkpoint()
2209
except BaseException:
2210
self.release()
2211
raise
2212
2213
def release(self) -> None:
2214
self.release_on_behalf_of(current_task())
2215
2216
def release_on_behalf_of(self, borrower: object) -> None:
2217
try:
2218
self._borrowers.remove(borrower)
2219
except KeyError:
2220
raise RuntimeError(
2221
"this borrower isn't holding any of this CapacityLimiter's tokens"
2222
) from None
2223
2224
self._notify_next_waiter()
2225
2226
def statistics(self) -> CapacityLimiterStatistics:
2227
return CapacityLimiterStatistics(
2228
self.borrowed_tokens,
2229
self.total_tokens,
2230
tuple(self._borrowers),
2231
len(self._wait_queue),
2232
)
2233
2234
2235
_default_thread_limiter: RunVar[CapacityLimiter] = RunVar("_default_thread_limiter")
2236
2237
2238
#
2239
# Operating system signals
2240
#
2241
2242
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()
2250
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)
2255
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)
2260
2261
return self
2262
2263
def __exit__(
2264
self,
2265
exc_type: type[BaseException] | None,
2266
exc_val: BaseException | None,
2267
exc_tb: TracebackType | None,
2268
) -> None:
2269
for sig in self._handled_signals:
2270
self._loop.remove_signal_handler(sig)
2271
2272
def __aiter__(self) -> _SignalReceiver:
2273
return self
2274
2275
async def __anext__(self) -> Signals:
2276
await AsyncIOBackend.checkpoint()
2277
if not self._signal_queue:
2278
self._future = asyncio.Future()
2279
await self._future
2280
2281
return self._signal_queue.popleft()
2282
2283
2284
#
2285
# Testing and debugging
2286
#
2287
2288
2289
class AsyncIOTaskInfo(TaskInfo):
2290
def __init__(self, task: asyncio.Task):
2291
task_state = _task_states.get(task)
2292
if task_state is None:
2293
parent_id = None
2294
else:
2295
parent_id = task_state.parent_id
2296
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)
2301
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
2305
return False
2306
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]
2310
):
2311
return True
2312
2313
if task_state := _task_states.get(task):
2314
if cancel_scope := task_state.cancel_scope:
2315
return cancel_scope._effectively_cancelled
2316
2317
return False
2318
2319
2320
class TestRunner(abc.TestRunner):
2321
_send_stream: MemoryObjectSendStream[tuple[Awaitable[Any], asyncio.Future[Any]]]
2322
2323
def __init__(
2324
self,
2325
*,
2326
debug: bool | None = None,
2327
use_uvloop: bool = False,
2328
loop_factory: Callable[[], AbstractEventLoop] | None = None,
2329
) -> None:
2330
if use_uvloop and loop_factory is None:
2331
if sys.platform != "win32":
2332
import uvloop
2333
2334
loop_factory = uvloop.new_event_loop
2335
else:
2336
import winloop
2337
2338
loop_factory = winloop.new_event_loop
2339
2340
self._runner = Runner(debug=debug, loop_factory=loop_factory)
2341
self._exceptions: list[BaseException] = []
2342
self._runner_task: asyncio.Task | None = None
2343
2344
def __enter__(self) -> Self:
2345
self._runner.__enter__()
2346
self.get_loop().set_exception_handler(self._exception_handler)
2347
return self
2348
2349
def __exit__(
2350
self,
2351
exc_type: type[BaseException] | None,
2352
exc_val: BaseException | None,
2353
exc_tb: TracebackType | None,
2354
) -> None:
2355
self._runner.__exit__(exc_type, exc_val, exc_tb)
2356
2357
def get_loop(self) -> AbstractEventLoop:
2358
return self._runner.get_loop()
2359
2360
def is_running(self) -> bool:
2361
try:
2362
asyncio.get_running_loop()
2363
return True
2364
except RuntimeError:
2365
return False
2366
2367
def _exception_handler(
2368
self, loop: asyncio.AbstractEventLoop, context: dict[str, Any]
2369
) -> None:
2370
if isinstance(context.get("exception"), Exception):
2371
self._exceptions.append(context["exception"])
2372
else:
2373
loop.default_exception_handler(context)
2374
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:
2380
raise exceptions[0]
2381
elif exceptions:
2382
raise BaseExceptionGroup(
2383
"Multiple exceptions occurred in asynchronous callbacks", exceptions
2384
)
2385
2386
async def _run_tests_and_fixtures(
2387
self,
2388
receive_stream: MemoryObjectReceiveStream[
2389
tuple[Awaitable[T_Retval], asyncio.Future[T_Retval]]
2390
],
2391
) -> None:
2392
from _pytest.outcomes import OutcomeException
2393
2394
with receive_stream, self._send_stream:
2395
async for coro, future in receive_stream:
2396
try:
2397
retval = await coro
2398
except CancelledError as exc:
2399
if not future.cancelled():
2400
future.cancel(*exc.args)
2401
2402
raise
2403
except BaseException as exc:
2404
if not future.cancelled():
2405
future.set_exception(exc)
2406
2407
if not isinstance(exc, (Exception, OutcomeException)):
2408
raise
2409
else:
2410
if not future.cancelled():
2411
future.set_result(retval)
2412
2413
async def _call_in_runner_task(
2414
self,
2415
func: Callable[P, Awaitable[T_Retval]],
2416
/,
2417
*args: P.args,
2418
**kwargs: P.kwargs,
2419
) -> T_Retval:
2420
if not self._runner_task:
2421
self._send_stream, receive_stream = create_memory_object_stream[
2422
tuple[Awaitable[Any], asyncio.Future]
2423
](1)
2424
self._runner_task = self.get_loop().create_task(
2425
self._run_tests_and_fixtures(receive_stream)
2426
)
2427
2428
coro = func(*args, **kwargs)
2429
future: asyncio.Future[T_Retval] = self.get_loop().create_future()
2430
self._send_stream.send_nowait((coro, future))
2431
return await future
2432
2433
def run_asyncgen_fixture(
2434
self,
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)
2441
)
2442
self._raise_async_exceptions()
2443
2444
yield fixturevalue
2445
2446
try:
2447
self.get_loop().run_until_complete(
2448
self._call_in_runner_task(asyncgen.asend, None)
2449
)
2450
except StopAsyncIteration:
2451
self._raise_async_exceptions()
2452
else:
2453
self.get_loop().run_until_complete(asyncgen.aclose())
2454
raise RuntimeError("Async generator fixture did not stop")
2455
2456
def run_fixture(
2457
self,
2458
fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]],
2459
kwargs: dict[str, Any],
2460
) -> T_Retval:
2461
retval = self.get_loop().run_until_complete(
2462
self._call_in_runner_task(fixture_func, **kwargs)
2463
)
2464
self._raise_async_exceptions()
2465
return retval
2466
2467
def run_test(
2468
self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any]
2469
) -> None:
2470
from _pytest.outcomes import OutcomeException
2471
2472
try:
2473
self.get_loop().run_until_complete(
2474
self._call_in_runner_task(test_func, **kwargs)
2475
)
2476
except Exception as exc:
2477
self._exceptions.append(exc)
2478
except OutcomeException:
2479
raise
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()
2487
try:
2488
self.get_loop().run_until_complete(self._runner_task)
2489
except CancelledError:
2490
pass
2491
finally:
2492
self._runner_task = None
2493
raise
2494
self._raise_async_exceptions()
2495
2496
2497
class _ProcessStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol):
2498
"""
2499
A subprocess protocol that allows us to be notified of ``process_exited``
2500
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.
2503
"""
2504
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()
2509
2510
def process_exited(self) -> None:
2511
super().process_exited()
2512
self.exited.set()
2513
2514
2515
class AsyncIOBackend(AsyncBackend):
2516
@classmethod
2517
def run(
2518
cls,
2519
func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
2520
args: tuple[Unpack[PosArgsT]],
2521
kwargs: dict[str, Any],
2522
options: dict[str, Any],
2523
) -> T_Retval:
2524
@wraps(func)
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)
2529
2530
try:
2531
return await func(*args)
2532
finally:
2533
del _task_states[task]
2534
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":
2539
import uvloop
2540
2541
loop_factory = uvloop.new_event_loop
2542
else:
2543
import winloop
2544
2545
loop_factory = winloop.new_event_loop
2546
2547
with Runner(debug=debug, loop_factory=loop_factory) as runner:
2548
return runner.run(wrapper())
2549
2550
@classmethod
2551
def current_token(cls) -> object:
2552
return get_running_loop()
2553
2554
@classmethod
2555
def current_time(cls) -> float:
2556
return get_running_loop().time()
2557
2558
@classmethod
2559
def cancelled_exception_class(cls) -> type[BaseException]:
2560
return CancelledError
2561
2562
@classmethod
2563
async def checkpoint(cls) -> None:
2564
await sleep(0)
2565
2566
@classmethod
2567
async def checkpoint_if_cancelled(cls) -> None:
2568
task = current_task()
2569
if task is None:
2570
return
2571
2572
try:
2573
cancel_scope = _task_states[task].cancel_scope
2574
except KeyError:
2575
return
2576
2577
while cancel_scope:
2578
if cancel_scope.cancel_called:
2579
await sleep(0)
2580
elif cancel_scope.shield:
2581
break
2582
else:
2583
cancel_scope = cancel_scope._parent_scope
2584
2585
@classmethod
2586
async def cancel_shielded_checkpoint(cls) -> None:
2587
with CancelScope(shield=True):
2588
await sleep(0)
2589
2590
@classmethod
2591
async def sleep(cls, delay: float) -> None:
2592
await sleep(delay)
2593
2594
@classmethod
2595
def create_cancel_scope(
2596
cls, *, deadline: float = math.inf, shield: bool = False
2597
) -> CancelScope:
2598
return CancelScope(deadline=deadline, shield=shield)
2599
2600
@classmethod
2601
def current_effective_deadline(cls) -> float:
2602
if (task := current_task()) is None:
2603
return math.inf
2604
2605
try:
2606
cancel_scope = _task_states[task].cancel_scope
2607
except KeyError:
2608
return math.inf
2609
2610
deadline = math.inf
2611
while cancel_scope:
2612
deadline = min(deadline, cancel_scope.deadline)
2613
if cancel_scope._cancel_called:
2614
deadline = -math.inf
2615
break
2616
elif cancel_scope.shield:
2617
break
2618
else:
2619
cancel_scope = cancel_scope._parent_scope
2620
2621
return deadline
2622
2623
@classmethod
2624
def create_task_group(cls) -> abc.TaskGroup:
2625
return TaskGroup()
2626
2627
@classmethod
2628
def create_event(cls) -> BaseEvent:
2629
return Event()
2630
2631
@classmethod
2632
def create_lock(cls, *, fast_acquire: bool) -> BaseLock:
2633
return Lock(fast_acquire=fast_acquire)
2634
2635
@classmethod
2636
def create_semaphore(
2637
cls,
2638
initial_value: int,
2639
*,
2640
max_value: int | None = None,
2641
fast_acquire: bool = False,
2642
) -> BaseSemaphore:
2643
return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire)
2644
2645
@classmethod
2646
def create_capacity_limiter(cls, total_tokens: float) -> BaseCapacityLimiter:
2647
return CapacityLimiter(total_tokens)
2648
2649
@classmethod
2650
async def run_sync_in_worker_thread( # type: ignore[return]
2651
cls,
2652
func: Callable[[Unpack[PosArgsT]], T_Retval],
2653
args: tuple[Unpack[PosArgsT]],
2654
abandon_on_cancel: bool = False,
2655
limiter: BaseCapacityLimiter | None = None,
2656
) -> T_Retval:
2657
await cls.checkpoint()
2658
2659
# If this is the first run in this event loop thread, set up the necessary
2660
# variables
2661
try:
2662
idle_workers = _threadpool_idle_workers.get()
2663
workers = _threadpool_workers.get()
2664
except LookupError:
2665
idle_workers = deque()
2666
workers = set()
2667
_threadpool_idle_workers.set(idle_workers)
2668
_threadpool_workers.set(workers)
2669
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)
2676
worker.start()
2677
workers.add(worker)
2678
root_task.add_done_callback(worker.stop, context=Context())
2679
else:
2680
worker = idle_workers.pop()
2681
2682
# Prune any other workers that have been idle for MAX_IDLE_TIME
2683
# seconds or longer
2684
now = cls.current_time()
2685
while idle_workers:
2686
if (
2687
now - idle_workers[0].idle_since
2688
< WorkerThread.MAX_IDLE_TIME
2689
):
2690
break
2691
2692
expired_worker = idle_workers.popleft()
2693
expired_worker.root_task.remove_done_callback(
2694
expired_worker.stop
2695
)
2696
expired_worker.stop()
2697
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
2702
else:
2703
worker_scope = scope._parent_scope
2704
2705
worker.queue.put_nowait((context, func, args, future, worker_scope))
2706
return await future
2707
2708
@classmethod
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}")
2714
2715
if scope.shield:
2716
return
2717
2718
scope = scope._parent_scope
2719
2720
@classmethod
2721
def run_async_from_thread(
2722
cls,
2723
func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
2724
args: tuple[Unpack[PosArgsT]],
2725
token: object,
2726
) -> T_co:
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)
2733
try:
2734
return await func(*args)
2735
except CancelledError as exc:
2736
raise concurrent.futures.CancelledError(str(exc)) from None
2737
finally:
2738
if scope is not None:
2739
scope._tasks.discard(task)
2740
2741
loop = cast(
2742
"AbstractEventLoop", token or threadlocals.current_token.native_token
2743
)
2744
if loop.is_closed():
2745
raise RunFinishedError
2746
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
2752
)
2753
return f.result()
2754
2755
@classmethod
2756
def run_sync_from_thread(
2757
cls,
2758
func: Callable[[Unpack[PosArgsT]], T_Retval],
2759
args: tuple[Unpack[PosArgsT]],
2760
token: object,
2761
) -> T_Retval:
2762
@wraps(func)
2763
def wrapper() -> None:
2764
try:
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):
2770
raise
2771
2772
loop = cast(
2773
"AbstractEventLoop", token or threadlocals.current_token.native_token
2774
)
2775
if loop.is_closed():
2776
raise RunFinishedError
2777
2778
f: concurrent.futures.Future[T_Retval] = Future()
2779
loop.call_soon_threadsafe(wrapper)
2780
return f.result()
2781
2782
@classmethod
2783
async def open_process(
2784
cls,
2785
command: StrOrBytesPath | Sequence[StrOrBytesPath],
2786
*,
2787
stdin: int | IO[Any] | None,
2788
stdout: int | IO[Any] | None,
2789
stderr: int | IO[Any] | None,
2790
**kwargs: Any,
2791
) -> Process:
2792
await cls.checkpoint()
2793
if isinstance(command, PathLike):
2794
command = os.fspath(command)
2795
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,
2803
command,
2804
stdin=stdin,
2805
stdout=stdout,
2806
stderr=stderr,
2807
**kwargs,
2808
)
2809
else:
2810
transport, protocol = await loop.subprocess_exec(
2811
_ProcessStreamProtocol,
2812
*command,
2813
stdin=stdin,
2814
stdout=stdout,
2815
stderr=stderr,
2816
**kwargs,
2817
)
2818
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
2823
return Process(
2824
process,
2825
stdin_stream,
2826
stdout_stream,
2827
stderr_stream,
2828
protocol.exited,
2829
transport,
2830
)
2831
2832
@classmethod
2833
def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None:
2834
create_task(
2835
_shutdown_process_pool_on_exit(workers),
2836
name="AnyIO process pool shutdown task",
2837
)
2838
find_root_task().add_done_callback(
2839
partial(_forcibly_shutdown_process_pool_on_exit, workers) # type:ignore[arg-type]
2840
)
2841
2842
@classmethod
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
2850
),
2851
)
2852
transport.pause_reading()
2853
return SocketStream(transport, protocol)
2854
2855
@classmethod
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)
2861
while True:
2862
try:
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))
2868
await f
2869
except BaseException:
2870
raw_socket.close()
2871
raise
2872
else:
2873
return UNIXSocketStream(raw_socket)
2874
2875
@classmethod
2876
def create_tcp_listener(cls, sock: socket.socket) -> SocketListener:
2877
return TCPSocketListener(sock)
2878
2879
@classmethod
2880
def create_unix_listener(cls, sock: socket.socket) -> SocketListener:
2881
return UNIXSocketListener(sock)
2882
2883
@classmethod
2884
async def create_udp_socket(
2885
cls,
2886
family: AddressFamily,
2887
local_address: IPSockAddrType | None,
2888
remote_address: IPSockAddrType | None,
2889
reuse_port: bool,
2890
) -> UDPSocket | ConnectedUDPSocket:
2891
transport, protocol = await get_running_loop().create_datagram_endpoint(
2892
DatagramProtocol,
2893
local_addr=local_address,
2894
remote_addr=remote_address,
2895
family=family,
2896
reuse_port=reuse_port,
2897
)
2898
if protocol.exception:
2899
transport.close()
2900
raise protocol.exception
2901
2902
if not remote_address:
2903
return UDPSocket(transport, protocol)
2904
else:
2905
return ConnectedUDPSocket(transport, protocol)
2906
2907
@classmethod
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()
2913
2914
if remote_path:
2915
while True:
2916
try:
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))
2922
await f
2923
except BaseException:
2924
raw_socket.close()
2925
raise
2926
else:
2927
return ConnectedUNIXDatagramSocket(raw_socket)
2928
else:
2929
return UNIXDatagramSocket(raw_socket)
2930
2931
@classmethod
2932
async def getaddrinfo(
2933
cls,
2934
host: bytes | str | None,
2935
port: str | int | None,
2936
*,
2937
family: int | AddressFamily = 0,
2938
type: int | SocketKind = 0,
2939
proto: int = 0,
2940
flags: int = 0,
2941
) -> Sequence[
2942
tuple[
2943
AddressFamily,
2944
SocketKind,
2945
int,
2946
str,
2947
tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes],
2948
]
2949
]:
2950
return await get_running_loop().getaddrinfo(
2951
host, port, family=family, type=type, proto=proto, flags=flags
2952
)
2953
2954
@classmethod
2955
async def getnameinfo(
2956
cls, sockaddr: IPSockAddrType, flags: int = 0
2957
) -> tuple[str, str]:
2958
return await get_running_loop().getnameinfo(sockaddr, flags)
2959
2960
@classmethod
2961
async def wait_readable(cls, obj: FileDescriptorLike) -> None:
2962
try:
2963
read_events = _read_events.get()
2964
except LookupError:
2965
read_events = {}
2966
_read_events.set(read_events)
2967
2968
fd = obj if isinstance(obj, int) else obj.fileno()
2969
if read_events.get(fd):
2970
raise BusyResourceError("reading from")
2971
2972
loop = get_running_loop()
2973
fut: asyncio.Future[bool] = loop.create_future()
2974
2975
def cb() -> None:
2976
try:
2977
del read_events[fd]
2978
except KeyError:
2979
pass
2980
else:
2981
remove_reader(fd)
2982
2983
try:
2984
fut.set_result(True)
2985
except asyncio.InvalidStateError:
2986
pass
2987
2988
try:
2989
loop.add_reader(fd, cb)
2990
except NotImplementedError:
2991
from anyio._core._asyncio_selector_thread import get_selector
2992
2993
selector = get_selector()
2994
selector.add_reader(fd, cb)
2995
remove_reader = selector.remove_reader
2996
else:
2997
remove_reader = loop.remove_reader
2998
2999
read_events[fd] = fut
3000
try:
3001
success = await fut
3002
finally:
3003
try:
3004
del read_events[fd]
3005
except KeyError:
3006
pass
3007
else:
3008
remove_reader(fd)
3009
3010
if not success:
3011
raise ClosedResourceError
3012
3013
@classmethod
3014
async def wait_writable(cls, obj: FileDescriptorLike) -> None:
3015
try:
3016
write_events = _write_events.get()
3017
except LookupError:
3018
write_events = {}
3019
_write_events.set(write_events)
3020
3021
fd = obj if isinstance(obj, int) else obj.fileno()
3022
if write_events.get(fd):
3023
raise BusyResourceError("writing to")
3024
3025
loop = get_running_loop()
3026
fut: asyncio.Future[bool] = loop.create_future()
3027
3028
def cb() -> None:
3029
try:
3030
del write_events[fd]
3031
except KeyError:
3032
pass
3033
else:
3034
remove_writer(fd)
3035
3036
try:
3037
fut.set_result(True)
3038
except asyncio.InvalidStateError:
3039
pass
3040
3041
try:
3042
loop.add_writer(fd, cb)
3043
except NotImplementedError:
3044
from anyio._core._asyncio_selector_thread import get_selector
3045
3046
selector = get_selector()
3047
selector.add_writer(fd, cb)
3048
remove_writer = selector.remove_writer
3049
else:
3050
remove_writer = loop.remove_writer
3051
3052
write_events[fd] = fut
3053
try:
3054
success = await fut
3055
finally:
3056
try:
3057
del write_events[fd]
3058
except KeyError:
3059
pass
3060
else:
3061
remove_writer(fd)
3062
3063
if not success:
3064
raise ClosedResourceError
3065
3066
@classmethod
3067
def notify_closing(cls, obj: FileDescriptorLike) -> None:
3068
fd = obj if isinstance(obj, int) else obj.fileno()
3069
loop = get_running_loop()
3070
3071
try:
3072
write_events = _write_events.get()
3073
except LookupError:
3074
pass
3075
else:
3076
try:
3077
fut = write_events.pop(fd)
3078
except KeyError:
3079
pass
3080
else:
3081
try:
3082
fut.set_result(False)
3083
except asyncio.InvalidStateError:
3084
pass
3085
3086
try:
3087
loop.remove_writer(fd)
3088
except NotImplementedError:
3089
from anyio._core._asyncio_selector_thread import get_selector
3090
3091
get_selector().remove_writer(fd)
3092
3093
try:
3094
read_events = _read_events.get()
3095
except LookupError:
3096
pass
3097
else:
3098
try:
3099
fut = read_events.pop(fd)
3100
except KeyError:
3101
pass
3102
else:
3103
try:
3104
fut.set_result(False)
3105
except asyncio.InvalidStateError:
3106
pass
3107
3108
try:
3109
loop.remove_reader(fd)
3110
except NotImplementedError:
3111
from anyio._core._asyncio_selector_thread import get_selector
3112
3113
get_selector().remove_reader(fd)
3114
3115
@classmethod
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)
3119
3120
return TCPSocketListener(sock)
3121
3122
@classmethod
3123
async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream:
3124
transport, protocol = await get_running_loop().create_connection(
3125
StreamProtocol, sock=sock
3126
)
3127
return SocketStream(transport, protocol)
3128
3129
@classmethod
3130
async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream:
3131
return UNIXSocketStream(sock)
3132
3133
@classmethod
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
3137
)
3138
return UDPSocket(transport, protocol)
3139
3140
@classmethod
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
3144
)
3145
return ConnectedUDPSocket(transport, protocol)
3146
3147
@classmethod
3148
async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket:
3149
return UNIXDatagramSocket(sock)
3150
3151
@classmethod
3152
async def wrap_connected_unix_datagram_socket(
3153
cls, sock: socket.socket
3154
) -> ConnectedUNIXDatagramSocket:
3155
return ConnectedUNIXDatagramSocket(sock)
3156
3157
@classmethod
3158
def current_default_thread_limiter(cls) -> CapacityLimiter:
3159
try:
3160
return _default_thread_limiter.get()
3161
except LookupError:
3162
limiter = CapacityLimiter(40)
3163
_default_thread_limiter.set(limiter)
3164
return limiter
3165
3166
@classmethod
3167
def open_signal_receiver(
3168
cls, *signals: Signals
3169
) -> AbstractContextManager[AsyncIterator[Signals]]:
3170
return _SignalReceiver(signals)
3171
3172
@classmethod
3173
def get_current_task(cls) -> TaskInfo:
3174
return AsyncIOTaskInfo(current_task()) # type: ignore[arg-type]
3175
3176
@classmethod
3177
def get_running_tasks(cls) -> Sequence[TaskInfo]:
3178
return [AsyncIOTaskInfo(task) for task in all_tasks() if not task.done()]
3179
3180
@classmethod
3181
async def wait_all_tasks_blocked(cls) -> None:
3182
await cls.checkpoint()
3183
this_task = current_task()
3184
while True:
3185
for task in all_tasks():
3186
if task is this_task:
3187
continue
3188
3189
waiter = task._fut_waiter # type: ignore[attr-defined]
3190
if waiter is None or waiter.done():
3191
await sleep(0.1)
3192
break
3193
else:
3194
return
3195
3196
@classmethod
3197
def create_test_runner(cls, options: dict[str, Any]) -> TestRunner:
3198
return TestRunner(**options)
3199
3200
3201
backend_class = AsyncIOBackend