Skip to content

Commit fa2b5bc

Browse files
committed
Re-vendor Mint with set_window_size/3, call it from Conn
Refactored the Mint HTTP/2 connection-window patch from a narrow `:connection_window_size` connect option into a proper public API, `Mint.HTTP2.set_window_size(conn, target, new_size)`, that supports both `:connection` and `{:request, ref}` and can be called at any point after connect. Tracks the receive window in a new `receive_window_size` field (connection and stream); grow-only; validated to `1..2^31-1`. This is the function shape that fills a longstanding, well-known gap in Mint's public API — upstream issue #357 (2022, closed) asked for exactly this, #432 (2024, still open) is a related enhancement. Ready to submit upstream as a PR. On the hex side: * Re-vendor from the integration branch (ericmj/hex-vendor-integration) which has PR #478 (Elixir 1.12), PR #479 (HTTP/1 1xx handling) and the new set_window_size/3 commit all stacked. * `Conn.do_connect` now calls `Hex.Mint.HTTP2.set_window_size(conn, :connection, 8_000_000)` immediately after a successful HTTP/2 connect. No-op on HTTP/1. TCP ordering guarantees the WINDOW_UPDATE reaches the server before any request HEADERS, so there's no extra RTT. * `:client_settings: [initial_window_size: 8_000_000]` still handles the per-stream initial window via SETTINGS. * Update `scripts/vendor_mint.sh` comment block to point at the integration branch and list all three upstream Mint patches with branch links.
1 parent b230ba3 commit fa2b5bc

22 files changed

Lines changed: 264 additions & 109 deletions

lib/hex/http/pool/conn.ex

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -140,48 +140,65 @@ defmodule Hex.HTTP.Pool.Conn do
140140

141141
## Connect / reconnect
142142

143+
@receive_window_size 8_000_000
144+
143145
defp do_connect(%{key: {scheme, host, port, _inet}, connect_opts: opts} = state) do
144146
# Negotiate HTTP/2 via ALPN when the server supports it; fall back to
145147
# HTTP/1. Benchmarked against real hex.pm + repo.hex.pm — with the HTTP/2
146-
# window bumps below, both protocols are equivalent on `mix deps.get`
147-
# wall time, and HTTP/2 uses slightly less CPU (fewer TLS handshakes).
148-
#
149-
# HTTP/2 window tuning: default per-stream and connection-level windows
150-
# are only 64 KB (spec minimum), which stalls large-body downloads
151-
# (hex tarballs) every 64 KB waiting for a WINDOW_UPDATE RTT. Bump both
152-
# to 8 MB. `connection_window_size` is a local Mint patch; see HEX PATCH
153-
# in lib/hex/mint/http2.ex.
148+
# window bumps applied below, both protocols are equivalent on
149+
# `mix deps.get` wall time, and HTTP/2 uses slightly less CPU (fewer
150+
# TLS handshakes).
154151
opts =
155152
Keyword.merge(
156153
[
157154
protocols: [:http1, :http2],
158-
client_settings: [initial_window_size: 8_000_000],
159-
connection_window_size: 8_000_000
155+
# Per-stream initial window (SETTINGS). The connection-level window
156+
# is bumped via set_window_size/3 immediately below.
157+
client_settings: [initial_window_size: @receive_window_size]
160158
],
161159
opts
162160
)
163161

164162
case MintHTTP.connect(scheme, host, port, opts) do
165163
{:ok, conn} ->
166-
protocol = MintHTTP.protocol(conn)
167-
capacity = compute_capacity(conn, protocol)
168-
GenServer.cast(state.host_pid, {:conn_ready, self(), protocol, capacity})
169-
170-
{:noreply,
171-
%{
172-
state
173-
| conn: conn,
174-
protocol: protocol,
175-
capacity: capacity,
176-
ready: true,
177-
backoff_ms: 0
178-
}}
164+
case maybe_bump_connection_window(conn) do
165+
{:ok, conn} ->
166+
protocol = MintHTTP.protocol(conn)
167+
capacity = compute_capacity(conn, protocol)
168+
GenServer.cast(state.host_pid, {:conn_ready, self(), protocol, capacity})
169+
170+
{:noreply,
171+
%{
172+
state
173+
| conn: conn,
174+
protocol: protocol,
175+
capacity: capacity,
176+
ready: true,
177+
backoff_ms: 0
178+
}}
179+
180+
{:error, _conn, reason} ->
181+
schedule_reconnect(reason, %{state | conn: nil, ready: false})
182+
end
179183

180184
{:error, reason} ->
181185
schedule_reconnect(reason, %{state | conn: nil, ready: false})
182186
end
183187
end
184188

189+
# HTTP/2 default per-connection receive window is 64 KB (RFC 7540 §5.2.2),
190+
# not tunable via SETTINGS. Hex tarballs are routinely multi-MB and we run
191+
# them in parallel sharing one HTTP/2 connection, so without bumping this
192+
# the server pauses every ~64 KB waiting for a `WINDOW_UPDATE` round-trip.
193+
# HTTP/1 has no concept of a receive window so this is a no-op there —
194+
# `set_window_size/3` is only on `Hex.Mint.HTTP2`.
195+
defp maybe_bump_connection_window(conn) do
196+
case MintHTTP.protocol(conn) do
197+
:http2 -> Hex.Mint.HTTP2.set_window_size(conn, :connection, @receive_window_size)
198+
:http1 -> {:ok, conn}
199+
end
200+
end
201+
185202
defp close_and_reconnect(state) do
186203
if state.conn, do: safe_close(state.conn)
187204
schedule_reconnect(:closed, %{state | conn: nil, ready: false})

lib/hex/mint/core/conn.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Vendored from mint v1.7.1 (f7f0313), do not edit manually
1+
# Vendored from mint v1.7.1 (74471c9), do not edit manually
22

33
defmodule Hex.Mint.Core.Conn do
44
@moduledoc false

lib/hex/mint/core/headers.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Vendored from mint v1.7.1 (f7f0313), do not edit manually
1+
# Vendored from mint v1.7.1 (74471c9), do not edit manually
22

33
defmodule Hex.Mint.Core.Headers do
44
@moduledoc false

lib/hex/mint/core/transport.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Vendored from mint v1.7.1 (f7f0313), do not edit manually
1+
# Vendored from mint v1.7.1 (74471c9), do not edit manually
22

33
defmodule Hex.Mint.Core.Transport do
44
@moduledoc false

lib/hex/mint/core/transport/ssl.ex

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Vendored from mint v1.7.1 (f7f0313), do not edit manually
1+
# Vendored from mint v1.7.1 (74471c9), do not edit manually
22

33
defmodule Hex.Mint.Core.Transport.SSL do
44
@moduledoc false
@@ -603,14 +603,18 @@ defmodule Hex.Mint.Core.Transport.SSL do
603603
end
604604

605605
defp get_cacertfile(path) do
606-
case :persistent_term.get({:hex_mint, {:cacertfile, path}}, :error) do
607-
{:ok, cacerts} ->
608-
cacerts
609-
610-
:error ->
611-
cacerts = decode_cacertfile(path)
612-
:persistent_term.put({:hex_mint, {:cacertfile, path}}, {:ok, cacerts})
613-
cacerts
606+
if Application.get_env(:mint, :persistent_term) do
607+
case :persistent_term.get({:hex_mint, {:cacertfile, path}}, :error) do
608+
{:ok, cacerts} ->
609+
cacerts
610+
611+
:error ->
612+
cacerts = decode_cacertfile(path)
613+
:persistent_term.put({:hex_mint, {:cacertfile, path}}, {:ok, cacerts})
614+
cacerts
615+
end
616+
else
617+
decode_cacertfile(path)
614618
end
615619
end
616620

lib/hex/mint/core/transport/tcp.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Vendored from mint v1.7.1 (f7f0313), do not edit manually
1+
# Vendored from mint v1.7.1 (74471c9), do not edit manually
22

33
defmodule Hex.Mint.Core.Transport.TCP do
44
@moduledoc false

lib/hex/mint/core/util.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Vendored from mint v1.7.1 (f7f0313), do not edit manually
1+
# Vendored from mint v1.7.1 (74471c9), do not edit manually
22

33
defmodule Hex.Mint.Core.Util do
44
@moduledoc false

lib/hex/mint/http.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Vendored from mint v1.7.1 (f7f0313), do not edit manually
1+
# Vendored from mint v1.7.1 (74471c9), do not edit manually
22

33
defmodule Hex.Mint.HTTP do
44
_ = """

lib/hex/mint/http1.ex

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Vendored from mint v1.7.1 (f7f0313), do not edit manually
1+
# Vendored from mint v1.7.1 (74471c9), do not edit manually
22

33
defmodule Hex.Mint.HTTP1 do
44
_ = """
@@ -744,9 +744,9 @@ defmodule Hex.Mint.HTTP1 do
744744
{:ok, conn, responses}
745745
end
746746

747-
# HEX PATCH: informational (1xx) response finished parsing — do NOT emit
748-
# :done and do NOT pop the request. Reset the response-side fields and
749-
# continue parsing; the final response arrives on the same request ref.
747+
# Informational (1xx) responses have no body and must not finalize the
748+
# request; the final response follows on the same request ref. Reset the
749+
# request's response-side fields and continue parsing without popping it.
750750
defp decode_body(:informational, conn, data, _request_ref, responses) do
751751
request = %{
752752
conn.request
@@ -785,7 +785,7 @@ defmodule Hex.Mint.HTTP1 do
785785
{:ok, conn, responses}
786786

787787
length <= byte_size(data) ->
788-
{body, rest} = :erlang.split_binary(data, length)
788+
<<body::binary-size(^length), rest::binary>> = data
789789
{conn, responses} = add_body(conn, body, responses)
790790
conn = request_done(conn)
791791
responses = [{:done, request_ref} | responses]
@@ -859,7 +859,7 @@ defmodule Hex.Mint.HTTP1 do
859859
{:ok, conn, responses}
860860

861861
length <= byte_size(data) ->
862-
{body, rest} = :erlang.split_binary(data, length)
862+
<<body::binary-size(^length), rest::binary>> = data
863863
{conn, responses} = add_body(conn, body, responses)
864864
conn = put_in(conn.request.body, {:chunked, :crlf})
865865
decode_body({:chunked, :crlf}, conn, rest, request_ref, responses)
@@ -1004,10 +1004,6 @@ defmodule Hex.Mint.HTTP1 do
10041004
status == 101 ->
10051005
{:ok, :single}
10061006

1007-
# HEX PATCH: 1xx informational responses don't have a body and must not
1008-
# finalize the request — the final response follows on the same ref.
1009-
# Upstream Mint bundles them with HEAD/204/304 (see :none clause below),
1010-
# which pops the request and breaks the real response. Split 1xx out.
10111007
status in 100..199 ->
10121008
{:ok, :informational}
10131009

lib/hex/mint/http1/parse.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Vendored from mint v1.7.1 (f7f0313), do not edit manually
1+
# Vendored from mint v1.7.1 (74471c9), do not edit manually
22

33
defmodule Hex.Mint.HTTP1.Parse do
44
@moduledoc false

0 commit comments

Comments
 (0)