Skip to content

Commit 835061d

Browse files
authored
Merge pull request #42 from rainforestapp/RF-22643/pp/retry-failure
[RF-22643] Deserialize arguments before reenqueuing a failed job
2 parents 586a340 + 8ba663e commit 835061d

File tree

10 files changed

+122
-21
lines changed

10 files changed

+122
-21
lines changed

.circleci/config.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
version: 2.1
2+
3+
jobs:
4+
test:
5+
docker:
6+
- image: circleci/ruby:2.7.4-node
7+
auth:
8+
username: $DOCKERHUB_USERNAME
9+
password: $DOCKERHUB_TOKEN
10+
environment:
11+
DATABASE_URL: postgres://circleci:circleci@127.0.0.1:5432/queue_classic_plus_test
12+
- image: circleci/postgres:9.6.6-alpine
13+
auth:
14+
username: $DOCKERHUB_USERNAME
15+
password: $DOCKERHUB_TOKEN
16+
environment:
17+
POSTGRES_USER: circleci
18+
POSTGRES_PASSWORD: circleci
19+
POSTGRES_DB: queue_classic_plus_test
20+
steps:
21+
- checkout
22+
- run:
23+
name: run tests
24+
command: |
25+
bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3
26+
bundle exec rspec
27+
28+
push_to_rubygems:
29+
docker:
30+
- image: circleci/ruby:2.7.4
31+
auth:
32+
username: $DOCKERHUB_USERNAME
33+
password: $DOCKERHUB_TOKEN
34+
steps:
35+
- checkout
36+
- run:
37+
name: Create .gem/credentials file
38+
command: |
39+
mkdir ~/.gem
40+
echo "---
41+
:rubygems_api_key: $RUBYGEMS_API_KEY
42+
" > ~/.gem/credentials
43+
chmod 600 ~/.gem/credentials
44+
- run:
45+
name: Release to rubygems
46+
command: |
47+
gem build queue_classic_plus
48+
gem push queue_classic_plus-*.gem
49+
50+
workflows:
51+
version: 2
52+
gem_release:
53+
jobs:
54+
- test:
55+
context:
56+
- DockerHub
57+
58+
- push_to_rubygems:
59+
filters:
60+
branches:
61+
ignore:
62+
- /.*/
63+
tags:
64+
only:
65+
- /^v.*/
66+
context:
67+
- DockerHub

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*.gem
22
*.rbc
33
.bundle
4+
.byebug_history
45
.config
56
.yardoc
67
Gemfile.lock
@@ -9,6 +10,7 @@ _yardoc
910
coverage
1011
doc/
1112
lib/bundler/man
13+
log/
1214
pkg
1315
rdoc
1416
spec/reports

.travis.yml

Lines changed: 0 additions & 13 deletions
This file was deleted.

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ group :development do
1212
end
1313

1414
group :test do
15+
gem 'byebug'
1516
gem 'rake'
1617
gem 'rspec'
1718
gem 'timecop'

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# QueueClassicPlus
22

3-
[![Build Status](https://travis-ci.org/rainforestapp/queue_classic_plus.svg?branch=master)](https://travis-ci.org/rainforestapp/queue_classic_plus)
3+
[![rainforestapp](https://circleci.com/gh/rainforestapp/queue_classic_plus.svg?branch=master)](https://app.circleci.com/pipelines/github/rainforestapp/queue_classic_plus?branch=master)
44

55
[queue_classic](https://github.com/QueueClassic/queue_classic) is a simple Postgresql backed DB queue. However, it's a little too simple to use it as the main queueing system of a medium to large app. This was developed at [Rainforest QA](https://www.rainforestqa.com/).
66

lib/queue_classic_plus/base.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,7 @@ def self.list
142142
execute q
143143
end
144144

145-
private
146-
def self.execute(sql, *args)
147-
QC.default_conn_adapter.execute(sql, *args)
148-
end
145+
protected
149146

150147
def self.serialized(args)
151148
if defined?(Rails)
@@ -162,5 +159,11 @@ def self.deserialized(args)
162159
args
163160
end
164161
end
162+
163+
private
164+
165+
def self.execute(sql, *args)
166+
QC.default_conn_adapter.execute(sql, *args)
167+
end
165168
end
166169
end

lib/queue_classic_plus/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module QueueClassicPlus
2-
VERSION = "4.0.0.alpha7"
2+
VERSION = '4.0.0.alpha8'.freeze
33
end

lib/queue_classic_plus/worker.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def handle_failure(job, e)
2525
end
2626

2727
@failed_job = job
28+
@failed_job_args = failed_job_class ? failed_job_class.deserialized(job[:args]) : job[:args]
2829

2930
if force_retry && !(failed_job_class.respond_to?(:disable_retries) && failed_job_class.disable_retries)
3031
Metrics.increment("qc.force_retry", source: @q_name)
@@ -44,7 +45,7 @@ def handle_failure(job, e)
4445

4546
def retry_with_remaining(e)
4647
if remaining_retries > 0
47-
failed_job_class.restart_in(backoff, remaining_retries - 1, *@failed_job[:args])
48+
failed_job_class.restart_in(backoff, remaining_retries - 1, *@failed_job_args)
4849
else
4950
enqueue_failed(e)
5051
end
@@ -79,7 +80,7 @@ def connection_error?(e)
7980
def enqueue_failed(e)
8081
sql = "INSERT INTO #{QC.table_name} (q_name, method, args, last_error) VALUES ('failed_jobs', $1, $2, $3)"
8182
last_error = e.backtrace ? ([e.message] + e.backtrace ).join("\n") : e.message
82-
QC.default_conn_adapter.execute sql, @failed_job[:method], JSON.dump(@failed_job[:args]), last_error
83+
QC.default_conn_adapter.execute sql, @failed_job[:method], JSON.dump(@failed_job_args), last_error
8384

8485
QueueClassicPlus.exception_handler.call(e, @failed_job)
8586
Metrics.increment("qc.errors", source: @q_name)

spec/spec_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require 'queue_classic_matchers'
55
require_relative './sample_jobs'
66
require_relative './helpers'
7+
require 'byebug'
78
require 'pry'
89
require 'ddtrace'
910

spec/worker_spec.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,45 @@
6767
end
6868
end
6969

70+
context 'when Rails is defined' do
71+
require 'active_job'
72+
require 'active_job/arguments'
73+
74+
before { stub_const('Rails', Struct.new(:logger).new(Logger.new(STDOUT))) }
75+
76+
it 'retries' do
77+
expect do
78+
job_type.enqueue_perform(:foo)
79+
end.to change_queue_size_of(job_type).by(1)
80+
81+
expect(Jobs::Tests::LockedTestJob).to have_queue_size_of(1)
82+
expect(failed_queue.count).to eq(0)
83+
expect(QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [:foo]).first['remaining_retries']).to be_nil
84+
85+
expect(QueueClassicPlus::Metrics).to receive(:increment).with('qc.retry', source: nil).twice
86+
87+
Timecop.freeze do
88+
worker.work
89+
90+
expect(failed_queue.count).to eq(0) # not enqueued on Failed
91+
expect(QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [:foo]).first['remaining_retries']).to eq "4"
92+
expect(Jobs::Tests::LockedTestJob).to have_scheduled(:foo).at(Time.now + described_class::BACKOFF_WIDTH) # should have scheduled a retry for later
93+
end
94+
95+
Timecop.freeze(Time.now + (described_class::BACKOFF_WIDTH * 2)) do
96+
# the job should be re-enqueued with a decremented retry count
97+
jobs = QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [:foo])
98+
expect(jobs.size).to eq(1)
99+
job = jobs.first
100+
expect(job['remaining_retries'].to_i).to eq(job_type.max_retries - 1)
101+
expect(job['locked_by']).to be_nil
102+
expect(job['locked_at']).to be_nil
103+
end
104+
105+
worker.work
106+
end
107+
end
108+
70109
context 'when PG connection reaped during a job' do
71110
before { Jobs::Tests::ConnectionReapedTestJob.enqueue_perform }
72111

0 commit comments

Comments
 (0)