Skip to content

Commit f83b897

Browse files
authored
Don't send RST_STREAM when not needed (#434)
When the stream state is local_half_closed and an end_stream flag is received, the stream can be terminated normally without RST_STREAM.
1 parent cb43462 commit f83b897

File tree

2 files changed

+43
-18
lines changed

2 files changed

+43
-18
lines changed

lib/mint/http2.ex

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,7 +1566,7 @@ defmodule Mint.HTTP2 do
15661566
responses = [{:data, stream.ref, data} | responses]
15671567

15681568
if flag_set?(flags, :data, :end_stream) do
1569-
conn = close_stream!(conn, stream.id, :no_error)
1569+
conn = close_stream!(conn, stream.id, :remote_end_stream)
15701570
{conn, [{:done, stream.ref} | responses]}
15711571
else
15721572
{conn, responses}
@@ -1675,7 +1675,7 @@ defmodule Mint.HTTP2 do
16751675
{conn, responses}
16761676

16771677
end_stream? ->
1678-
conn = close_stream!(conn, stream.id, :no_error)
1678+
conn = close_stream!(conn, stream.id, :remote_end_stream)
16791679
{conn, [{:done, ref} | new_responses]}
16801680

16811681
true ->
@@ -1685,7 +1685,7 @@ defmodule Mint.HTTP2 do
16851685
end
16861686

16871687
end_stream? ->
1688-
conn = close_stream!(conn, stream.id, :no_error)
1688+
conn = close_stream!(conn, stream.id, :remote_end_stream)
16891689
{conn, [{:done, ref} | new_responses]}
16901690

16911691
true ->
@@ -1695,7 +1695,7 @@ defmodule Mint.HTTP2 do
16951695
# Trailer headers. We don't care about the :status header here.
16961696
headers when received_first_headers? ->
16971697
if end_stream? do
1698-
conn = close_stream!(conn, stream.id, :no_error)
1698+
conn = close_stream!(conn, stream.id, :remote_end_stream)
16991699
headers = headers |> Headers.remove_unallowed_trailer() |> join_cookie_headers()
17001700
{conn, [{:done, ref}, {:headers, ref, headers} | responses]}
17011701
else
@@ -2094,18 +2094,27 @@ defmodule Mint.HTTP2 do
20942094
throw({:mint, %{conn | state: :closed}, wrap_error({error_code, debug_data})})
20952095
end
20962096

2097-
defp close_stream!(conn, stream_id, error_code) do
2097+
# Reason is either an error code or `remote_end_stream`
2098+
defp close_stream!(conn, stream_id, reason) do
20982099
stream = Map.fetch!(conn.streams, stream_id)
20992100

2100-
# First of all we send a RST_STREAM with the given error code so that we
2101-
# move the stream to the :closed state (that is, we remove it).
2102-
rst_stream_frame = rst_stream(stream_id: stream_id, error_code: error_code)
2103-
21042101
conn =
2105-
if open?(conn) do
2106-
send!(conn, Frame.encode(rst_stream_frame))
2107-
else
2108-
conn
2102+
cond do
2103+
# If the stream is ended on both sides, it is already deemed closed and
2104+
# there's no need to send a RST_STREAM frame
2105+
reason == :remote_end_stream and stream.state == :half_closed_local ->
2106+
conn
2107+
2108+
# We send a RST_STREAM with the given error code so that we move the
2109+
# stream to the :closed state (that is, we remove it).
2110+
open?(conn) ->
2111+
error_code = if reason == :remote_end_stream, do: :no_error, else: reason
2112+
rst_stream_frame = rst_stream(stream_id: stream_id, error_code: error_code)
2113+
send!(conn, Frame.encode(rst_stream_frame))
2114+
2115+
# If the connection is already closed, no-op
2116+
true ->
2117+
conn
21092118
end
21102119

21112120
delete_stream(conn, stream)

test/mint/http2/conn_test.exs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ defmodule Mint.HTTP2Test do
325325

326326
assert [{:status, ^ref, 200}, {:headers, ^ref, []}, {:done, ^ref}] = responses
327327

328-
assert_recv_frames [rst_stream(stream_id: ^stream_id)]
328+
assert Enum.empty?(conn.streams)
329329

330330
assert {:ok, %HTTP2{} = conn, []} =
331331
stream_frames(conn, [
@@ -335,6 +335,25 @@ defmodule Mint.HTTP2Test do
335335

336336
assert HTTP2.open?(conn)
337337
end
338+
339+
test "doesn't send RST_STREAM when stream is declared ended in both sides", %{conn: conn} do
340+
{conn, ref} = open_request(conn)
341+
342+
assert_recv_frames [headers(stream_id: stream_id)]
343+
344+
assert conn.streams[stream_id].state == :half_closed_local
345+
346+
assert {:ok, %HTTP2{} = conn, responses} =
347+
stream_frames(conn, [
348+
{:headers, stream_id, [{":status", "200"}], [:end_headers, :end_stream]}
349+
])
350+
351+
assert [{:status, ^ref, 200}, {:headers, ^ref, []}, {:done, ^ref}] = responses
352+
353+
assert_recv_frames([])
354+
assert is_nil(conn.streams[stream_id])
355+
assert HTTP2.open?(conn)
356+
end
338357
end
339358

340359
describe "stream state transitions" do
@@ -763,8 +782,7 @@ defmodule Mint.HTTP2Test do
763782

764783
assert [{:status, ^ref, 200}, {:headers, ^ref, _headers}, {:done, ^ref}] = responses
765784

766-
assert_recv_frames [rst_stream(error_code: :no_error)]
767-
785+
assert Enum.empty?(conn.streams)
768786
assert HTTP2.open?(conn)
769787
end
770788

@@ -1466,8 +1484,6 @@ defmodule Mint.HTTP2Test do
14661484
{:done, ^ref}
14671485
] = responses
14681486

1469-
assert_recv_frames [rst_stream(stream_id: ^stream_id, error_code: :no_error)]
1470-
14711487
# Here we send headers for the two promised streams. Note that neither of the
14721488
# header frames have the END_STREAM flag set otherwise we close the streams and
14731489
# they don't count towards the open stream count.

0 commit comments

Comments
 (0)