Coverage for /pythoncovmergedfiles/medio/medio/src/aiohttp/aiohttp/base_protocol.py: 44%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import asyncio
2from typing import TYPE_CHECKING, Any, cast
4from .client_exceptions import ClientConnectionResetError
5from .helpers import set_exception
6from .tcp_helpers import tcp_nodelay
8if TYPE_CHECKING:
9 from .http_parser import HttpParser
11# Raised by transport.pause_reading()/resume_reading() when the transport
12# does not support flow control; safe to ignore.
13# NOTE: Catch these with a plain try/except/pass, never contextlib.suppress():
14# pause/resume run on the hot read path and suppress() is ~6x slower than
15# try/except here (it builds a context manager and unpacks this tuple per call).
16PAUSE_RESUME_READING_ERRORS = (AttributeError, NotImplementedError, RuntimeError)
19class BaseProtocol(asyncio.Protocol):
20 __slots__ = (
21 "_loop",
22 "_paused",
23 "_parser",
24 "_drain_waiter",
25 "_connection_lost",
26 "_reading_paused",
27 "_buffer_paused",
28 "_upgraded",
29 "transport",
30 )
32 def __init__(
33 self, loop: asyncio.AbstractEventLoop, parser: "HttpParser[Any] | None" = None
34 ) -> None:
35 self._loop: asyncio.AbstractEventLoop = loop
36 self._paused = False
37 self._drain_waiter: asyncio.Future[None] | None = None
38 self._reading_paused = False
39 self._buffer_paused = False
40 self._parser = parser
41 self._upgraded = False
43 self.transport: asyncio.Transport | None = None
45 @property
46 def connected(self) -> bool:
47 """Return True if the connection is open."""
48 return self.transport is not None
50 @property
51 def writing_paused(self) -> bool:
52 return self._paused
54 def pause_writing(self) -> None:
55 assert not self._paused
56 self._paused = True
58 def resume_writing(self) -> None:
59 assert self._paused
60 self._paused = False
62 waiter = self._drain_waiter
63 if waiter is not None:
64 self._drain_waiter = None
65 if not waiter.done():
66 waiter.set_result(None)
68 def pause_reading(self) -> None:
69 self._reading_paused = True
70 # Parser shouldn't be paused on websockets.
71 if not self._upgraded:
72 assert self._parser is not None
73 self._parser.pause_reading()
74 self._pause_transport_reading()
76 def _pause_reading_for_buffer(self) -> None:
77 """Hold the transport for a buffer this protocol cannot drain yet.
79 One flag covers every buffer a protocol owns; the server already
80 multiplexes its message queue and its tail through it. Re-check the
81 conditions on resume rather than adding another flag.
82 """
83 self._buffer_paused = True
84 self._pause_transport_reading()
86 def _resume_reading_for_buffer(self) -> None:
87 """Release that hold, unless flow control is still holding it too."""
88 self._buffer_paused = False
89 if not self._reading_paused:
90 self._resume_transport_reading()
92 def _pause_transport_reading(self) -> None:
93 if self.transport is not None:
94 try:
95 self.transport.pause_reading()
96 except PAUSE_RESUME_READING_ERRORS:
97 # Transport lacks flow control; nothing to pause. Intentionally
98 # ignored (see PAUSE_RESUME_READING_ERRORS; do not use suppress).
99 pass
101 def _resume_transport_reading(self) -> None:
102 if self.transport is not None:
103 try:
104 self.transport.resume_reading()
105 except PAUSE_RESUME_READING_ERRORS:
106 # Transport lacks flow control; nothing to resume. Intentionally
107 # ignored (see PAUSE_RESUME_READING_ERRORS; do not use suppress).
108 pass
110 def resume_reading(self, resume_parser: bool = True) -> None:
111 self._reading_paused = False
113 # This will resume parsing any unprocessed data from the last pause.
114 if not self._upgraded and resume_parser:
115 self.data_received(b"")
117 # Reading may have been paused again in the above call if there was a lot of
118 # compressed data still pending.
119 if not self._reading_paused and not self._buffer_paused:
120 self._resume_transport_reading()
122 def connection_made(self, transport: asyncio.BaseTransport) -> None:
123 tr = cast(asyncio.Transport, transport)
124 tcp_nodelay(tr, True)
125 self.transport = tr
127 def connection_lost(self, exc: BaseException | None) -> None:
128 # Wake up the writer if currently paused.
129 self.transport = None
130 if not self._paused:
131 return
132 waiter = self._drain_waiter
133 if waiter is None:
134 return
135 self._drain_waiter = None
136 if waiter.done():
137 return
138 if exc is None:
139 waiter.set_result(None)
140 else:
141 set_exception(
142 waiter,
143 ConnectionError("Connection lost"),
144 exc,
145 )
147 async def _drain_helper(self) -> None:
148 if self.transport is None:
149 raise ClientConnectionResetError("Connection lost")
150 if not self._paused:
151 return
152 waiter = self._drain_waiter
153 if waiter is None:
154 waiter = self._loop.create_future()
155 self._drain_waiter = waiter
156 await waiter