queue_classic_plus 1.1.0 → 4.0.0.alpha8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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