queue_classic 3.2.0.RC1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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