]> cat aescling's git repositories - mastodon.git/commitdiff
Add stoplight for object storage failures, return HTTP 503 (#13043)
authorEugen Rochko <eugen@zeonfederated.com>
Tue, 15 Dec 2020 11:55:29 +0000 (12:55 +0100)
committerGitHub <noreply@github.com>
Tue, 15 Dec 2020 11:55:29 +0000 (12:55 +0100)
app/controllers/api/base_controller.rb
app/controllers/application_controller.rb
app/lib/activitypub/activity/create.rb
config/initializers/paperclip.rb
lib/paperclip/attachment_extensions.rb

index fe199e689a0a9cca276391eb798fce5a1a60af20..85f4cc7681a511461b29b42bf54f9b92c8f73ac3 100644 (file)
@@ -40,7 +40,7 @@ class Api::BaseController < ApplicationController
     render json: { error: 'This action is not allowed' }, status: 403
   end
 
-  rescue_from Mastodon::RaceConditionError do
+  rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight do
     render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
   end
 
index 2201e463e68d05f8202eaf0ff13c02d9058d1bdc..44616d6e5ee45e35a8609946126d5fd9cfe2f38c 100644 (file)
@@ -28,7 +28,7 @@ class ApplicationController < ActionController::Base
   rescue_from ActiveRecord::RecordNotFound, with: :not_found
   rescue_from Mastodon::NotPermittedError, with: :forbidden
   rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
-  rescue_from Mastodon::RaceConditionError, with: :service_unavailable
+  rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight, with: :service_unavailable
   rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
 
   before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
index c77f237f9a77fac148384a5e368dd0968dbf5c2c..6127446763dc02ad915b793f7c0229ce60478f65 100644 (file)
@@ -228,6 +228,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     emoji ||= CustomEmoji.new(domain: @account.domain, shortcode: shortcode, uri: uri)
     emoji.image_remote_url = image_url
     emoji.save
+  rescue Seahorse::Client::NetworkingError
+    nil
   end
 
   def process_attachments
@@ -250,6 +252,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
         media_attachment.save
       rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
         RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id)
+      rescue Seahorse::Client::NetworkingError
+        nil
       end
     end
 
index 25adcd8d634c6f452bc6e005532b922a34476871..9ad7fd814c80dac34d29c05a0269e46731d3f4f3 100644 (file)
@@ -113,3 +113,14 @@ else
 end
 
 Paperclip.options[:content_type_mappings] = { csv: Import::FILE_TYPES }
+
+# In some places in the code, we rescue this exception, but we don't always
+# load the S3 library, so it may be an undefined constant:
+
+unless defined?(Seahorse)
+  module Seahorse
+    module Client
+      class NetworkingError < StandardError; end
+    end
+  end
+end
index 752e79e65ed8ff6f8df03c85964250188ef43aeb..94f7769b65b57c2ecfeb2ca7d72738ab6dc03892 100644 (file)
@@ -39,6 +39,23 @@ module Paperclip
     def default_url(style_name = default_style)
       @url_generator.for_as_default(style_name)
     end
+
+    STOPLIGHT_THRESHOLD = 10
+    STOPLIGHT_COOLDOWN  = 30
+
+    # We overwrite this method to put a circuit breaker around
+    # calls to object storage, to stop hitting APIs that are slow
+    # to respond or don't respond at all and as such minimize the
+    # impact of object storage outages on application throughput
+    def save
+      Stoplight('object-storage') { super }.with_threshold(STOPLIGHT_THRESHOLD).with_cool_off_time(STOPLIGHT_COOLDOWN).with_error_handler do |error, handle|
+        if error.is_a?(Seahorse::Client::NetworkingError)
+          handle.call(error)
+        else
+          raise error
+        end
+      end.run
+    end
   end
 end