Skip to content

retry middleware fails in parallel #2

@BuonOmo

Description

@BuonOmo

Basic Info

  • Faraday Version: 1.7.1 (and also main branch at fdf797b)
  • Ruby Version: 2.7.4

Issue description

The retry count middleware does not wait for completed requests when in parallel, hence retries are not issued.

Steps to reproduce

#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/inline"
gemfile do
  source "https://rubygems.org"

  gem "faraday"
  gem "typhoeus"
end

count = 0
expected = 5

faraday = Faraday.new do |conn|
  retry_options = {
    max: expected,
    interval: 0.1,
    retry_statuses: [503],
    retry_block: proc { count += 1 }
  }
  conn.request :retry, **retry_options
  conn.adapter :typhoeus
end

faraday.in_parallel do
  faraday.get("https://httpbin.org/status/503")
end

exit 0 if count == expected

warn "Retried #{count} times, expected #{expected}"
exit 1

Fix

Note that I already found a fix working locally for typhoeus, I would just like to know if that's a bug for you and I spend my time wisely making it work in every cases! (I think the tricky part will concern chaining with the raise middleware)

Here's a working patch for the precise reproduction above:

diff --git a/lib/faraday/request/retry.rb b/lib/faraday/request/retry.rb
index 3ba50b7..460b683 100644
--- a/lib/faraday/request/retry.rb
+++ b/lib/faraday/request/retry.rb
@@ -145,10 +145,29 @@ module Faraday
         begin
           # after failure env[:body] is set to the response body
           env[:body] = request_body
-          @app.call(env).tap do |resp|
+          # TODO: distinguish the parallel and usual pipeline for this.
+          @app.call(env).on_complete do |resp|
+            if !resp.status
+              raise "bug!"
+            end
             if @options.retry_statuses.include?(resp.status)
               raise Faraday::RetriableResponse.new(nil, resp)
             end
+          rescue @errmatch => e
+            # TODO: dry...
+            if retries.positive? && retry_request?(env, e)
+              retries -= 1
+              rewind_files(request_body)
+              @options.retry_block.call(env, @options, retries, e)
+              if (sleep_amount = calculate_sleep_amount(retries + 1, env))
+                sleep sleep_amount
+                retry
+              end
+
+              raise unless e.is_a?(Faraday::RetriableResponse)
+
+              e.response
+            end
           end
         rescue @errmatch => e
           if retries.positive? && retry_request?(env, e)

If you're in I should be able to get that done within 2 weeks


Bonus

Note that the instrumentation middleware has basically the same issue, I can fix it with

diff --git a/lib/faraday/request/instrumentation.rb b/lib/faraday/request/instrumentation.rb
index c03ba1e..84822d6 100644
--- a/lib/faraday/request/instrumentation.rb
+++ b/lib/faraday/request/instrumentation.rb
@@ -44,10 +44,13 @@ module Faraday
       end
 
       # @param env [Faraday::Env]
-      def call(env)
-        @instrumenter.instrument(@name, env) do
-          @app.call(env)
-        end
+      def on_request(env)
+        @instrumenter.start(@name, env)
+      end
+
+      # @param env [Faraday::Env]
+      def on_complete(env)
+        @instrumenter.finish(@name, env)
       end
     end
   end

One issue though, the time (ends - starts) would be wrong, and I see that faraday does not add the request time to env data..

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions