queue_classic_plus 1.1.0 → 4.0.0.alpha8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88536cc93e084fd7a3921cbd10ab31843882e42eef8979222528ed550ea18033
4
- data.tar.gz: c2b1ae63963c66c44d2c86e33441edd2ca90b03800cb76528e477205afc652a4
3
+ metadata.gz: 69af5e1cbbcc08c7cd0acad4c0f06e112a9fff09756a06c9ae2ffd80a71ab39a
4
+ data.tar.gz: cd3cc486050b9de66397099f81e0277aac83f5469bb925456527b331dec85a6a
5
5
  SHA512:
6
- metadata.gz: 6cbadb6208bcb150fdf50bc6122ed99811cd7b92b0f336b337c57f03b8196a384b8e14b40ea6b67ef95d862771a266c62d2f892d8da49a6ff17b1e643281ec44
7
- data.tar.gz: 3d2c44a3bb10a5a7c59cd8a22164e57074515ef221ce467a9fe6017d644ca6e14e0d23c0412b4fcb0eef52381e3bf21ecfc0fc327ed516958e051865bc7961c8
6
+ metadata.gz: 8d3980cb5a576ff5681812a2e5d7497c35ff98dc84934778f4dd3c867643e10e9d87a38499bad6850cc72d1c4cd519a91a2509011f8ad916e78b10be3c2e076e
7
+ data.tar.gz: c31d0baa284b76731b268e29ab21b3ae0e679f7404345d3b449fb755a34df11100d6f6790a6f9e0dfb373ed908a8fa365575d88184f96d28a079270be5e67605
@@ -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
@@ -0,0 +1,8 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ time: "13:00"
8
+ open-pull-requests-limit: 10
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  *.gem
2
2
  *.rbc
3
3
  .bundle
4
+ .byebug_history
4
5
  .config
5
6
  .yardoc
6
7
  Gemfile.lock
@@ -9,6 +10,7 @@ _yardoc
9
10
  coverage
10
11
  doc/
11
12
  lib/bundler/man
13
+ log/
12
14
  pkg
13
15
  rdoc
14
16
  spec/reports
data/Gemfile CHANGED
@@ -12,8 +12,10 @@ group :development do
12
12
  end
13
13
 
14
14
  group :test do
15
+ gem 'byebug'
15
16
  gem 'rake'
16
17
  gem 'rspec'
17
18
  gem 'timecop'
18
19
  gem 'newrelic_rpm'
20
+ gem 'ddtrace'
19
21
  end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # QueueClassicPlus
2
2
 
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)
4
4
 
5
5
  [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/).
6
6
 
@@ -130,7 +130,7 @@ If you want to log exceptions in your favorite exception tracker. You can config
130
130
 
131
131
  ```ruby
132
132
  QueueClassicPlus.exception_handler = -> (exception, job) do
133
- Raven.capture_exception(exception, extra: {job: job, env: ENV})
133
+ Sentry.capture_exception(exception, extra: { job: job, env: ENV })
134
134
  end
135
135
  ```
136
136
 
@@ -150,6 +150,12 @@ If you are using NewRelic and want to push performance data to it, you can add t
150
150
  require "queue_classic_plus/new_relic"
151
151
  ```
152
152
 
153
+ To instrument DataDog monitoring add this to your QC initializer:
154
+
155
+ ```ruby
156
+ require "queue_classic_plus/datadog"
157
+ ```
158
+
153
159
  ## Contributing
154
160
 
155
161
  1. Fork it ( https://github.com/[my-github-username]/queue_classic_plus/fork )
@@ -67,7 +67,7 @@ module QueueClassicPlus
67
67
  )
68
68
  AS x"
69
69
 
70
- result = QC.default_conn_adapter.execute(q, @queue, method, args.to_json)
70
+ result = QC.default_conn_adapter.execute(q, @queue, method, JSON.dump(serialized(args)))
71
71
  result['count'].to_i == 0
72
72
  else
73
73
  true
@@ -76,7 +76,7 @@ module QueueClassicPlus
76
76
 
77
77
  def self.enqueue(method, *args)
78
78
  if can_enqueue?(method, *args)
79
- queue.enqueue(method, *args)
79
+ queue.enqueue(method, *serialized(args))
80
80
  end
81
81
  end
82
82
 
@@ -86,11 +86,11 @@ module QueueClassicPlus
86
86
 
87
87
  def self.enqueue_perform_in(time, *args)
88
88
  raise "Can't enqueue in the future for locked jobs" if locked?
89
- queue.enqueue_in(time, "#{self.to_s}._perform", *args)
89
+ queue.enqueue_in(time, "#{self.to_s}._perform", *serialized(args))
90
90
  end
91
91
 
92
92
  def self.restart_in(time, remaining_retries, *args)
93
- queue.enqueue_retry_in(time, "#{self.to_s}._perform", remaining_retries, *args)
93
+ queue.enqueue_retry_in(time, "#{self.to_s}._perform", remaining_retries, *serialized(args))
94
94
  end
95
95
 
96
96
  def self.do(*args)
@@ -102,13 +102,13 @@ module QueueClassicPlus
102
102
  def self._perform(*args)
103
103
  Metrics.timing("qu_perform_time", source: librato_key) do
104
104
  if skip_transaction
105
- perform(*args)
105
+ perform(*deserialized(args))
106
106
  else
107
107
  transaction do
108
108
  # .to_i defaults to 0, which means no timeout in postgres
109
109
  timeout = ENV['POSTGRES_STATEMENT_TIMEOUT'].to_i * 1000
110
110
  execute "SET LOCAL statement_timeout = #{timeout}"
111
- perform(*args)
111
+ perform(*deserialized(args))
112
112
  end
113
113
  end
114
114
  end
@@ -119,7 +119,7 @@ module QueueClassicPlus
119
119
  end
120
120
 
121
121
  def self.transaction(options = {}, &block)
122
- if defined?(ActiveRecord)
122
+ if defined?(ActiveRecord) && ActiveRecord::Base.connected?
123
123
  # If ActiveRecord is loaded, we use it's own transaction mechanisn since
124
124
  # it has slightly different semanctics for rollback.
125
125
  ActiveRecord::Base.transaction(options, &block)
@@ -142,7 +142,26 @@ module QueueClassicPlus
142
142
  execute q
143
143
  end
144
144
 
145
+ protected
146
+
147
+ def self.serialized(args)
148
+ if defined?(Rails)
149
+ ActiveJob::Arguments.serialize(args)
150
+ else
151
+ args
152
+ end
153
+ end
154
+
155
+ def self.deserialized(args)
156
+ if defined?(Rails)
157
+ ActiveJob::Arguments.deserialize(args)
158
+ else
159
+ args
160
+ end
161
+ end
162
+
145
163
  private
164
+
146
165
  def self.execute(sql, *args)
147
166
  QC.default_conn_adapter.execute(sql, *args)
148
167
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QueueClassicDatadog
4
+ def _perform(*args)
5
+ Datadog.tracer.trace('qc.job', service_name: 'qc.job', resource: "#{name}#perform") do |_|
6
+ super
7
+ end
8
+ end
9
+
10
+ QueueClassicPlus::Base.singleton_class.send(:prepend, QueueClassicDatadog)
11
+ end
@@ -3,7 +3,7 @@ module QC
3
3
 
4
4
  def enqueue_retry_in(seconds, method, remaining_retries, *args)
5
5
  QC.log_yield(:measure => 'queue.enqueue') do
6
- s = "INSERT INTO #{TABLE_NAME} (q_name, method, args, scheduled_at, remaining_retries)
6
+ s = "INSERT INTO #{QC.table_name} (q_name, method, args, scheduled_at, remaining_retries)
7
7
  VALUES ($1, $2, $3, now() + interval '#{seconds.to_i} seconds', $4)"
8
8
 
9
9
  conn_adapter.execute(s, name, method, JSON.dump(args), remaining_retries)
@@ -12,16 +12,36 @@ module QC
12
12
 
13
13
  def lock
14
14
  QC.log_yield(:measure => 'queue.lock') do
15
- s = "SELECT * FROM lock_head($1, $2)"
16
- if r = conn_adapter.execute(s, name, top_bound)
15
+ s = <<~SQL
16
+ WITH selected_job AS (
17
+ SELECT id
18
+ FROM queue_classic_jobs
19
+ WHERE
20
+ locked_at IS NULL AND
21
+ q_name = $1 AND
22
+ scheduled_at <= now()
23
+ LIMIT 1
24
+ FOR NO KEY UPDATE SKIP LOCKED
25
+ )
26
+ UPDATE queue_classic_jobs
27
+ SET
28
+ locked_at = now(),
29
+ locked_by = pg_backend_pid()
30
+ FROM selected_job
31
+ WHERE queue_classic_jobs.id = selected_job.id
32
+ RETURNING *
33
+ SQL
34
+
35
+ if r = conn_adapter.execute(s, name)
17
36
  {}.tap do |job|
18
37
  job[:id] = r["id"]
19
38
  job[:q_name] = r["q_name"]
20
39
  job[:method] = r["method"]
21
40
  job[:args] = JSON.parse(r["args"])
22
- job[:remaining_retries] = r["remaining_retries"]
41
+ job[:remaining_retries] = r["remaining_retries"]&.to_s
23
42
  if r["scheduled_at"]
24
- job[:scheduled_at] = Time.parse(r["scheduled_at"])
43
+ # ActiveSupport may cast time strings to Time
44
+ job[:scheduled_at] = r["scheduled_at"].kind_of?(Time) ? r["scheduled_at"] : Time.parse(r["scheduled_at"])
25
45
  ttl = Integer((Time.now - job[:scheduled_at]) * 1000)
26
46
  QC.measure("time-to-lock=#{ttl}ms source=#{name}")
27
47
  end
@@ -3,10 +3,15 @@ namespace :qc_plus do
3
3
  task :work => :environment do
4
4
  puts "Starting up worker for queue #{ENV['QUEUE']}"
5
5
 
6
- if defined? Raven
6
+ # ActiveRecord::RecordNotFound is ignored by Sentry by default,
7
+ # which shouldn't happen in background jobs.
8
+ if defined?(Sentry)
9
+ Sentry.init do |config|
10
+ config.excluded_exceptions = []
11
+ config.background_worker_threads = 0 if Gem::Version.new(Sentry::VERSION) >= Gem::Version.new('4.1.0')
12
+ end
13
+ elsif defined?(Raven)
7
14
  Raven.configure do |config|
8
- # ActiveRecord::RecordNotFound is ignored by Raven by default,
9
- # which shouldn't happen in background jobs.
10
15
  config.excluded_exceptions = []
11
16
  end
12
17
  end
@@ -1,3 +1,3 @@
1
1
  module QueueClassicPlus
2
- VERSION = "1.1.0"
2
+ VERSION = '4.0.0.alpha8'.freeze
3
3
  end
@@ -25,6 +25,7 @@ module QueueClassicPlus
25
25
  end
26
26
 
27
27
  @failed_job = job
28
+ @failed_job_args = failed_job_class ? failed_job_class.deserialized(job[:args]) : job[:args]
28
29
 
29
30
  if force_retry && !(failed_job_class.respond_to?(:disable_retries) && failed_job_class.disable_retries)
30
31
  Metrics.increment("qc.force_retry", source: @q_name)
@@ -44,7 +45,7 @@ module QueueClassicPlus
44
45
 
45
46
  def retry_with_remaining(e)
46
47
  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)
48
49
  else
49
50
  enqueue_failed(e)
50
51
  end
@@ -77,9 +78,9 @@ module QueueClassicPlus
77
78
  end
78
79
 
79
80
  def enqueue_failed(e)
80
- sql = "INSERT INTO #{QC::TABLE_NAME} (q_name, method, args, last_error) VALUES ('failed_jobs', $1, $2, $3)"
81
+ sql = "INSERT INTO #{QC.table_name} (q_name, method, args, last_error) VALUES ('failed_jobs', $1, $2, $3)"
81
82
  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
83
84
 
84
85
  QueueClassicPlus.exception_handler.call(e, @failed_job)
85
86
  Metrics.increment("qc.errors", source: @q_name)
@@ -14,13 +14,13 @@ module QueueClassicPlus
14
14
  require 'queue_classic_plus/railtie' if defined?(Rails)
15
15
 
16
16
  def self.migrate(c = QC::default_conn_adapter.connection)
17
- conn = QC::ConnAdapter.new(c)
17
+ conn = QC::ConnAdapter.new(connection: c)
18
18
  conn.execute("ALTER TABLE queue_classic_jobs ADD COLUMN last_error TEXT")
19
19
  conn.execute("ALTER TABLE queue_classic_jobs ADD COLUMN remaining_retries INTEGER")
20
20
  end
21
21
 
22
22
  def self.demigrate(c = QC::default_conn_adapter.connection)
23
- conn = QC::ConnAdapter.new(c)
23
+ conn = QC::ConnAdapter.new(connection: c)
24
24
  conn.execute("ALTER TABLE queue_classic_jobs DROP COLUMN last_error")
25
25
  conn.execute("ALTER TABLE queue_classic_jobs DROP COLUMN remaining_retries")
26
26
  end
@@ -18,11 +18,13 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "queue_classic", ">= 3.1.0"
21
+ spec.add_dependency "queue_classic", "4.0.0.pre.alpha1"
22
22
  if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3.0')
23
23
  spec.add_development_dependency "bundler", "~> 1.6"
24
24
  else
25
25
  spec.add_development_dependency "bundler", "~> 2.0"
26
26
  end
27
27
  spec.add_development_dependency "rake"
28
+ spec.add_development_dependency "activerecord", "~> 6.0"
29
+ spec.add_development_dependency "activejob"
28
30
  end
data/spec/base_spec.rb CHANGED
@@ -12,7 +12,19 @@ describe QueueClassicPlus::Base do
12
12
  it "does not allow multiple enqueues" do
13
13
  subject.do
14
14
  subject.do
15
- subject.should have_queue_size_of(1)
15
+ expect(subject).to have_queue_size_of(1)
16
+ end
17
+
18
+ it "checks for an existing job using the same serializing as job enqueuing" do
19
+ # simulate a case where obj#to_json and JSON.dump(obj) do not match
20
+ require 'active_support/core_ext/date_time'
21
+ require 'active_support/json'
22
+ ActiveSupport::JSON::Encoding.use_standard_json_time_format = false
23
+
24
+ date = DateTime.new(2020, 11, 3)
25
+ subject.do(date)
26
+ subject.do(date)
27
+ expect(subject).to have_queue_size_of(1)
16
28
  end
17
29
 
18
30
  it "does allow multiple enqueues if something got locked for too long" do
@@ -20,7 +32,7 @@ describe QueueClassicPlus::Base do
20
32
  one_day_ago = Time.now - 60*60*24
21
33
  execute "UPDATE queue_classic_jobs SET locked_at = '#{one_day_ago}' WHERE q_name = 'test'"
22
34
  subject.do
23
- subject.should have_queue_size_of(2)
35
+ expect(subject).to have_queue_size_of(2)
24
36
  end
25
37
  end
26
38
 
@@ -39,13 +51,15 @@ describe QueueClassicPlus::Base do
39
51
  end
40
52
 
41
53
  it "calls perform in a transaction" do
42
- QueueClassicPlus::Base.should_receive(:transaction).and_call_original
54
+ expect(QueueClassicPlus::Base).to receive(:transaction).and_call_original
55
+
43
56
  subject._perform
44
57
  end
45
58
 
46
59
  it "measures the time" do
47
- QueueClassicPlus::Metrics.should_receive(:timing).with("qu_perform_time", {source: "funky.name"}).and_call_original
48
- subject._perform
60
+ expect(QueueClassicPlus::Metrics).to receive(:timing).with("qu_perform_time", {source: "funky.name"}).and_call_original
61
+
62
+ subject._perform
49
63
  end
50
64
  end
51
65
 
@@ -61,7 +75,8 @@ describe QueueClassicPlus::Base do
61
75
  end
62
76
 
63
77
  it "calls perform outside of a transaction" do
64
- QueueClassicPlus::Base.should_not_receive(:transaction)
78
+ expect(QueueClassicPlus::Base).to_not receive(:transaction)
79
+
65
80
  subject._perform
66
81
  end
67
82
  end
@@ -79,15 +94,15 @@ describe QueueClassicPlus::Base do
79
94
  end
80
95
 
81
96
  it "retries on specified exception" do
82
- subject.retries_on?(SomeException.new).should be(true)
97
+ expect(subject.retries_on?(SomeException.new)).to be(true)
83
98
  end
84
99
 
85
100
  it "does not retry on unspecified exceptions" do
86
- subject.retries_on?(RuntimeError).should be(false)
101
+ expect(subject.retries_on?(RuntimeError)).to be(false)
87
102
  end
88
103
 
89
104
  it "sets max retries" do
90
- subject.max_retries.should == 5
105
+ expect(subject.max_retries).to eq(5)
91
106
  end
92
107
  end
93
108
 
@@ -104,16 +119,16 @@ describe QueueClassicPlus::Base do
104
119
  end
105
120
 
106
121
  it "retries on all specified exceptions" do
107
- subject.retries_on?(SomeException.new).should be(true)
108
- subject.retries_on?(SomeOtherException.new).should be(true)
122
+ expect(subject.retries_on?(SomeException.new)).to be(true)
123
+ expect(subject.retries_on?(SomeOtherException.new)).to be(true)
109
124
  end
110
125
 
111
126
  it "does not retry on unspecified exceptions" do
112
- subject.retries_on?(RuntimeError).should be(false)
127
+ expect(subject.retries_on?(RuntimeError)).to be(false)
113
128
  end
114
129
 
115
130
  it "sets max retries" do
116
- subject.max_retries.should == 5
131
+ expect(subject.max_retries).to eq(5)
117
132
  end
118
133
  end
119
134
 
@@ -133,22 +148,49 @@ describe QueueClassicPlus::Base do
133
148
  end
134
149
 
135
150
  it "retries on a subclass of a specified exception" do
136
- subject.retries_on?(ServiceReallyUnavailable.new).should be(true)
151
+ expect(subject.retries_on?(ServiceReallyUnavailable.new)).to be(true)
137
152
  end
138
153
 
139
154
  it "does not retry on unspecified exceptions" do
140
- subject.retries_on?(RuntimeError).should be(false)
155
+ expect(subject.retries_on?(RuntimeError)).to be(false)
141
156
  end
142
157
 
143
158
  it "sets max retries" do
144
- subject.max_retries.should == 5
159
+ expect(subject.max_retries).to eq(5)
160
+ end
161
+ end
162
+
163
+ context "with Rails defined" do
164
+ require 'active_job/arguments'
165
+
166
+ before { stub_const('Rails', true) }
167
+
168
+ subject do
169
+ Class.new(QueueClassicPlus::Base) do
170
+ @queue = :test
171
+
172
+ def self.perform(foo, bar)
173
+ end
174
+ end
175
+ end
176
+
177
+ it "serializes parameters when enqueuing a job" do
178
+ expect(ActiveJob::Arguments).to receive(:serialize).with([42, true])
179
+
180
+ subject.do(42, true)
181
+ end
182
+
183
+ it "deserializes parameters when performing an enqueued job" do
184
+ expect(ActiveJob::Arguments).to receive(:deserialize).with([42, true]) { [42, true] }
185
+
186
+ subject._perform(42, true)
145
187
  end
146
188
  end
147
189
  end
148
190
 
149
191
  describe ".librato_key" do
150
192
  it "removes unsupported caracter from the classname" do
151
- Jobs::Tests::TestJob.librato_key.should == 'jobs.tests.test_job'
193
+ expect(Jobs::Tests::TestJob.librato_key).to eq('jobs.tests.test_job')
152
194
  end
153
195
  end
154
196
  end
@@ -0,0 +1,18 @@
1
+ describe 'requiring queue_classic_plus/new_relic' do
2
+ class FunkyName < QueueClassicPlus::Base
3
+ @queue = :test
4
+
5
+ def self.perform
6
+ end
7
+ end
8
+
9
+ subject { FunkyName._perform }
10
+
11
+ it 'adds Datadog profiling support' do
12
+ require 'queue_classic_plus/datadog'
13
+ expect(Datadog.tracer).to receive(:trace).with(
14
+ 'qc.job', service_name: 'qc.job', resource: 'FunkyName#perform'
15
+ )
16
+ subject
17
+ end
18
+ end
data/spec/helpers.rb CHANGED
@@ -4,6 +4,6 @@ module QcHelpers
4
4
  end
5
5
 
6
6
  def find_job(id)
7
- execute("SELECT * FROM #{QC::TABLE_NAME} WHERE id = $1", id)
7
+ execute("SELECT * FROM #{QC.table_name} WHERE id = $1", id)
8
8
  end
9
9
  end
@@ -1,12 +1,23 @@
1
1
  require 'spec_helper'
2
+ require 'active_record'
2
3
 
3
4
  describe QC do
4
-
5
5
  describe ".lock" do
6
+ context "with a connection from ActiveRecord that casts return types" do
7
+ before do
8
+ @old_conn_adapter = QC.default_conn_adapter
9
+ @activerecord_conn = ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"])
10
+ QC.default_conn_adapter = QC::ConnAdapter.new(
11
+ connection: ActiveRecord::Base.connection.raw_connection
12
+ )
13
+ end
6
14
 
7
- context "lock" do
15
+ after do
16
+ @activerecord_conn.disconnect!
17
+ QC.default_conn_adapter = @old_conn_adapter
18
+ end
8
19
 
9
- it "should lock the job with remaining_retries" do
20
+ it "locks the job with remaining_retries" do
10
21
  QC.enqueue_retry_in(1, "puts", 5, 2)
11
22
  sleep 1
12
23
  job = QC.lock
@@ -18,6 +29,15 @@ describe QC do
18
29
  end
19
30
  end
20
31
 
21
- end
32
+ it "locks the job with remaining_retries" do
33
+ QC.enqueue_retry_in(1, "puts", 5, 2)
34
+ sleep 1
35
+ job = QC.lock
22
36
 
37
+ expect(job[:q_name]).to eq("default")
38
+ expect(job[:method]).to eq("puts")
39
+ expect(job[:args][0]).to be(2)
40
+ expect(job[:remaining_retries]).to eq("5")
41
+ end
42
+ end
23
43
  end
data/spec/sample_jobs.rb CHANGED
@@ -14,7 +14,7 @@ module Jobs
14
14
  @queue = :low
15
15
  retry! on: SomeException, max: 5
16
16
 
17
- def self.perform should_raise
17
+ def self.perform(should_raise)
18
18
  raise SomeException if should_raise
19
19
  end
20
20
  end
@@ -27,7 +27,7 @@ module Jobs
27
27
 
28
28
  @queue = :low
29
29
 
30
- def self.perform should_raise
30
+ def self.perform(should_raise)
31
31
  raise Custom if should_raise
32
32
  end
33
33
  end
@@ -37,7 +37,7 @@ module Jobs
37
37
  @queue = :low
38
38
  retry! on: SomeException, max: 1
39
39
 
40
- def self.perform should_raise
40
+ def self.perform(should_raise)
41
41
  raise SomeException if should_raise
42
42
  end
43
43
  end
data/spec/spec_helper.rb CHANGED
@@ -4,8 +4,11 @@ require 'timecop'
4
4
  require 'queue_classic_matchers'
5
5
  require_relative './sample_jobs'
6
6
  require_relative './helpers'
7
+ require 'byebug'
7
8
  require 'pry'
9
+ require 'ddtrace'
8
10
 
11
+ ENV["QC_RAILS_DATABASE"] ||= "false" # test on QC::ConnAdapter by default
9
12
  ENV["DATABASE_URL"] ||= "postgres:///queue_classic_plus_test"
10
13
 
11
14
  RSpec.configure do |config|
@@ -20,5 +23,7 @@ RSpec.configure do |config|
20
23
 
21
24
  config.before(:each) do
22
25
  QC.default_conn_adapter.execute "TRUNCATE queue_classic_jobs;"
26
+ # Reset the default (memoized) queue instance between specs
27
+ QC.default_queue = nil
23
28
  end
24
29
  end
data/spec/worker_spec.rb CHANGED
@@ -9,22 +9,22 @@ describe QueueClassicPlus::CustomWorker do
9
9
 
10
10
  it "record failures in the failed queue" do
11
11
  queue.enqueue("Kerklfadsjflaksj", 1, 2, 3)
12
- failed_queue.count.should == 0
12
+ expect(failed_queue.count).to eq(0)
13
13
  worker.work
14
- failed_queue.count.should == 1
14
+ expect(failed_queue.count).to eq(1)
15
15
  job = failed_queue.lock
16
- job[:method].should == "Kerklfadsjflaksj"
17
- job[:args].should == [1, 2, 3]
16
+ expect(job[:method]).to eq("Kerklfadsjflaksj")
17
+ expect(job[:args]).to eq([1, 2, 3])
18
18
  full_job = find_job(job[:id])
19
19
 
20
- full_job['last_error'].should_not be_nil
20
+ expect(full_job['last_error']).to_not be_nil
21
21
  end
22
22
 
23
23
  it "records normal errors" do
24
24
  queue.enqueue("Jobs::Tests::TestJobNoRetry.perform", true)
25
- failed_queue.count.should == 0
25
+ expect(failed_queue.count).to eq(0)
26
26
  worker.work
27
- failed_queue.count.should == 1
27
+ expect(failed_queue.count).to eq(1)
28
28
  end
29
29
  end
30
30
 
@@ -42,28 +42,67 @@ describe QueueClassicPlus::CustomWorker do
42
42
  job_type.enqueue_perform(true)
43
43
  end.to change_queue_size_of(job_type).by(1)
44
44
 
45
- Jobs::Tests::LockedTestJob.should have_queue_size_of(1)
46
- failed_queue.count.should == 0
47
- QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries'].should be_nil
45
+ expect(Jobs::Tests::LockedTestJob).to have_queue_size_of(1)
46
+ expect(failed_queue.count).to eq(0)
47
+ expect(QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries']).to be_nil
48
48
 
49
- QueueClassicPlus::Metrics.should_receive(:increment).with('qc.retry', source: nil )
49
+ expect(QueueClassicPlus::Metrics).to receive(:increment).with('qc.retry', source: nil )
50
50
 
51
51
  Timecop.freeze do
52
52
  worker.work
53
53
 
54
- failed_queue.count.should == 0 # not enqueued on Failed
55
- QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries'].should eq "4"
56
- Jobs::Tests::LockedTestJob.should have_scheduled(true).at(Time.now + described_class::BACKOFF_WIDTH) # should have scheduled a retry for later
54
+ expect(failed_queue.count).to eq(0) # not enqueued on Failed
55
+ expect(QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries']).to eq "4"
56
+ expect(Jobs::Tests::LockedTestJob).to have_scheduled(true).at(Time.now + described_class::BACKOFF_WIDTH) # should have scheduled a retry for later
57
57
  end
58
58
 
59
59
  Timecop.freeze(Time.now + (described_class::BACKOFF_WIDTH * 2)) do
60
60
  # the job should be re-enqueued with a decremented retry count
61
61
  jobs = QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true])
62
- jobs.size.should == 1
62
+ expect(jobs.size).to eq(1)
63
63
  job = jobs.first
64
- job['remaining_retries'].to_i.should == job_type.max_retries - 1
65
- job['locked_by'].should be_nil
66
- job['locked_at'].should be_nil
64
+ expect(job['remaining_retries'].to_i).to eq(job_type.max_retries - 1)
65
+ expect(job['locked_by']).to be_nil
66
+ expect(job['locked_at']).to be_nil
67
+ end
68
+ end
69
+
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
67
106
  end
68
107
  end
69
108
 
@@ -71,11 +110,11 @@ describe QueueClassicPlus::CustomWorker do
71
110
  before { Jobs::Tests::ConnectionReapedTestJob.enqueue_perform }
72
111
 
73
112
  it 'retries' do
74
- QueueClassicPlus::Metrics.should_receive(:increment).with('qc.force_retry', source: nil )
113
+ expect(QueueClassicPlus::Metrics).to receive(:increment).with('qc.force_retry', source: nil )
75
114
  Timecop.freeze do
76
115
  worker.work
77
116
  expect(failed_queue.count).to eq 0
78
- QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::ConnectionReapedTestJob._perform', []).first['remaining_retries'].should eq "4"
117
+ expect(QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::ConnectionReapedTestJob._perform', []).first['remaining_retries']).to eq "4"
79
118
  end
80
119
  end
81
120
 
@@ -92,11 +131,11 @@ describe QueueClassicPlus::CustomWorker do
92
131
  before { Jobs::Tests::TestJob.enqueue_perform(true) }
93
132
 
94
133
  it 'retries' do
95
- QueueClassicPlus::Metrics.should_receive(:increment).with('qc.retry', source: nil )
134
+ expect(QueueClassicPlus::Metrics).to receive(:increment).with('qc.retry', source: nil )
96
135
  Timecop.freeze do
97
136
  worker.work
98
137
  expect(failed_queue.count).to eq 0
99
- QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::TestJob._perform', [true]).first['remaining_retries'].should eq "0"
138
+ expect(QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::TestJob._perform', [true]).first['remaining_retries']).to eq "0"
100
139
  end
101
140
  end
102
141
  end
@@ -126,16 +165,16 @@ describe QueueClassicPlus::CustomWorker do
126
165
  job_type.enqueue_perform(true)
127
166
  end.to change_queue_size_of(job_type).by(1)
128
167
 
129
- Jobs::Tests::LockedTestJob.should have_queue_size_of(1)
130
- failed_queue.count.should == 0
131
- QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries'].should be_nil
168
+ expect(Jobs::Tests::LockedTestJob).to have_queue_size_of(1)
169
+ expect(failed_queue.count).to eq(0)
170
+ expect(QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries']).to be_nil
132
171
 
133
172
  Timecop.freeze do
134
173
  worker.work
135
174
 
136
- QueueClassicMatchers::QueueClassicRspec.find_by_args('failed_jobs', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries'].should be_nil
137
- failed_queue.count.should == 1 # not enqueued on Failed
138
- Jobs::Tests::LockedTestJob.should_not have_scheduled(true).at(Time.now + described_class::BACKOFF_WIDTH) # should have scheduled a retry for later
175
+ expect(QueueClassicMatchers::QueueClassicRspec.find_by_args('failed_jobs', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries']).to be_nil
176
+ expect(failed_queue.count).to eq(1) # not enqueued on Failed
177
+ expect(Jobs::Tests::LockedTestJob).to_not have_scheduled(true).at(Time.now + described_class::BACKOFF_WIDTH) # should have scheduled a retry for later
139
178
  end
140
179
  end
141
180
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: queue_classic_plus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 4.0.0.alpha8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Mathieu
@@ -10,22 +10,22 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-07-19 00:00:00.000000000 Z
13
+ date: 2021-10-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: queue_classic
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - ">="
19
+ - - '='
20
20
  - !ruby/object:Gem::Version
21
- version: 3.1.0
21
+ version: 4.0.0.pre.alpha1
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
- - - ">="
26
+ - - '='
27
27
  - !ruby/object:Gem::Version
28
- version: 3.1.0
28
+ version: 4.0.0.pre.alpha1
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: bundler
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -54,6 +54,34 @@ dependencies:
54
54
  - - ">="
55
55
  - !ruby/object:Gem::Version
56
56
  version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: activerecord
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '6.0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: '6.0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: activejob
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
57
85
  description: ''
58
86
  email:
59
87
  - simon.math@gmail.com
@@ -63,10 +91,11 @@ executables: []
63
91
  extensions: []
64
92
  extra_rdoc_files: []
65
93
  files:
94
+ - ".circleci/config.yml"
95
+ - ".github/dependabot.yml"
66
96
  - ".gitignore"
67
97
  - ".rspec"
68
98
  - ".rvmrc"
69
- - ".travis.yml"
70
99
  - Gemfile
71
100
  - Guardfile
72
101
  - LICENSE.txt
@@ -74,6 +103,7 @@ files:
74
103
  - Rakefile
75
104
  - lib/queue_classic_plus.rb
76
105
  - lib/queue_classic_plus/base.rb
106
+ - lib/queue_classic_plus/datadog.rb
77
107
  - lib/queue_classic_plus/inflector.rb
78
108
  - lib/queue_classic_plus/inheritable_attr.rb
79
109
  - lib/queue_classic_plus/metrics.rb
@@ -89,6 +119,7 @@ files:
89
119
  - lib/rails/generators/qc_plus_job/templates/job_spec.rb.erb
90
120
  - queue_classic_plus.gemspec
91
121
  - spec/base_spec.rb
122
+ - spec/datadog_spec.rb
92
123
  - spec/helpers.rb
93
124
  - spec/inflector_spec.rb
94
125
  - spec/new_relic_spec.rb
@@ -112,17 +143,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
112
143
  version: '0'
113
144
  required_rubygems_version: !ruby/object:Gem::Requirement
114
145
  requirements:
115
- - - ">="
146
+ - - ">"
116
147
  - !ruby/object:Gem::Version
117
- version: '0'
148
+ version: 1.3.1
118
149
  requirements: []
119
- rubyforge_project:
120
- rubygems_version: 2.7.6
150
+ rubygems_version: 3.1.6
121
151
  signing_key:
122
152
  specification_version: 4
123
153
  summary: Useful extras for Queue Classic
124
154
  test_files:
125
155
  - spec/base_spec.rb
156
+ - spec/datadog_spec.rb
126
157
  - spec/helpers.rb
127
158
  - spec/inflector_spec.rb
128
159
  - spec/new_relic_spec.rb
data/.travis.yml DELETED
@@ -1,14 +0,0 @@
1
- language: ruby
2
- services:
3
- - postgresql
4
- before_install:
5
- - gem update bundler
6
- install: bundle install --without development
7
- before_script:
8
- - psql -c 'create database queue_classic_plus_test;' -U postgres
9
-
10
- rvm:
11
- - 2.0.0
12
- - 2.1.5
13
- - 2.2.0
14
- - 2.3.1