Commit 7ecb8cb1 authored by Tiago's avatar Tiago
Browse files

do not resend requests from an HTTP/2 conn failed on settings timeout

there are GOAWAY errors which are not recoverable and should therefore
bubble up to pending requests. for instance, a connection failing on a
timeout on handshake, just fails.

A gate has been set to avoid that a peer which constantly GOAWAYs new
connections without processing new requests, causes httpx to be stuck in
a loop.
parent 1faaeff4
Loading
Loading
Loading
Loading
+15 −1
Changes for lib/httpx/connection.rb: 15 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -53,6 +53,10 @@ module HTTPX

      @exhausted = @cloned = @main_sibling = false

      # variable used to gate against a potential endless loop where the peer continuously closes the connection with
      # GOAWAY frames without ever processing a request.
      @exhausted_error_counter = 1

      @options = Options.new(options)
      @type = initialize_type(uri, @options)
      @origins = [uri.origin]
@@ -726,9 +730,19 @@ module HTTPX
      parser.on(:promise) do |request, stream|
        request.emit(:promise, parser, stream)
      end
      parser.on(:exhausted) do
      parser.on(:exhausted) do |error|
        enqueue_pending_requests_from_parser(parser)

        if error
          if @exhausted_error_counter.zero?
            @exhausted_error_counter += 1
            on_error(error)
            next
          else
            @exhausted_error_counter -= 1
          end
        end

        @exhausted = true
        parser.close

+9 −2
Changes for lib/httpx/connection/http2.rb: 9 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -23,12 +23,19 @@ module HTTPX
    end

    class GoawayError < Error
      UNRECOVERABLE_ERRORS = %i[settings_timeout inadequate_security].freeze

      attr_reader :last_stream_id

      def initialize(code, last_stream_id)
        @code = code
        @last_stream_id = last_stream_id
        super(0, code)
      end

      def unrecoverable?
        UNRECOVERABLE_ERRORS.include?(@code)
      end
    end

    class RstStreamError < Error
@@ -188,10 +195,10 @@ module HTTPX
        emit(:error, req, ex)
      end

      if ex.is_a?(GoawayError)
      if ex.is_a?(GoawayError) && !ex.unrecoverable?
        # resend unprocessed requests on a different connection
        @pending.unshift(*inflight_unprocessed_requests) if inflight_unprocessed_requests.any?
        emit(:exhausted) if @pending.any?
        emit(:exhausted, ex) if @pending.any?
        return
      end

+1 −0
Changes for sig/connection.rbs: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ module HTTPX
    @response_received_at: Float
    @exhausted: bool
    @cloned: bool
    @exhausted_error_counter: bool
    @coalesced_connection: instance?
    @altsvc_connection: instance?
    @sibling: instance?
+6 −0
Changes for sig/connection/http2.rbs: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -116,9 +116,15 @@ module HTTPX
    end

    class GoawayError < Error
      UNRECOVERABLE_ERRORS: Array[Symbol]

      attr_reader last_stream_id: Integer

      @code: Symbol

      def initialize: (Symbol code, Integer last_stream_id) -> void

      def unrecoverable?: () -> bool
    end

    class PingError < Error