From de593f6e9f9e1363ca53be29a2acdb78572e2716 Mon Sep 17 00:00:00 2001
From: Raphael Sofaer <rafi@sofaer.net>
Date: Thu, 23 Jan 2014 16:47:43 -0800
Subject: [PATCH] Do not retry sending out posts on SSL errors.  See #4728
 Refactor HydraWrapper so the when-to-retry logic is in the worker

---
 app/workers/http_multi.rb       | 15 ++++++++++---
 lib/hydra_wrapper.rb            | 26 ++++++++++++++++------
 spec/workers/http_multi_spec.rb | 38 +++++++++++++++++++++++++++++++--
 3 files changed, 68 insertions(+), 11 deletions(-)

diff --git a/app/workers/http_multi.rb b/app/workers/http_multi.rb
index 2d633a1fe9..e370cd981b 100644
--- a/app/workers/http_multi.rb
+++ b/app/workers/http_multi.rb
@@ -7,7 +7,10 @@ module Workers
     sidekiq_options queue: :http
 
     MAX_RETRIES = 3
-
+    ABANDON_ON_CODES=[:peer_failed_verification, # Certificate does not match URL
+                      :ssl_connect_error, # Problem negotiating ssl version or Cert couldn't be verified (often self-signed)
+                      :ssl_cacert, # Expired SSL cert
+                      ]
     def perform(user_id, encoded_object_xml, person_ids, dispatcher_class_as_string, retry_count=0)
       user = User.find(user_id)
       people = Person.where(:id => person_ids)
@@ -16,11 +19,17 @@ module Workers
       hydra = HydraWrapper.new(user, people, encoded_object_xml, dispatcher)
 
       hydra.enqueue_batch
+
+      hydra.keep_for_retry_if do |response|
+        !ABANDON_ON_CODES.include?(response.return_code)
+      end
+
       hydra.run
 
-      unless hydra.failed_people.empty?
+
+      unless hydra.people_to_retry.empty?
         if retry_count < MAX_RETRIES
-          Workers::HttpMulti.perform_in(1.hour, user_id, encoded_object_xml, hydra.failed_people, dispatcher_class_as_string, retry_count + 1)
+          Workers::HttpMulti.perform_in(1.hour, user_id, encoded_object_xml, hydra.people_to_retry, dispatcher_class_as_string, retry_count + 1)
         else
           Rails.logger.info("event=http_multi_abandon sender_id=#{user_id} failed_recipient_ids='[#{person_ids.join(', ')}] '")
         end
diff --git a/lib/hydra_wrapper.rb b/lib/hydra_wrapper.rb
index 562a7ef26e..e22d94d7f9 100644
--- a/lib/hydra_wrapper.rb
+++ b/lib/hydra_wrapper.rb
@@ -17,16 +17,19 @@ class HydraWrapper
     }
   }
 
-  attr_reader :failed_people, :user, :encoded_object_xml
+  attr_reader :people_to_retry , :user, :encoded_object_xml
   attr_accessor :dispatcher_class, :people
   delegate :run, to: :hydra
 
   def initialize user, people, encoded_object_xml, dispatcher_class
     @user = user
-    @failed_people = []
+    @people_to_retry = []
     @people = people
     @dispatcher_class = dispatcher_class
     @encoded_object_xml = encoded_object_xml
+    @keep_for_retry_proc = Proc.new do |response|
+      true
+    end
   end
 
   # Inserts jobs for all @people
@@ -38,6 +41,14 @@ class HydraWrapper
     end
   end
 
+  # This method can be used to tell the hydra whether or not to
+  # retry a request that it made which failed.
+  # @yieldparam response [Typhoeus::Response] The response object for the failed request.
+  # @yieldreturn [Boolean] Whether the request whose response was passed to the block should be retried.
+  def keep_for_retry_if &block
+    @keep_for_retry_proc = block
+  end
+
   private
 
   def hydra
@@ -55,7 +66,7 @@ class HydraWrapper
     @people.group_by { |person|
       @dispatcher_class.receive_url_for person
     }
-  end 
+  end
 
   # Prepares and inserts job into the hydra queue
   # @param url [String]
@@ -83,11 +94,14 @@ class HydraWrapper
           event: "http_multi_fail",
           sender_id: @user.id,
           url: response.effective_url,
-          response_code: response.code
+          return_code: response.return_code
         }
-        message[:response_message] = response.return_message if response.code == 0
         Rails.logger.info message.to_a.map { |k,v| "#{k}=#{v}" }.join(' ')
-        @failed_people += people_for_receive_url.map(&:id)
+
+        if @keep_for_retry_proc.call(response)
+          @people_to_retry += people_for_receive_url.map(&:id)
+        end
+
       end
     end
   end
diff --git a/spec/workers/http_multi_spec.rb b/spec/workers/http_multi_spec.rb
index f157fa2bd8..b78088e8f1 100644
--- a/spec/workers/http_multi_spec.rb
+++ b/spec/workers/http_multi_spec.rb
@@ -26,13 +26,29 @@ describe Workers::HttpMulti do
       code: 200,
       body: "",
       time: 0.2,
-      effective_url: 'http://foobar.com'
+      effective_url: 'http://foobar.com',
+      return_code: :ok
     )
     @failed_response = Typhoeus::Response.new(
       code: 504,
       body: "",
       time: 0.2,
-      effective_url: 'http://foobar.com'
+      effective_url: 'http://foobar.com',
+      return_code: :ok
+    )
+    @ssl_error_response = Typhoeus::Response.new(
+      code: 0,
+      body: "",
+      time: 0.2,
+      effective_url: 'http://foobar.com',
+      return_code: :ssl_connect_error
+    )
+    @unable_to_resolve_response = Typhoeus::Response.new(
+      code: 0,
+      body: "",
+      time: 0.2,
+      effective_url: 'http://foobar.com',
+      return_code: :couldnt_resolve_host
     )
   end
 
@@ -56,6 +72,24 @@ describe Workers::HttpMulti do
     Workers::HttpMulti.new.perform bob.id, @post_xml, [person.id], "Postzord::Dispatcher::Private"
   end
 
+  it 'retries if it could not resolve the server' do
+    person = @people.first
+
+    Typhoeus.stub(person.receive_url).and_return @unable_to_resolve_response
+
+    Workers::HttpMulti.should_receive(:perform_in).with(1.hour, bob.id, @post_xml, [person.id], anything, 1).once
+    Workers::HttpMulti.new.perform bob.id, @post_xml, [person.id], "Postzord::Dispatcher::Private"
+  end
+
+  it 'does not retry on an SSL error' do
+    person = @people.first
+
+    Typhoeus.stub(person.receive_url).and_return @ssl_error_response
+
+    Workers::HttpMulti.should_not_receive(:perform_in)
+    Workers::HttpMulti.new.perform bob.id, @post_xml, [person.id], "Postzord::Dispatcher::Private"
+  end
+
   it 'max retries' do
     person = @people.first
 
-- 
GitLab