diff --git a/lib/http/2/connection.rb b/lib/http/2/connection.rb index 49c31f73..1553ef1f 100644 --- a/lib/http/2/connection.rb +++ b/lib/http/2/connection.rb @@ -81,6 +81,7 @@ def initialize(**settings) @active_stream_count = 0 @streams = {} + @streams_recently_closed = {} @pending_settings = [] @framer = Framer.new @@ -282,6 +283,12 @@ def receive(data) parent = @streams[frame[:stream]] pid = frame[:promise_stream] + # if PUSH parent is recently closed, RST_STREAM the push + if @streams_recently_closed[frame[:stream]] + send(type: :rst_stream, stream: pid, error: :refused_stream) + return + end + connection_error(msg: 'missing parent ID') if parent.nil? unless parent.state == :open || parent.state == :half_closed_local @@ -634,7 +641,18 @@ def activate_stream(id: nil, **args) # states count toward the maximum number of streams that an endpoint is # permitted to open. stream.once(:active) { @active_stream_count += 1 } - stream.once(:close) { @active_stream_count -= 1 } + stream.once(:close) do + @streams.delete id + @active_stream_count -= 1 + + # Store a reference to the closed stream, such that we can respond + # to any in-flight frames while close is registered on both sides. + # References to such streams will be purged whenever another stream + # is closed, with a minimum of 15s RTT time window. + @streams_recently_closed.delete_if { |_, v| (Time.now - v) > 15 } + @streams_recently_closed[id] = Time.now + end + stream.on(:promise, &method(:promise)) if self.is_a? Server stream.on(:frame, &method(:send)) stream.on(:window_update, &method(:window_update))