Skip to content

Commit

Permalink
feat: adds ability for retry jobs after temporary errors
Browse files Browse the repository at this point in the history
  • Loading branch information
taleksei committed Aug 6, 2018
1 parent 81313de commit 528feab
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ build:
- COMPOSE_FILE_EXT=drone
- RUBY_IMAGE_TAG=2.2-latest
commands:
- wrapdocker docker -v
- prepare-build

- fetch-images --image abakpress/ruby-app:$RUBY_IMAGE_TAG
- dip provision
Expand Down
37 changes: 19 additions & 18 deletions lib/resque/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,33 +100,34 @@ def priority?

# Extend resque-retry.
#
# options - Hash
# options - Hash of retry options (default: {}):
# :limit - Integer max number of retry attempts (default: 2)
# :delay - Integer seconds between retry attempts (default: 60)
# :exceptions - Array or Hash of specific exceptions to retry (optional)
# :temporary - boolean retry on temporary exceptions list (default: false)
# :expire_retry_key_after - Integer expire of retry key in redis (default: 3200)
#
# :limit - Integer (default: 2)
# :delay - Integer (default: 60)
# :expire_retry_key_after - Integer (default: 3200), истечение ключа в секундах. Если
#
# t - среднее время выполнения одного джоба
# n - текущее кол-во джобов в очереди
# k - кол-во воркеров
#
# то expire_retry_key_after >= t * n / k
#
# Иначе ключ истечет, прежде чем джоб отработает.
#
#
# Returns nothing
# Returns nothing.
def retrys(options = {})
if unique?
raise '`retrys` should be declared higher in code than `unique`'
end
raise '`retries` should be declared higher in code than `unique`' if unique?

extend Resque::Plugins::Retry

@retry_limit = options.fetch(:limit, 2)
@retry_delay = options.fetch(:delay, 60)

@retry_exceptions = options[:exceptions] if options.key? :exceptions

if options[:temporary]
@retry_exceptions = @retry_exceptions && @retry_exceptions.dup || {}
@retry_exceptions = @retry_exceptions.product([@retry_delay]).to_h if @retry_exceptions.is_a? Array

@retry_exceptions.reverse_merge!(Rails.application.config.temporary_exceptions)
end

@expire_retry_key_after = options.fetch(:expire_retry_key_after, 1.hour.seconds)
end
alias retries retrys

# Mark Job as ordered
def ordered(options = {})
Expand Down
7 changes: 6 additions & 1 deletion lib/resque/integration/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class Engine < Rails::Engine
end

# Глушим ошибки, по которым происходит автоматический перезапуск
initializer 'resque-integration.retrys' do
initializer 'resque-integration.retrys' do |app|
require 'resque/failure'
require 'resque/failure/redis'

Expand All @@ -89,6 +89,11 @@ class Engine < Rails::Engine
end

Resque::Failure.backend = Resque::Failure::MultipleWithRetrySuppression
# Hash - temporary exceptions
#
# example:
# {PG::TRDeadlockDetected => 20, Net::OpenTimeout => 123}
app.config.temporary_exceptions = {}
end

initializer "resque-integration.extensions" do
Expand Down
10 changes: 10 additions & 0 deletions spec/internal/app/jobs/retries_argument_error_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class RetriesArgumentErrorJob
include Resque::Integration

retries temporary: true, exceptions: [ArgumentError]
queue :test_retries

def self.execute
DummyForRetriesService.call
end
end
10 changes: 10 additions & 0 deletions spec/internal/app/jobs/retries_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class RetriesJob
include Resque::Integration

retries
queue :test_retries

def self.execute
DummyForRetriesService.call
end
end
10 changes: 10 additions & 0 deletions spec/internal/app/jobs/retries_standard_error_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class RetriesStandardErrorJob
include Resque::Integration

retries temporary: true, exceptions: [StandardError]
queue :test_retries

def self.execute
DummyForRetriesService.call
end
end
5 changes: 5 additions & 0 deletions spec/internal/app/services/dummy_for_retries_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class DummyForRetriesService
def self.call
raise StandardError.new('test')
end
end
32 changes: 31 additions & 1 deletion spec/resque/integration_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# coding: utf-8
require 'spec_helper'

RSpec.describe Resque::Integration do
Expand Down Expand Up @@ -102,4 +101,35 @@ def self.execute(id, params)
end
end
end

describe 'retries' do
context 'with default params' do
let(:job) { Resque::Job.new(:test_retries, 'class' => 'RetriesJob', 'args' => ['meta']) }

it do
expect { job.perform }.to raise_error StandardError
expect(Resque.delayed_queue_schedule_size).to eq 1
end
end

context 'with custom params' do
context 'with StandardError in exceptions' do
let(:job) { Resque::Job.new(:test_retries, 'class' => 'RetriesStandardErrorJob', 'args' => ['meta']) }

it do
expect { job.perform }.to raise_error StandardError
expect(Resque.delayed_queue_schedule_size).to eq 1
end
end

context 'with ArgumentError in exceptions' do
let(:job) { Resque::Job.new(:test_retries, 'class' => 'RetriesArgumentErrorJob', 'args' => ['meta']) }

it do
expect { job.perform }.to raise_error StandardError
expect(Resque.delayed_queue_schedule_size).to eq 0
end
end
end
end
end

0 comments on commit 528feab

Please sign in to comment.