@@ -176,7 +176,16 @@ defmodule Mint.HTTP2 do
176176
177177 # Fields of the connection.
178178 buffer: "" ,
179+ # `window_size` is the client *send* window for the connection — how
180+ # much request-body data we're allowed to send to the server before it
181+ # refills the window with a WINDOW_UPDATE frame.
179182 window_size: @ default_window_size ,
183+ # `receive_window_size` is the client *receive* window for the
184+ # connection — the peak size we've advertised to the server via
185+ # `WINDOW_UPDATE` frames on stream 0 (or the spec default of 65_535
186+ # if we've never sent one). Mint auto-refills to maintain this peak
187+ # as DATA frames arrive.
188+ receive_window_size: @ default_window_size ,
180189 encode_table: HPAX . new ( 4096 ) ,
181190 decode_table: HPAX . new ( 4096 ) ,
182191
@@ -729,11 +738,21 @@ defmodule Mint.HTTP2 do
729738 end
730739
731740 @ doc """
732- Returns the window size of the connection or of a single request.
741+ Returns the client **send** window size for the connection or a request.
733742
734- This function is HTTP/2 specific. It returns the window size of
735- either the connection if `connection_or_request` is `:connection` or of a single
736- request if `connection_or_request` is `{:request, request_ref}`.
743+ > #### Send vs receive windows {: .warning}
744+ >
745+ > This function returns the *send* window — how much body data this client
746+ > is still permitted to send to the server before being throttled. It is
747+ > decremented by `request/5` and `stream_request_body/3` and refilled by
748+ > the server, which `stream/2` handles transparently.
749+ >
750+ > It does **not** return the client *receive* window (how much the server
751+ > is permitted to send us). To influence that, use `set_window_size/3`.
752+
753+ This function is HTTP/2 specific. It returns the send window of either the
754+ connection if `connection_or_request` is `:connection` or of a single request
755+ if `connection_or_request` is `{:request, request_ref}`.
737756
738757 Use this function to check the window size of the connection before sending a
739758 full request. Also use this function to check the window size of both the
@@ -744,21 +763,23 @@ defmodule Mint.HTTP2 do
744763
745764 ## HTTP/2 Flow Control
746765
747- In HTTP/2, flow control is implemented through a
748- window size. When the client sends data to the server, the window size is decreased
749- and the server needs to "refill" it on the client side. You don't need to take care of
750- the refilling of the client window as it happens behind the scenes in `stream/2`.
766+ In HTTP/2, flow control is implemented through a window size. When the client
767+ sends data to the server, the window size is decreased and the server needs
768+ to "refill" it on the client side, which `stream/2` handles transparently.
769+ Symmetrically, the server's outbound flow toward the client is bounded by a
770+ receive window the client advertises and refills — see `set_window_size/3`.
751771
752- A window size is kept for the entire connection and all requests affect this window
753- size. A window size is also kept per request.
772+ A window size is kept for the entire connection and all requests affect this
773+ window size. A window size is also kept per request.
754774
755- The only thing that affects the window size is the body of a request, regardless of
756- if it's a full request sent with `request/5` or body chunks sent through
757- `stream_request_body/3`. That means that if we make a request with a body that is
758- five bytes long, like `"hello"`, the window size of the connection and the window size
759- of that particular request will decrease by five bytes.
775+ The only thing that affects the send window size is the body of a request,
776+ regardless of whether it's a full request sent with `request/5` or body chunks
777+ sent through `stream_request_body/3`. That means that if we make a request with
778+ a body that is five bytes long, like `"hello"`, the send window size of the
779+ connection and the send window size of that particular request will decrease
780+ by five bytes.
760781
761- If we use all the window size before the server refills it, functions like
782+ If we use all the send window size before the server refills it, functions like
762783 `request/5` will return an error.
763784
764785 ## Examples
@@ -797,6 +818,118 @@ defmodule Mint.HTTP2 do
797818 end
798819 end
799820
821+ @ doc """
822+ Advertises a larger client **receive** window to the server.
823+
824+ > #### Receive vs send windows {: .warning}
825+ >
826+ > This function sets the *receive* window — the peak amount of body data
827+ > the server is permitted to send us before being throttled. It does
828+ > **not** set the *send* window (how much body data we're permitted to
829+ > send to the server) — the server controls that. See `get_window_size/2`
830+ > for the send window.
831+
832+ Without calling this, `stream/2` refills the receive window in small
833+ increments as response body data is consumed. Each refill costs a
834+ round-trip before the server can send more, so bulk throughput is capped
835+ at roughly `window / RTT`; on higher-latency links the default 64 KB
836+ window makes that cap well below the link bandwidth. Raising the window
837+ removes those pauses and is the main HTTP/2 tuning knob for bulk or
838+ highly parallel downloads.
839+
840+ Mint exposes the per-stream initial window as the `:initial_window_size`
841+ client setting passed to `connect/4`, but there is no connection-level
842+ equivalent — use this function for the connection window, and for any
843+ per-stream adjustment after a request has started.
844+
845+ `connection_or_request` is `:connection` for the whole connection or
846+ `{:request, request_ref}` for a single request. `new_size` must be in
847+ `1..2_147_483_647`. Windows can only grow: `new_size` smaller than the
848+ current receive window returns
849+ `{:error, conn, %Mint.HTTPError{reason: :window_size_too_small}}`, and
850+ `new_size` equal to the current window is a no-op.
851+
852+ For more information on flow control and window sizes in HTTP/2, see the
853+ section below.
854+
855+ ## HTTP/2 Flow Control
856+
857+ See `get_window_size/2` for a description of the client *send* window.
858+ The client *receive* window is the symmetric bound on the server's
859+ outbound flow: it starts at 64 KB for the connection and for each new
860+ request, is decremented by response body bytes, and is refilled by
861+ `stream/2` as the body is consumed. A window size is kept for the entire
862+ connection and all responses affect this window size; a window size is
863+ also kept per request.
864+
865+ This function raises the *advertised* receive window — the peak the
866+ server is allowed to fill before pausing. It does not pre-allocate any
867+ buffers; it only permits the server to send further ahead of the
868+ client's reads.
869+
870+ ## Examples
871+
872+ Bump the connection-level receive window right after connect so the server
873+ can stream multi-MB bodies without flow-control pauses:
874+
875+ {:ok, conn} = Mint.HTTP2.connect(:https, host, 443)
876+ {:ok, conn} = Mint.HTTP2.set_window_size(conn, :connection, 8_000_000)
877+
878+ Give one specific request a bigger window than the per-stream default:
879+
880+ {:ok, conn, ref} = Mint.HTTP2.request(conn, "GET", "/huge", [], nil)
881+ {:ok, conn} = Mint.HTTP2.set_window_size(conn, {:request, ref}, 16_000_000)
882+
883+ """
884+ @ spec set_window_size ( t ( ) , :connection | { :request , Types . request_ref ( ) } , pos_integer ( ) ) ::
885+ { :ok , t ( ) } | { :error , t ( ) , Types . error ( ) }
886+ def set_window_size ( conn , connection_or_request , new_size )
887+
888+ def set_window_size ( % __MODULE__ { } = _conn , _target , new_size )
889+ when not ( is_integer ( new_size ) and new_size >= 1 and new_size <= @ max_window_size ) do
890+ raise ArgumentError ,
891+ "new window size must be an integer in 1..#{ @ max_window_size } , got: #{ inspect ( new_size ) } "
892+ end
893+
894+ def set_window_size ( % __MODULE__ { } = conn , :connection , new_size ) do
895+ do_set_window_size ( conn , 0 , conn . receive_window_size , new_size , fn conn , size ->
896+ put_in ( conn . receive_window_size , size )
897+ end )
898+ catch
899+ :throw , { :mint , conn , error } -> { :error , conn , error }
900+ end
901+
902+ def set_window_size ( % __MODULE__ { } = conn , { :request , request_ref } , new_size ) do
903+ case Map . fetch ( conn . ref_to_stream_id , request_ref ) do
904+ { :ok , stream_id } ->
905+ current = conn . streams [ stream_id ] . receive_window_size
906+
907+ do_set_window_size ( conn , stream_id , current , new_size , fn conn , size ->
908+ put_in ( conn . streams [ stream_id ] . receive_window_size , size )
909+ end )
910+
911+ :error ->
912+ { :error , conn , wrap_error ( { :unknown_request_to_stream , request_ref } ) }
913+ end
914+ catch
915+ :throw , { :mint , conn , error } -> { :error , conn , error }
916+ end
917+
918+ defp do_set_window_size ( conn , _stream_id , current , new_size , _update ) when new_size == current do
919+ { :ok , conn }
920+ end
921+
922+ defp do_set_window_size ( conn , _stream_id , current , new_size , _update ) when new_size < current do
923+ { :error , conn , wrap_error ( { :window_size_too_small , current , new_size } ) }
924+ end
925+
926+ defp do_set_window_size ( conn , stream_id , current , new_size , update ) do
927+ increment = new_size - current
928+ frame = window_update ( stream_id: stream_id , window_size_increment: increment )
929+ conn = send! ( conn , Frame . encode ( frame ) )
930+ { :ok , update . ( conn , new_size ) }
931+ end
932+
800933 @ doc """
801934 See `Mint.HTTP.stream/2`.
802935 """
@@ -1083,7 +1216,15 @@ defmodule Mint.HTTP2 do
10831216 id: conn . next_stream_id ,
10841217 ref: make_ref ( ) ,
10851218 state: :idle ,
1219+ # Client send window — decremented as we send body bytes, refilled
1220+ # by incoming WINDOW_UPDATE frames from the server. Bounded initially
1221+ # by the server's SETTINGS_INITIAL_WINDOW_SIZE.
10861222 window_size: conn . server_settings . initial_window_size ,
1223+ # Client receive window — the peak we've advertised to the server
1224+ # for this stream. Starts at whatever we told the server via our
1225+ # SETTINGS_INITIAL_WINDOW_SIZE; can be bumped per-stream with
1226+ # `set_window_size/3`.
1227+ receive_window_size: conn . client_settings . initial_window_size ,
10871228 received_first_headers?: false
10881229 }
10891230
@@ -2223,6 +2364,15 @@ defmodule Mint.HTTP2 do
22232364 "can't stream chunk of data because the request is unknown"
22242365 end
22252366
2367+ def format_error ( { :unknown_request_to_stream , ref } ) do
2368+ "request with reference #{ inspect ( ref ) } was not found"
2369+ end
2370+
2371+ def format_error ( { :window_size_too_small , current , new_size } ) do
2372+ "set_window_size/3 can only grow a window; new size #{ new_size } is " <>
2373+ "smaller than the current size #{ current } "
2374+ end
2375+
22262376 def format_error ( :request_is_not_streaming ) do
22272377 "can't send more data on this request since it's not streaming"
22282378 end
0 commit comments