queue_classic 3.2.0.RC1 → 4.0.0

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.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/codeql-analysis.yml +72 -0
  3. data/.github/workflows/main.yaml +71 -0
  4. data/.gitignore +2 -0
  5. data/{changelog → CHANGELOG.md} +87 -34
  6. data/CODE_OF_CONDUCT.md +46 -0
  7. data/Gemfile +9 -5
  8. data/README.md +103 -126
  9. data/Rakefile +2 -0
  10. data/lib/generators/queue_classic/install_generator.rb +6 -0
  11. data/lib/generators/queue_classic/templates/add_queue_classic.rb +3 -1
  12. data/lib/generators/queue_classic/templates/update_queue_classic_3_0_0.rb +3 -1
  13. data/lib/generators/queue_classic/templates/update_queue_classic_3_0_2.rb +3 -1
  14. data/lib/generators/queue_classic/templates/update_queue_classic_3_1_0.rb +3 -1
  15. data/lib/generators/queue_classic/templates/update_queue_classic_4_0_0.rb +11 -0
  16. data/lib/queue_classic/config.rb +2 -1
  17. data/lib/queue_classic/conn_adapter.rb +29 -15
  18. data/lib/queue_classic/queue.rb +66 -12
  19. data/lib/queue_classic/railtie.rb +2 -0
  20. data/lib/queue_classic/setup.rb +24 -7
  21. data/lib/queue_classic/tasks.rb +4 -5
  22. data/lib/queue_classic/version.rb +3 -1
  23. data/lib/queue_classic/worker.rb +15 -6
  24. data/lib/queue_classic.rb +4 -11
  25. data/queue_classic.gemspec +3 -2
  26. data/sql/create_table.sql +7 -16
  27. data/sql/ddl.sql +6 -82
  28. data/sql/downgrade_from_4_0_0.sql +88 -0
  29. data/sql/update_to_3_0_0.sql +5 -5
  30. data/sql/update_to_3_1_0.sql +6 -6
  31. data/sql/update_to_4_0_0.sql +6 -0
  32. data/test/benchmark_test.rb +2 -0
  33. data/test/config_test.rb +2 -0
  34. data/test/helper.rb +34 -0
  35. data/test/lib/queue_classic_rails_connection_test.rb +9 -6
  36. data/test/lib/queue_classic_test.rb +2 -0
  37. data/test/lib/queue_classic_test_with_activerecord_typecast.rb +21 -0
  38. data/test/queue_test.rb +62 -2
  39. data/test/rails-tests/.gitignore +2 -0
  40. data/test/rails-tests/rails523.sh +23 -0
  41. data/test/worker_test.rb +138 -17
  42. metadata +43 -13
  43. data/.travis.yml +0 -15
@@ -1,15 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path("../../helper.rb", __FILE__)
2
4
 
3
5
  class QueueClassicRailsConnectionTest < QCTest
4
6
  def before_setup
5
- Object.send :const_set, :ActiveRecord, Module.new
6
- ActiveRecord.const_set :Base, Module.new
7
-
8
7
  @original_conn_adapter = QC.default_conn_adapter
9
8
  QC.default_conn_adapter = nil
10
9
  end
11
10
 
12
- def after_teardown
11
+ def before_teardown
13
12
  ActiveRecord.send :remove_const, :Base
14
13
  Object.send :remove_const, :ActiveRecord
15
14
 
@@ -18,12 +17,14 @@ class QueueClassicRailsConnectionTest < QCTest
18
17
 
19
18
  def test_uses_active_record_connection_if_exists
20
19
  connection = get_connection
21
- assert connection.verify
20
+ QC.default_conn_adapter.execute('SELECT 1;')
21
+ connection.verify
22
22
  end
23
23
 
24
24
  def test_does_not_use_active_record_connection_if_env_var_set
25
25
  with_env 'QC_RAILS_DATABASE' => 'false' do
26
26
  connection = get_connection
27
+ QC.default_conn_adapter.execute('SELECT 1;')
27
28
  assert_raises(MockExpectationError) { connection.verify }
28
29
  end
29
30
  end
@@ -31,8 +32,10 @@ class QueueClassicRailsConnectionTest < QCTest
31
32
  private
32
33
  def get_connection
33
34
  connection = Minitest::Mock.new
34
- connection.expect(:raw_connection, QC::ConnAdapter.new.connection)
35
+ connection.expect(:raw_connection, QC::ConnAdapter.new(active_record_connection_share: true).connection)
35
36
 
37
+ Object.send :const_set, :ActiveRecord, Module.new
38
+ ActiveRecord.const_set :Base, Module.new
36
39
  ActiveRecord::Base.define_singleton_method(:connection) do
37
40
  connection
38
41
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path("../../helper.rb", __FILE__)
2
4
 
3
5
  class QueueClassicTest < QCTest
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("../../helper.rb", __FILE__)
4
+
5
+ class QueueClassicTest < QCTest
6
+ def before_teardown
7
+ ActiveRecord.send :remove_const, :Base
8
+ Object.send :remove_const, :ActiveRecord
9
+
10
+ QC.default_conn_adapter = @original_conn_adapter
11
+ end
12
+
13
+ def test_lock_with_active_record_timestamp_type_cast
14
+ # Insert an unlocked job
15
+ p_queue = QC::Queue.new("priority_queue")
16
+ conn_adapter = Minitest::Mock.new
17
+ conn_adapter.expect(:execute, {"id" => '1', "q_name" => 'test', "method" => "Kernel.puts", "args" => "[]", "scheduled_at" => Time.now}, [String, String])
18
+ QC.default_conn_adapter = conn_adapter
19
+ assert_equal(p_queue.lock, {})
20
+ end
21
+ end
data/test/queue_test.rb CHANGED
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'helper'
2
4
 
3
5
  class QueueTest < QCTest
4
6
 
5
- ResetError = Class.new(PGError)
7
+ ResetError = Class.new(PG::Error)
6
8
 
7
9
  def test_enqueue
8
10
  QC.enqueue("Klass.method")
@@ -62,6 +64,20 @@ class QueueTest < QCTest
62
64
  def test_count
63
65
  QC.enqueue("Klass.method")
64
66
  assert_equal(1, QC.count)
67
+
68
+ QC.enqueue("Klass.method")
69
+ assert_equal(2, QC.count)
70
+ assert_equal(2, QC.count_ready)
71
+ assert_equal(0, QC.count_scheduled)
72
+
73
+ QC.enqueue_in(60, "Klass.method")
74
+ assert_equal(3, QC.count)
75
+ assert_equal(2, QC.count_ready)
76
+ assert_equal(1, QC.count_scheduled)
77
+
78
+ assert_raises(ArgumentError) do
79
+ QC.count(:potatoes)
80
+ end
65
81
  end
66
82
 
67
83
  def test_delete
@@ -105,13 +121,57 @@ class QueueTest < QCTest
105
121
  queue.enqueue("Klass.method")
106
122
  assert_equal(1, queue.count)
107
123
  conn = queue.conn_adapter.connection
108
- def conn.exec(*args); raise(PGError); end
124
+ def conn.exec(*args); raise(PG::Error); end
109
125
  def conn.reset(*args); raise(ResetError) end
110
126
  # We ensure that the reset method is called on the connection.
111
127
  assert_raises(PG::Error, ResetError) {queue.enqueue("Klass.other_method")}
112
128
  queue.conn_adapter.disconnect
113
129
  end
114
130
 
131
+ def test_enqueue_retry
132
+ queue = QC::Queue.new("queue_classic_jobs")
133
+ queue.conn_adapter = QC::ConnAdapter.new
134
+ conn = queue.conn_adapter.connection
135
+ conn.exec('select pg_terminate_backend(pg_backend_pid())') rescue nil
136
+ queue.enqueue("Klass.method")
137
+ assert_equal(1, queue.count)
138
+ queue.conn_adapter.disconnect
139
+ end
140
+
141
+ def test_enqueue_stops_retrying_on_permanent_error
142
+ queue = QC::Queue.new("queue_classic_jobs")
143
+ queue.conn_adapter = QC::ConnAdapter.new
144
+ conn = queue.conn_adapter.connection
145
+ conn.exec('select pg_terminate_backend(pg_backend_pid())') rescue nil
146
+ # Simulate permanent connection error
147
+ def conn.exec(*args); raise(PG::Error); end
148
+ # Ensure that the error is reraised on second time
149
+ assert_raises(PG::Error) {queue.enqueue("Klass.other_method")}
150
+ queue.conn_adapter.disconnect
151
+ end
152
+
153
+ def test_enqueue_in_retry
154
+ queue = QC::Queue.new("queue_classic_jobs")
155
+ queue.conn_adapter = QC::ConnAdapter.new
156
+ conn = queue.conn_adapter.connection
157
+ conn.exec('select pg_terminate_backend(pg_backend_pid())') rescue nil
158
+ queue.enqueue_in(10,"Klass.method")
159
+ assert_equal(1, queue.count)
160
+ queue.conn_adapter.disconnect
161
+ end
162
+
163
+ def test_enqueue_in_stops_retrying_on_permanent_error
164
+ queue = QC::Queue.new("queue_classic_jobs")
165
+ queue.conn_adapter = QC::ConnAdapter.new
166
+ conn = queue.conn_adapter.connection
167
+ conn.exec('select pg_terminate_backend(pg_backend_pid())') rescue nil
168
+ # Simulate permanent connection error
169
+ def conn.exec(*args); raise(PG::Error); end
170
+ # Ensure that the error is reraised on second time
171
+ assert_raises(PG::Error) {queue.enqueue_in(10,"Klass.method")}
172
+ queue.conn_adapter.disconnect
173
+ end
174
+
115
175
  def test_custom_default_queue
116
176
  queue_class = Class.new do
117
177
  attr_accessor :jobs
@@ -0,0 +1,2 @@
1
+ vendor/
2
+ qctest*/
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # remove any old folder, should only matter locally
5
+ rm -rf qctest523
6
+
7
+ # install rails but not with much stuff
8
+ gem install rails -v 5.2.3
9
+ rails new qctest523 --api --database=postgresql --skip-test-unit --skip-keeps --skip-spring --skip-sprockets --skip-javascript --skip-turbolinks
10
+ cd qctest523
11
+
12
+ # get the db setup, run any default migrations
13
+ bundle install
14
+ bundle exec rails db:drop:all || true
15
+ bundle exec rails db:create
16
+ bundle exec rails db:migrate
17
+ bundle exec rails db:setup
18
+
19
+ # install qc --> gem file, bundle, add ourselves and migrate.
20
+ echo "gem 'queue_classic', path: '../../../'" >> Gemfile
21
+ bundle install
22
+ bundle exec rails generate queue_classic:install
23
+ bundle exec rails db:migrate
data/test/worker_test.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'helper'
2
4
 
3
5
  module TestObject
@@ -121,16 +123,16 @@ class WorkerTest < QCTest
121
123
  t.join
122
124
  end
123
125
 
124
- def test_worker_uses_one_conn
125
- skip "This test is broken and needs to be fixed."
126
-
126
+ def test_worker_reuses_conn
127
127
  QC.enqueue("TestObject.no_args")
128
+ count = QC.default_conn_adapter.execute("SELECT count(*) from pg_stat_activity where datname = current_database()")["count"].to_i;
128
129
  worker = TestWorker.new
129
130
  worker.work
130
- assert_equal(
131
- 1,
132
- QC.default_conn_adapter.execute("SELECT count(*) from pg_stat_activity where datname = current_database()")["count"].to_i,
133
- "Multiple connections found -- are there open connections to #{ QC.default_conn_adapter.send(:db_url) } in other terminals?"
131
+
132
+ new_count = QC.default_conn_adapter.execute("SELECT count(*) from pg_stat_activity where datname = current_database()")["count"].to_i;
133
+ assert(
134
+ new_count == count,
135
+ "Worker should not initialize new connections to #{ QC.default_conn_adapter.send(:db_url) }."
134
136
  )
135
137
  end
136
138
 
@@ -176,15 +178,6 @@ class WorkerTest < QCTest
176
178
  assert_equal(0, worker.failed_count)
177
179
  end
178
180
 
179
- def test_init_worker_with_arg
180
- with_database 'postgres:///invalid' do
181
- conn = PG::Connection.connect(dbname: 'queue_classic_test')
182
- QC::Worker.new connection: conn
183
-
184
- conn.close
185
- end
186
- end
187
-
188
181
  def test_init_worker_with_database_url
189
182
  with_database ENV['DATABASE_URL'] || ENV['QC_DATABASE_URL'] do
190
183
  worker = QC::Worker.new
@@ -197,7 +190,135 @@ class WorkerTest < QCTest
197
190
 
198
191
  def test_init_worker_without_conn
199
192
  with_database nil do
200
- assert_raises(ArgumentError) { QC::Worker.new }
193
+ assert_raises(ArgumentError) do
194
+ worker = QC::Worker.new
195
+ QC.enqueue("TestObject.no_args")
196
+ worker.lock_job
197
+ end
198
+ end
199
+ end
200
+
201
+ def test_worker_unlocks_job_on_signal_exception
202
+ job_details = QC.enqueue("Kernel.eval", "raise SignalException.new('INT')")
203
+ worker = TestWorker.new
204
+
205
+ unlocked = nil
206
+
207
+ fake_unlock = Proc.new do |job_id|
208
+ if job_id == job_details['id']
209
+ unlocked = true
210
+ end
211
+ original_unlock(job_id)
212
+ end
213
+
214
+ stub_any_instance(QC::Queue, :unlock, fake_unlock) do
215
+ begin
216
+ worker.work
217
+ rescue SignalException
218
+ ensure
219
+ assert unlocked, "SignalException failed to unlock the job in the queue."
220
+ end
221
+ end
222
+ end
223
+
224
+ def test_worker_unlocks_job_on_system_exit
225
+ job_details = QC.enqueue("Kernel.eval", "raise SystemExit.new")
226
+ worker = TestWorker.new
227
+
228
+ unlocked = nil
229
+
230
+ fake_unlock = Proc.new do |job_id|
231
+ if job_id == job_details['id']
232
+ unlocked = true
233
+ end
234
+ original_unlock(job_id)
235
+ end
236
+
237
+ stub_any_instance(QC::Queue, :unlock, fake_unlock) do
238
+ begin
239
+ worker.work
240
+ rescue SystemExit
241
+ ensure
242
+ assert unlocked, "SystemExit failed to unlock the job in the queue."
243
+ end
244
+ end
245
+ end
246
+
247
+ def test_worker_does_not_unlock_jobs_on_syntax_error
248
+ job_details = QC.enqueue("Kernel.eval", "bad syntax")
249
+ worker = TestWorker.new
250
+
251
+ unlocked = nil
252
+
253
+ fake_unlock = Proc.new do |job_id|
254
+ if job_id == job_details['id']
255
+ unlocked = true
256
+ end
257
+ original_unlock(job_id)
258
+ end
259
+
260
+ stub_any_instance(QC::Queue, :unlock, fake_unlock) do
261
+ begin
262
+ errors = capture_stderr_output do
263
+ worker.work
264
+ end
265
+ ensure
266
+ message = ["SyntaxError unexpectedly unlocked the job in the queue."]
267
+ message << "Errors:\n#{errors}" unless errors.empty?
268
+ refute unlocked, message.join("\n")
269
+ end
270
+ end
271
+ end
272
+
273
+ def test_worker_does_not_unlock_jobs_on_load_error
274
+ job_details = QC.enqueue("Kernel.eval", "require 'not_a_real_file'")
275
+ worker = TestWorker.new
276
+
277
+ unlocked = nil
278
+
279
+ fake_unlock = Proc.new do |job_id|
280
+ if job_id == job_details['id']
281
+ unlocked = true
282
+ end
283
+ original_unlock(job_id)
284
+ end
285
+
286
+ stub_any_instance(QC::Queue, :unlock, fake_unlock) do
287
+ begin
288
+ errors = capture_stderr_output do
289
+ worker.work
290
+ end
291
+ ensure
292
+ message = ["LoadError unexpectedly unlocked the job in the queue."]
293
+ message << "Errors:\n#{errors}" unless errors.empty?
294
+ refute unlocked, message.join("\n")
295
+ end
296
+ end
297
+ end
298
+
299
+ def test_worker_does_not_unlock_jobs_on_no_memory_error
300
+ job_details = QC.enqueue("Kernel.eval", "raise NoMemoryError.new")
301
+ worker = TestWorker.new
302
+
303
+ unlocked = nil
304
+
305
+ fake_unlock = Proc.new do |job_id|
306
+ if job_id == job_details['id']
307
+ unlocked = true
308
+ end
309
+ original_unlock(job_id)
310
+ end
311
+
312
+ stub_any_instance(QC::Queue, :unlock, fake_unlock) do
313
+ begin
314
+ errors = capture_stderr_output do
315
+ worker.work
316
+ end
317
+ ensure
318
+ message = ["NoMemoryError unexpectedly unlocked the job in the queue."]
319
+ message << "Errors:\n#{errors}" unless errors.empty?
320
+ refute unlocked, message.join("\n")
321
+ end
201
322
  end
202
323
  end
203
324
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: queue_classic
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0.RC1
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Smith (♠ ace hacker)
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-15 00:00:00.000000000 Z
11
+ date: 2022-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -16,20 +16,40 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0.17'
19
+ version: '1.1'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '0.19'
22
+ version: '2.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '0.17'
29
+ version: '1.1'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '0.19'
32
+ version: '2.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: activerecord
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 5.0.0
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '6.1'
43
+ type: :development
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 5.0.0
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '6.1'
33
53
  description: queue_classic is a queueing library for Ruby apps. (Rails, Sinatra, Etc...)
34
54
  queue_classic features asynchronous job polling, database maintained locks and no
35
55
  ridiculous dependencies. As a matter of fact, queue_classic only requires pg.
@@ -38,19 +58,22 @@ executables: []
38
58
  extensions: []
39
59
  extra_rdoc_files: []
40
60
  files:
61
+ - ".github/workflows/codeql-analysis.yml"
62
+ - ".github/workflows/main.yaml"
41
63
  - ".gitignore"
42
- - ".travis.yml"
64
+ - CHANGELOG.md
65
+ - CODE_OF_CONDUCT.md
43
66
  - CONTRIBUTING.md
44
67
  - Gemfile
45
68
  - LICENSE.txt
46
69
  - README.md
47
70
  - Rakefile
48
- - changelog
49
71
  - lib/generators/queue_classic/install_generator.rb
50
72
  - lib/generators/queue_classic/templates/add_queue_classic.rb
51
73
  - lib/generators/queue_classic/templates/update_queue_classic_3_0_0.rb
52
74
  - lib/generators/queue_classic/templates/update_queue_classic_3_0_2.rb
53
75
  - lib/generators/queue_classic/templates/update_queue_classic_3_1_0.rb
76
+ - lib/generators/queue_classic/templates/update_queue_classic_4_0_0.rb
54
77
  - lib/queue_classic.rb
55
78
  - lib/queue_classic/config.rb
56
79
  - lib/queue_classic/conn_adapter.rb
@@ -65,18 +88,23 @@ files:
65
88
  - sql/ddl.sql
66
89
  - sql/downgrade_from_3_0_0.sql
67
90
  - sql/downgrade_from_3_1_0.sql
91
+ - sql/downgrade_from_4_0_0.sql
68
92
  - sql/drop_ddl.sql
69
93
  - sql/update_to_3_0_0.sql
70
94
  - sql/update_to_3_1_0.sql
95
+ - sql/update_to_4_0_0.sql
71
96
  - test/benchmark_test.rb
72
97
  - test/config_test.rb
73
98
  - test/helper.rb
74
99
  - test/helper.sql
75
100
  - test/lib/queue_classic_rails_connection_test.rb
76
101
  - test/lib/queue_classic_test.rb
102
+ - test/lib/queue_classic_test_with_activerecord_typecast.rb
77
103
  - test/queue_test.rb
104
+ - test/rails-tests/.gitignore
105
+ - test/rails-tests/rails523.sh
78
106
  - test/worker_test.rb
79
- homepage: http://github.com/QueueClassic/queue_classic
107
+ homepage: https://github.com/QueueClassic/queue_classic
80
108
  licenses:
81
109
  - MIT
82
110
  metadata: {}
@@ -91,12 +119,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
91
119
  version: '0'
92
120
  required_rubygems_version: !ruby/object:Gem::Requirement
93
121
  requirements:
94
- - - ">"
122
+ - - ">="
95
123
  - !ruby/object:Gem::Version
96
- version: 1.3.1
124
+ version: '0'
97
125
  requirements: []
98
- rubyforge_project:
99
- rubygems_version: 2.4.5.1
126
+ rubygems_version: 3.1.6
100
127
  signing_key:
101
128
  specification_version: 4
102
129
  summary: Simple, efficient worker queue for Ruby & PostgreSQL.
@@ -107,5 +134,8 @@ test_files:
107
134
  - test/helper.sql
108
135
  - test/lib/queue_classic_rails_connection_test.rb
109
136
  - test/lib/queue_classic_test.rb
137
+ - test/lib/queue_classic_test_with_activerecord_typecast.rb
110
138
  - test/queue_test.rb
139
+ - test/rails-tests/.gitignore
140
+ - test/rails-tests/rails523.sh
111
141
  - test/worker_test.rb
data/.travis.yml DELETED
@@ -1,15 +0,0 @@
1
- language: ruby
2
- before_script:
3
- - psql -c 'create database queue_classic_test;' -U postgres
4
- env:
5
- global:
6
- - QC_DATABASE_URL="postgres://postgres@localhost/queue_classic_test"
7
- - QC_BENCHMARK=true
8
- - QC_BENCHMARK_MAX_TIME_DEQUEUE=60
9
- rvm:
10
- - 2.2
11
- - 2.1
12
- - 2.0.0
13
- - 1.9.3
14
- addons:
15
- postgresql: 9.3