queue_classic 3.2.0.RC1 → 4.0.0.pre.alpha1
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 +5 -5
- data/.circleci/config.yml +151 -0
- data/.gitignore +2 -0
- data/{changelog → CHANGELOG.md} +80 -34
- data/CODE_OF_CONDUCT.md +46 -0
- data/Gemfile +8 -5
- data/README.md +77 -85
- data/Rakefile +2 -0
- data/lib/generators/queue_classic/install_generator.rb +6 -0
- data/lib/generators/queue_classic/templates/add_queue_classic.rb +3 -1
- data/lib/generators/queue_classic/templates/update_queue_classic_3_0_0.rb +3 -1
- data/lib/generators/queue_classic/templates/update_queue_classic_3_0_2.rb +3 -1
- data/lib/generators/queue_classic/templates/update_queue_classic_3_1_0.rb +3 -1
- data/lib/generators/queue_classic/templates/update_queue_classic_4_0_0.rb +11 -0
- data/lib/queue_classic.rb +4 -11
- data/lib/queue_classic/config.rb +2 -1
- data/lib/queue_classic/conn_adapter.rb +28 -14
- data/lib/queue_classic/queue.rb +65 -11
- data/lib/queue_classic/railtie.rb +2 -0
- data/lib/queue_classic/setup.rb +24 -7
- data/lib/queue_classic/tasks.rb +4 -5
- data/lib/queue_classic/version.rb +3 -1
- data/lib/queue_classic/worker.rb +10 -5
- data/queue_classic.gemspec +1 -1
- data/sql/create_table.sql +7 -16
- data/sql/ddl.sql +6 -82
- data/sql/downgrade_from_4_0_0.sql +88 -0
- data/sql/update_to_3_0_0.sql +5 -5
- data/sql/update_to_3_1_0.sql +6 -6
- data/sql/update_to_4_0_0.sql +6 -0
- data/test/benchmark_test.rb +2 -0
- data/test/config_test.rb +2 -0
- data/test/helper.rb +34 -0
- data/test/lib/queue_classic_rails_connection_test.rb +9 -6
- data/test/lib/queue_classic_test.rb +2 -0
- data/test/queue_test.rb +62 -2
- data/test/rails-tests/.gitignore +2 -0
- data/test/rails-tests/rails523.sh +23 -0
- data/test/worker_test.rb +138 -17
- metadata +15 -7
- data/.travis.yml +0 -15
@@ -0,0 +1,88 @@
|
|
1
|
+
DO $$DECLARE r record;
|
2
|
+
BEGIN
|
3
|
+
-- If jsonb type is available, do nothing as we're downgrading from 4.0.0
|
4
|
+
IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'jsonb') THEN
|
5
|
+
-- do nothing - it should already be already jsonb
|
6
|
+
-- Otherwise, use json type for the args column if available
|
7
|
+
ELSIF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'json') THEN
|
8
|
+
-- this should only happen if someone downgrades QC and their database < pg 9.4
|
9
|
+
ALTER TABLE queue_classic_jobs ALTER COLUMN args TYPE json USING args::json;
|
10
|
+
END IF;
|
11
|
+
|
12
|
+
|
13
|
+
END$$;
|
14
|
+
|
15
|
+
|
16
|
+
--
|
17
|
+
-- Re install the lock_head function
|
18
|
+
--
|
19
|
+
|
20
|
+
-- We are declaring the return type to be queue_classic_jobs.
|
21
|
+
-- This is ok since I am assuming that all of the users added queues will
|
22
|
+
-- have identical columns to queue_classic_jobs.
|
23
|
+
-- When QC supports queues with columns other than the default, we will have to change this.
|
24
|
+
|
25
|
+
CREATE OR REPLACE FUNCTION lock_head(q_name varchar, top_boundary integer)
|
26
|
+
RETURNS SETOF queue_classic_jobs AS $$
|
27
|
+
DECLARE
|
28
|
+
unlocked bigint;
|
29
|
+
relative_top integer;
|
30
|
+
job_count integer;
|
31
|
+
BEGIN
|
32
|
+
-- The purpose is to release contention for the first spot in the table.
|
33
|
+
-- The select count(*) is going to slow down dequeue performance but allow
|
34
|
+
-- for more workers. Would love to see some optimization here...
|
35
|
+
|
36
|
+
EXECUTE 'SELECT count(*) FROM '
|
37
|
+
|| '(SELECT * FROM queue_classic_jobs '
|
38
|
+
|| ' WHERE locked_at IS NULL'
|
39
|
+
|| ' AND q_name = '
|
40
|
+
|| quote_literal(q_name)
|
41
|
+
|| ' AND scheduled_at <= '
|
42
|
+
|| quote_literal(now())
|
43
|
+
|| ' LIMIT '
|
44
|
+
|| quote_literal(top_boundary)
|
45
|
+
|| ') limited'
|
46
|
+
INTO job_count;
|
47
|
+
|
48
|
+
SELECT TRUNC(random() * (top_boundary - 1))
|
49
|
+
INTO relative_top;
|
50
|
+
|
51
|
+
IF job_count < top_boundary THEN
|
52
|
+
relative_top = 0;
|
53
|
+
END IF;
|
54
|
+
|
55
|
+
LOOP
|
56
|
+
BEGIN
|
57
|
+
EXECUTE 'SELECT id FROM queue_classic_jobs '
|
58
|
+
|| ' WHERE locked_at IS NULL'
|
59
|
+
|| ' AND q_name = '
|
60
|
+
|| quote_literal(q_name)
|
61
|
+
|| ' AND scheduled_at <= '
|
62
|
+
|| quote_literal(now())
|
63
|
+
|| ' ORDER BY id ASC'
|
64
|
+
|| ' LIMIT 1'
|
65
|
+
|| ' OFFSET ' || quote_literal(relative_top)
|
66
|
+
|| ' FOR UPDATE NOWAIT'
|
67
|
+
INTO unlocked;
|
68
|
+
EXIT;
|
69
|
+
EXCEPTION
|
70
|
+
WHEN lock_not_available THEN
|
71
|
+
-- do nothing. loop again and hope we get a lock
|
72
|
+
END;
|
73
|
+
END LOOP;
|
74
|
+
|
75
|
+
RETURN QUERY EXECUTE 'UPDATE queue_classic_jobs '
|
76
|
+
|| ' SET locked_at = (CURRENT_TIMESTAMP),'
|
77
|
+
|| ' locked_by = (select pg_backend_pid())'
|
78
|
+
|| ' WHERE id = $1'
|
79
|
+
|| ' AND locked_at is NULL'
|
80
|
+
|| ' RETURNING *'
|
81
|
+
USING unlocked;
|
82
|
+
|
83
|
+
RETURN;
|
84
|
+
END $$ LANGUAGE plpgsql;
|
85
|
+
|
86
|
+
CREATE OR REPLACE FUNCTION lock_head(tname varchar) RETURNS SETOF queue_classic_jobs AS $$ BEGIN
|
87
|
+
RETURN QUERY EXECUTE 'SELECT * FROM lock_head($1,10)' USING tname;
|
88
|
+
END $$ LANGUAGE plpgsql;
|
data/sql/update_to_3_0_0.sql
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
DO $$DECLARE r record;
|
2
2
|
BEGIN
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
BEGIN
|
4
|
+
ALTER TABLE queue_classic_jobs ADD COLUMN created_at timestamptz DEFAULT now();
|
5
|
+
EXCEPTION
|
6
|
+
WHEN duplicate_column THEN RAISE NOTICE 'column created_at already exists in queue_classic_jobs.';
|
7
|
+
END;
|
8
8
|
END$$;
|
9
9
|
|
10
10
|
DO $$DECLARE r record;
|
data/sql/update_to_3_1_0.sql
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
DO $$DECLARE r record;
|
2
2
|
BEGIN
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
BEGIN
|
4
|
+
ALTER TABLE queue_classic_jobs ADD COLUMN scheduled_at timestamptz DEFAULT now();
|
5
|
+
CREATE INDEX idx_qc_on_scheduled_at_only_unlocked ON queue_classic_jobs (scheduled_at, id) WHERE locked_at IS NULL;
|
6
|
+
EXCEPTION
|
7
|
+
WHEN duplicate_column THEN RAISE NOTICE 'column scheduled_at already exists in queue_classic_jobs.';
|
8
|
+
END;
|
9
9
|
END$$;
|
data/test/benchmark_test.rb
CHANGED
data/test/config_test.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -1,6 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "bundler"
|
4
|
+
require "minitest/reporters"
|
5
|
+
|
2
6
|
Bundler.setup :default, :test
|
3
7
|
|
8
|
+
if ENV['CIRCLECI'] == "true"
|
9
|
+
Minitest::Reporters.use! Minitest::Reporters::JUnitReporter.new
|
10
|
+
else
|
11
|
+
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
12
|
+
end
|
13
|
+
|
4
14
|
ENV["DATABASE_URL"] ||= "postgres:///queue_classic_test"
|
5
15
|
|
6
16
|
require_relative '../lib/queue_classic'
|
@@ -58,4 +68,28 @@ class QCTest < Minitest::Test
|
|
58
68
|
ensure
|
59
69
|
original_environment.each { |name, value| ENV[name] = value }
|
60
70
|
end
|
71
|
+
|
72
|
+
def stub_any_instance(class_name, method_name, definition)
|
73
|
+
new_method_name = "new_#{method_name}"
|
74
|
+
original_method_name = "original_#{method_name}"
|
75
|
+
|
76
|
+
method_present = class_name.instance_methods(false).include? method_name
|
77
|
+
|
78
|
+
if method_present
|
79
|
+
class_name.send(:alias_method, original_method_name, method_name)
|
80
|
+
class_name.send(:define_method, new_method_name, definition)
|
81
|
+
class_name.send(:alias_method, method_name, new_method_name)
|
82
|
+
|
83
|
+
yield
|
84
|
+
else
|
85
|
+
message = "#{class_name} does not have method #{method_name}."
|
86
|
+
message << "\nAvailable methods: #{class_name.instance_methods(false)}"
|
87
|
+
raise ArgumentError.new message
|
88
|
+
end
|
89
|
+
ensure
|
90
|
+
if method_present
|
91
|
+
class_name.send(:alias_method, method_name, original_method_name)
|
92
|
+
class_name.send(:undef_method, new_method_name)
|
93
|
+
end
|
94
|
+
end
|
61
95
|
end
|
@@ -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
|
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
|
-
|
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
|
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(
|
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(
|
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,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
|
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
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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)
|
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
|
|