httpunk 0.4.1__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {httpunk-0.4.1 → httpunk-0.4.2}/Cargo.lock +1 -1
- {httpunk-0.4.1 → httpunk-0.4.2}/Cargo.toml +1 -1
- {httpunk-0.4.1 → httpunk-0.4.2}/PKG-INFO +1 -1
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/_httpunk.pyi +4 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h2/client.py +32 -7
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h2/connection.py +56 -41
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h2/conn.rs +355 -31
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h2/mod.rs +1 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h1_server.py +6 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h2_flow.py +147 -11
- httpunk-0.4.2/tests/test_h2_flow_control.py +384 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h2_server.py +10 -4
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_util_pool.py +76 -70
- httpunk-0.4.1/tests/test_h2_flow_control.py +0 -174
- {httpunk-0.4.1 → httpunk-0.4.2}/LICENSE +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/README.md +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/Cargo.toml +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/LICENSE +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/UPSTREAM_VERSION +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/codec/error.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/codec/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/error.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/ext.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/data.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/go_away.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/head.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/headers.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/ping.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/priority.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/reason.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/reset.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/settings.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/stream_id.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/util.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/frame/window_update.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/hpack/decoder.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/hpack/encoder.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/hpack/header.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/hpack/huffman/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/hpack/huffman/table.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/hpack/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/hpack/table.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/lib.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/proto/error.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/proto/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/proto/streams/flow_control.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/proto/streams/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-h2/src/proto/streams/state.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/Cargo.toml +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/LICENSE +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/UPSTREAM_VERSION +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/body/length.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/body/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/cfg.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/common/date.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/common/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/error.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/ext/h1_reason_phrase.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/ext/informational.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/ext/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/headers.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/lib.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/proto/h1/decode.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/proto/h1/encode.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/proto/h1/httpunk.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/proto/h1/io.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/proto/h1/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/proto/h1/role.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/proto/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper/src/trace.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper-util/Cargo.toml +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper-util/LICENSE +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper-util/UPSTREAM_VERSION +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper-util/src/client/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper-util/src/client/proxy/matcher.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper-util/src/client/proxy/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/crates/vendor-hyper-util/src/lib.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/__init__.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/_backend/__init__.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/_backend/asyncio.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/_backend/tonio.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/_common.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/asyncio.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/exceptions.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h1/__init__.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h1/client.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h1/connection.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h1/server.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h1/share.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h2/__init__.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h2/server.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h2/share.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/h2/stream.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/http.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/py.typed +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/types.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/util/__init__.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/util/auto.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/util/client.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/util/graceful.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/util/pool.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/httpunk/util/proxy.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/pyproject.toml +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/errors.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h1/codec.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h1/conn.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h1/errors.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h1/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h2/codec.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h2/errors.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h2/reason.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h2/settings.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/h2/streams.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/http/mod.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/lib.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/proxy.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/src/py.rs +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/_client.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/_transport.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/conftest.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_asyncio_backend.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_asyncio_client_protocol.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_asyncio_e2e.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_asyncio_protocol.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_backend.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_error_retention.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h1_codec.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h1_conn.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h1_decode.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h2_codec.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h2_conn.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h2_errors.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h2_ready.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h2_settings.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h2_state.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_h2_write_batch.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_headermap.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_util_auto.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_util_connect.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_util_graceful.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_util_proxy.py +0 -0
- {httpunk-0.4.1 → httpunk-0.4.2}/tests/test_util_tls.py +0 -0
|
@@ -867,6 +867,7 @@ H2_FLAG_WAKE: int # bytes were queued: wake the write pump
|
|
|
867
867
|
H2_FLAG_SLOT_FREED: int # client: a MAX_CONCURRENT slot freed / the limit changed
|
|
868
868
|
H2_FLAG_CONN_DONE: int # the connection failed or finished: wake the role waiters
|
|
869
869
|
H2_FLAG_STOP_ACCEPTING: int # server: the graceful drain completed
|
|
870
|
+
H2_FLAG_CAPACITY: int # the connection-capacity queue moved: ask `next_capacity_wake`
|
|
870
871
|
|
|
871
872
|
# `H2RecvHeadersVerdict.kind`.
|
|
872
873
|
H2_HEADERS_IGNORED: int
|
|
@@ -1060,6 +1061,9 @@ class H2Streams:
|
|
|
1060
1061
|
def stream_send_window(self, sid: int) -> int | None: ...
|
|
1061
1062
|
@property
|
|
1062
1063
|
def conn_send_window(self) -> int: ...
|
|
1064
|
+
def next_capacity_wake(self) -> object | None:
|
|
1065
|
+
"""After `H2_FLAG_CAPACITY`: the stream handle now first in line for connection
|
|
1066
|
+
window (wake its `window_evt`), or None."""
|
|
1063
1067
|
@property
|
|
1064
1068
|
def conn_recv_available(self) -> int: ...
|
|
1065
1069
|
@property
|
|
@@ -169,16 +169,41 @@ class Connection(H2ConnectionBase):
|
|
|
169
169
|
"""Send the request body concurrently with (not before) the caller awaiting
|
|
170
170
|
the response head: h2's `SendStream` (body) and `ResponseFuture` (head) are
|
|
171
171
|
independent — an early response that resets the request body (413 / redirect
|
|
172
|
-
during upload) must still deliver the received response.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
during upload) must still deliver the received response. hyper client.rs
|
|
173
|
+
`poll_pipe`: the pipe is polled once inline, and only if that leaves it pending
|
|
174
|
+
is it handed to the executor as a bare task that outlives `send_request` (full
|
|
175
|
+
duplex) and ends on its own — the body sent, or the stream stopped; `close()`
|
|
176
|
+
waits for the last one (`release_writers`), it never cancels them."""
|
|
177
|
+
offset = 0
|
|
178
|
+
if body is None or isinstance(body, bytes):
|
|
179
|
+
offset = self._send_body_inline(stream, body, trailers)
|
|
180
|
+
if offset is None:
|
|
181
|
+
return
|
|
182
|
+
if self.writer_start(): # refused: the stream is stopped already, nothing to write
|
|
183
|
+
self.backend.spawn_without_results(self._write_body(stream, body, trailers, offset))
|
|
184
|
+
|
|
185
|
+
def _send_body_inline(self, stream, body, trailers):
|
|
186
|
+
"""The inline first poll of a buffered body (hyper's `Full`: one chunk, EOS
|
|
187
|
+
set): queue what the windows take NOW. Returns the offset a background writer
|
|
188
|
+
resumes from, or None when nothing is left to write — the body (and trailers)
|
|
189
|
+
fully queued, or the stream already stopped, which the response reader sees."""
|
|
190
|
+
try:
|
|
191
|
+
offset = self._try_send_data(stream, body, 0, trailers is None) if body else None
|
|
192
|
+
if offset is None and trailers is not None:
|
|
193
|
+
self._send_trailers(stream, trailers)
|
|
194
|
+
except Exception:
|
|
195
|
+
return None
|
|
196
|
+
return offset
|
|
176
197
|
|
|
177
|
-
async def _write_body(self, stream, body, trailers
|
|
198
|
+
async def _write_body(self, stream, body, trailers, offset):
|
|
178
199
|
# The stream may error/reset mid-send: that is surfaced when the response body
|
|
179
200
|
# is read; a background writer has nowhere to propagate to.
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
try:
|
|
202
|
+
with contextlib.suppress(Exception):
|
|
203
|
+
await self._send_body(stream, body, trailers, offset)
|
|
204
|
+
finally:
|
|
205
|
+
if self.writer_end():
|
|
206
|
+
self._writers_evt.set()
|
|
182
207
|
|
|
183
208
|
|
|
184
209
|
class H2Connection(BaseClientConnection):
|
|
@@ -27,6 +27,7 @@ import contextlib
|
|
|
27
27
|
from .. import _backend
|
|
28
28
|
from .._common import PUMP_ABANDONED, PUMP_DONE, aclose_body, aiter_body, event_result
|
|
29
29
|
from .._httpunk import (
|
|
30
|
+
H2_FLAG_CAPACITY,
|
|
30
31
|
H2_FLAG_CONN_DONE,
|
|
31
32
|
H2_FLAG_SLOT_FREED,
|
|
32
33
|
H2_FLAG_STOP_ACCEPTING,
|
|
@@ -70,9 +71,12 @@ class H2ConnectionBase(H2Streams):
|
|
|
70
71
|
# pumps read it, `close()` closes it — never reassigned.
|
|
71
72
|
self.backend = _backend.resolve(backend)
|
|
72
73
|
self._transport = transport
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
|
|
74
|
+
# Set by the last background request-body writer to finish once `close()` is
|
|
75
|
+
# draining (client full-duplex send, F6): hyper's connection task ends only
|
|
76
|
+
# after every `PipeToSendStream` dropped its `conn_drop_ref`. Writers are bare
|
|
77
|
+
# tasks, never cancelled: a stopped stream (peer reset, GOAWAY, the connection
|
|
78
|
+
# failed or closed) is what ends one.
|
|
79
|
+
self._writers_evt = self.backend.event()
|
|
76
80
|
# Orders socket writes: the write pump and the inline flushes (`_flush`).
|
|
77
81
|
self._send_lock = self.backend.lock()
|
|
78
82
|
# Wakes the write pump. Set by the driver after any verdict that appended to
|
|
@@ -109,6 +113,12 @@ class H2ConnectionBase(H2Streams):
|
|
|
109
113
|
self._on_conn_done()
|
|
110
114
|
if flags & H2_FLAG_STOP_ACCEPTING:
|
|
111
115
|
self._on_stop_accepting()
|
|
116
|
+
if flags & H2_FLAG_CAPACITY:
|
|
117
|
+
# The front of the connection-capacity queue moved on: hand the turn
|
|
118
|
+
# to the next sender in line (h2 `assign_connection_capacity`).
|
|
119
|
+
handle = self.next_capacity_wake()
|
|
120
|
+
if handle is not None:
|
|
121
|
+
handle.window_evt.set()
|
|
112
122
|
|
|
113
123
|
@staticmethod
|
|
114
124
|
def _notify_stopped(handle, stop, notify_body):
|
|
@@ -187,7 +197,6 @@ class H2ConnectionBase(H2Streams):
|
|
|
187
197
|
# Shared handshake start (h2 client.rs/server.rs `handshake`): the preface
|
|
188
198
|
# (client) + our initial SETTINGS + the initial WINDOW_UPDATE(0) are queued by
|
|
189
199
|
# the state and flushed here, BEFORE the pumps start, so nothing can precede them.
|
|
190
|
-
await self._write_scope.__aenter__()
|
|
191
200
|
self.begin()
|
|
192
201
|
await self._flush()
|
|
193
202
|
# Bare tasks (`spawn_without_results`), never cancelled: every point they can
|
|
@@ -213,20 +222,21 @@ class H2ConnectionBase(H2Streams):
|
|
|
213
222
|
async def close(self):
|
|
214
223
|
# Stop the write pump by SIGNAL, flush-then-exit, and JOIN — never cancel it
|
|
215
224
|
# (a cancellation landing between its take and its flush would lose control
|
|
216
|
-
# frames).
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
225
|
+
# frames). Drain what is enqueued (h2 `codec.shutdown` = flush THEN shutdown,
|
|
226
|
+
# F23), shut the transport down — `FramedWrite::shutdown` ends in the IO's
|
|
227
|
+
# `poll_shutdown` (`close_notify` over TLS), which is also what ends the read
|
|
228
|
+
# pump's parked read — join it, and wake every straggler. On a transport that
|
|
229
|
+
# already died the alert cannot go out and the shutdown is the plain close
|
|
230
|
+
# hyper's drop would be: wire-identical. Last, wait for the background
|
|
231
|
+
# request-body writers: every stream is stopped by now, so each one wakes and
|
|
232
|
+
# exits — hyper's connection task likewise ends only after the last
|
|
233
|
+
# `PipeToSendStream` dropped its `conn_drop_ref`; the writers are never
|
|
234
|
+
# cancelled, here or there.
|
|
223
235
|
self.stop_pump()
|
|
224
236
|
self._write_evt.set()
|
|
225
237
|
handle = self.take_pump_handle()
|
|
226
238
|
if handle is not None:
|
|
227
239
|
await handle
|
|
228
|
-
self._write_scope.cancel()
|
|
229
|
-
await self._write_scope.__aexit__(None, None, None)
|
|
230
240
|
with contextlib.suppress(Exception): # best-effort: the connection is closing regardless
|
|
231
241
|
await self._flush()
|
|
232
242
|
await self.backend.shutdown_transport(self._transport)
|
|
@@ -234,6 +244,8 @@ class H2ConnectionBase(H2Streams):
|
|
|
234
244
|
if handle is not None:
|
|
235
245
|
await handle
|
|
236
246
|
self._fail(None) # no-op when the pump already failed everyone (F44)
|
|
247
|
+
if not self.release_writers():
|
|
248
|
+
await self._writers_evt.wait()
|
|
237
249
|
|
|
238
250
|
# ----- the write pump (h2 `poll_complete` draining its frame queue) -----
|
|
239
251
|
|
|
@@ -382,18 +394,19 @@ class H2ConnectionBase(H2Streams):
|
|
|
382
394
|
|
|
383
395
|
# ----- sending (h2 share.rs SendStream over send.rs, hyper PipeToSendStream) -----
|
|
384
396
|
|
|
385
|
-
async def _send_body(self, st, body, trailers=None):
|
|
397
|
+
async def _send_body(self, st, body, trailers=None, offset=0):
|
|
386
398
|
"""Stream a body, marking END_STREAM on the final DATA frame (or on the
|
|
387
399
|
trailing HEADERS), then close the send half. A bodyless message never reaches
|
|
388
|
-
here — its END_STREAM rode the HEADERS frame.
|
|
400
|
+
here — its END_STREAM rode the HEADERS frame. `offset`: where a bytes body
|
|
401
|
+
resumes after the client's inline attempt queued its head (`_send_body_inline`)."""
|
|
389
402
|
if isinstance(body, bytes):
|
|
390
403
|
# hyper's `Full` body: one poll yields the whole chunk with `is_end_stream()`
|
|
391
404
|
# set — one DATA carrying END_STREAM (or the trailers carry it, F45).
|
|
392
405
|
if trailers is None:
|
|
393
|
-
await self._send_data(st, body, end_stream=True)
|
|
406
|
+
await self._send_data(st, body, end_stream=True, offset=offset)
|
|
394
407
|
return
|
|
395
408
|
if body:
|
|
396
|
-
await self._send_data(st, body, end_stream=False)
|
|
409
|
+
await self._send_data(st, body, end_stream=False, offset=offset)
|
|
397
410
|
self._send_trailers(st, trailers)
|
|
398
411
|
return
|
|
399
412
|
if body is not None and not isinstance(body, bytearray) and hasattr(body, "__aiter__"):
|
|
@@ -459,16 +472,16 @@ class H2ConnectionBase(H2Streams):
|
|
|
459
472
|
await aclose_body(body)
|
|
460
473
|
done.set()
|
|
461
474
|
|
|
462
|
-
|
|
463
|
-
"""Queue `data`
|
|
464
|
-
stream window, send-buffer room, peer
|
|
465
|
-
behind `poll_capacity`. `send_data` is
|
|
466
|
-
append — and, with END_STREAM on the last
|
|
467
|
-
on the server the request's recv half
|
|
468
|
-
`RecvStream`: `handle` is the reader to notify
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
475
|
+
def _try_send_data(self, st, data, offset, end_stream):
|
|
476
|
+
"""Queue DATA frames from `data[offset:]` while the windows take them, each
|
|
477
|
+
reserved against min(connection window, stream window, send-buffer room, peer
|
|
478
|
+
max_frame_size) — h2 send.rs `send_data` behind `poll_capacity`. `send_data` is
|
|
479
|
+
one locked step (reserve + encode + append — and, with END_STREAM on the last
|
|
480
|
+
frame, the send half's close, as h2's; on the server the request's recv half
|
|
481
|
+
goes with it, hyper dropping the `RecvStream`: `handle` is the reader to notify
|
|
482
|
+
when its unread body was reset). Returns None once the last frame is queued, or
|
|
483
|
+
the offset to resume from on "no window now" (`sent == 0`, not `done`). Raises
|
|
484
|
+
the stopped error."""
|
|
472
485
|
while True:
|
|
473
486
|
v = self.send_data(st.id, data, offset, end_stream)
|
|
474
487
|
if v.stopped is not None:
|
|
@@ -478,23 +491,25 @@ class H2ConnectionBase(H2Streams):
|
|
|
478
491
|
if v.done:
|
|
479
492
|
if v.handle is not None:
|
|
480
493
|
self._notify_reset(v.handle, v.reader_stop)
|
|
481
|
-
return
|
|
494
|
+
return None
|
|
495
|
+
if not v.sent:
|
|
496
|
+
return offset
|
|
482
497
|
offset += v.sent
|
|
483
|
-
|
|
484
|
-
|
|
498
|
+
|
|
499
|
+
async def _send_data(self, st, data, end_stream, offset=0):
|
|
500
|
+
"""`_try_send_data` until the body is queued, awaiting "no window now" on
|
|
501
|
+
`window_evt` with the fixed waiter idiom: try, clear, try again, wait
|
|
502
|
+
(design §4)."""
|
|
503
|
+
while True:
|
|
504
|
+
offset = self._try_send_data(st, data, offset, end_stream)
|
|
505
|
+
if offset is None:
|
|
506
|
+
return
|
|
485
507
|
st.window_evt.clear()
|
|
486
|
-
|
|
487
|
-
if
|
|
488
|
-
raise self._stopped_error(st, v.stopped)
|
|
489
|
-
if v.flags:
|
|
490
|
-
self._after(v.flags)
|
|
491
|
-
if v.done:
|
|
492
|
-
if v.handle is not None:
|
|
493
|
-
self._notify_reset(v.handle, v.reader_stop)
|
|
508
|
+
resumed = self._try_send_data(st, data, offset, end_stream) # re-check after the clear
|
|
509
|
+
if resumed is None:
|
|
494
510
|
return
|
|
495
|
-
offset
|
|
496
|
-
|
|
497
|
-
await st.window_evt.wait()
|
|
511
|
+
offset = resumed
|
|
512
|
+
await st.window_evt.wait()
|
|
498
513
|
|
|
499
514
|
def _send_trailers(self, st, trailers):
|
|
500
515
|
"""The trailing HEADERS (END_STREAM) closes the send half in the same step
|