que 0.11.3 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/tests.yml +51 -0
- data/.gitignore +2 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +502 -97
- data/Dockerfile +20 -0
- data/LICENSE.txt +1 -1
- data/README.md +205 -59
- data/auto/dev +21 -0
- data/auto/pre-push-hook +30 -0
- data/auto/psql +9 -0
- data/auto/test +5 -0
- data/auto/test-postgres-14 +17 -0
- data/bin/que +8 -81
- data/docker-compose.yml +47 -0
- data/docs/README.md +881 -0
- data/lib/que/active_job/extensions.rb +114 -0
- data/lib/que/active_record/connection.rb +51 -0
- data/lib/que/active_record/model.rb +48 -0
- data/lib/que/command_line_interface.rb +259 -0
- data/lib/que/connection.rb +198 -0
- data/lib/que/connection_pool.rb +78 -0
- data/lib/que/job.rb +210 -103
- data/lib/que/job_buffer.rb +255 -0
- data/lib/que/job_methods.rb +176 -0
- data/lib/que/listener.rb +176 -0
- data/lib/que/locker.rb +507 -0
- data/lib/que/metajob.rb +47 -0
- data/lib/que/migrations/4/down.sql +48 -0
- data/lib/que/migrations/4/up.sql +267 -0
- data/lib/que/migrations/5/down.sql +73 -0
- data/lib/que/migrations/5/up.sql +76 -0
- data/lib/que/migrations/6/down.sql +8 -0
- data/lib/que/migrations/6/up.sql +8 -0
- data/lib/que/migrations/7/down.sql +5 -0
- data/lib/que/migrations/7/up.sql +13 -0
- data/lib/que/migrations.rb +37 -18
- data/lib/que/poller.rb +274 -0
- data/lib/que/rails/railtie.rb +12 -0
- data/lib/que/result_queue.rb +35 -0
- data/lib/que/sequel/model.rb +52 -0
- data/lib/que/utils/assertions.rb +62 -0
- data/lib/que/utils/constantization.rb +19 -0
- data/lib/que/utils/error_notification.rb +68 -0
- data/lib/que/utils/freeze.rb +20 -0
- data/lib/que/utils/introspection.rb +50 -0
- data/lib/que/utils/json_serialization.rb +21 -0
- data/lib/que/utils/logging.rb +79 -0
- data/lib/que/utils/middleware.rb +46 -0
- data/lib/que/utils/queue_management.rb +18 -0
- data/lib/que/utils/ruby2_keywords.rb +19 -0
- data/lib/que/utils/transactions.rb +34 -0
- data/lib/que/version.rb +5 -1
- data/lib/que/worker.rb +145 -149
- data/lib/que.rb +103 -159
- data/que.gemspec +17 -4
- data/scripts/docker-entrypoint +14 -0
- data/scripts/test +6 -0
- metadata +59 -95
- data/.rspec +0 -2
- data/.travis.yml +0 -17
- data/Gemfile +0 -24
- data/docs/advanced_setup.md +0 -106
- data/docs/customizing_que.md +0 -200
- data/docs/error_handling.md +0 -47
- data/docs/inspecting_the_queue.md +0 -114
- data/docs/logging.md +0 -50
- data/docs/managing_workers.md +0 -80
- data/docs/migrating.md +0 -30
- data/docs/multiple_queues.md +0 -27
- data/docs/shutting_down_safely.md +0 -7
- data/docs/using_plain_connections.md +0 -41
- data/docs/using_sequel.md +0 -31
- data/docs/writing_reliable_jobs.md +0 -117
- data/lib/generators/que/install_generator.rb +0 -24
- data/lib/generators/que/templates/add_que.rb +0 -13
- data/lib/que/adapters/active_record.rb +0 -54
- data/lib/que/adapters/base.rb +0 -127
- data/lib/que/adapters/connection_pool.rb +0 -16
- data/lib/que/adapters/pg.rb +0 -21
- data/lib/que/adapters/pond.rb +0 -16
- data/lib/que/adapters/sequel.rb +0 -20
- data/lib/que/railtie.rb +0 -16
- data/lib/que/rake_tasks.rb +0 -59
- data/lib/que/sql.rb +0 -152
- data/spec/adapters/active_record_spec.rb +0 -152
- data/spec/adapters/connection_pool_spec.rb +0 -22
- data/spec/adapters/pg_spec.rb +0 -41
- data/spec/adapters/pond_spec.rb +0 -22
- data/spec/adapters/sequel_spec.rb +0 -57
- data/spec/gemfiles/Gemfile1 +0 -18
- data/spec/gemfiles/Gemfile2 +0 -18
- data/spec/spec_helper.rb +0 -118
- data/spec/support/helpers.rb +0 -19
- data/spec/support/jobs.rb +0 -35
- data/spec/support/shared_examples/adapter.rb +0 -37
- data/spec/support/shared_examples/multi_threaded_adapter.rb +0 -46
- data/spec/travis.rb +0 -23
- data/spec/unit/connection_spec.rb +0 -14
- data/spec/unit/customization_spec.rb +0 -251
- data/spec/unit/enqueue_spec.rb +0 -245
- data/spec/unit/helper_spec.rb +0 -12
- data/spec/unit/logging_spec.rb +0 -101
- data/spec/unit/migrations_spec.rb +0 -84
- data/spec/unit/pool_spec.rb +0 -365
- data/spec/unit/run_spec.rb +0 -14
- data/spec/unit/states_spec.rb +0 -50
- data/spec/unit/stats_spec.rb +0 -46
- data/spec/unit/transaction_spec.rb +0 -36
- data/spec/unit/work_spec.rb +0 -407
- data/spec/unit/worker_spec.rb +0 -167
- data/tasks/benchmark.rb +0 -3
- data/tasks/rspec.rb +0 -14
- data/tasks/safe_shutdown.rb +0 -67
data/lib/que/sql.rb
DELETED
@@ -1,152 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Que
|
4
|
-
SQL = {
|
5
|
-
# Locks a job using a Postgres recursive CTE [1].
|
6
|
-
#
|
7
|
-
# As noted by the Postgres documentation, it may be slightly easier to
|
8
|
-
# think about this expression as iteration rather than recursion, despite
|
9
|
-
# the `RECURSION` nomenclature defined by the SQL standards committee.
|
10
|
-
# Recursion is used here so that jobs in the table can be iterated one-by-
|
11
|
-
# one until a lock can be acquired, where a non-recursive `SELECT` would
|
12
|
-
# have the undesirable side-effect of locking multiple jobs at once. i.e.
|
13
|
-
# Consider that the following would have the worker lock *all* unlocked
|
14
|
-
# jobs:
|
15
|
-
#
|
16
|
-
# SELECT (j).*, pg_try_advisory_lock((j).job_id) AS locked
|
17
|
-
# FROM que_jobs AS j;
|
18
|
-
#
|
19
|
-
# The CTE will initially produce an "anchor" from the non-recursive term
|
20
|
-
# (i.e. before the `UNION`), and then use it as the contents of the
|
21
|
-
# working table as it continues to iterate through `que_jobs` looking for
|
22
|
-
# a lock. The jobs table has a sort on (priority, run_at, job_id) which
|
23
|
-
# allows it to walk the jobs table in a stable manner. As noted above, the
|
24
|
-
# recursion examines one job at a time so that it only ever acquires a
|
25
|
-
# single lock.
|
26
|
-
#
|
27
|
-
# The recursion has two possible end conditions:
|
28
|
-
#
|
29
|
-
# 1. If a lock *can* be acquired, it bubbles up to the top-level `SELECT`
|
30
|
-
# outside of the `job` CTE which stops recursion because it is
|
31
|
-
# constrained with a `LIMIT` of 1.
|
32
|
-
#
|
33
|
-
# 2. If a lock *cannot* be acquired, the recursive term of the expression
|
34
|
-
# (i.e. what's after the `UNION`) will return an empty result set
|
35
|
-
# because there are no more candidates left that could possibly be
|
36
|
-
# locked. This empty result automatically ends recursion.
|
37
|
-
#
|
38
|
-
# Note that this query can be easily modified to lock any number of jobs
|
39
|
-
# by tweaking the LIMIT clause in the main SELECT statement.
|
40
|
-
#
|
41
|
-
# [1] http://www.postgresql.org/docs/devel/static/queries-with.html
|
42
|
-
#
|
43
|
-
# Thanks to RhodiumToad in #postgresql for help with the original version
|
44
|
-
# of the job lock CTE.
|
45
|
-
:lock_job => %{
|
46
|
-
WITH RECURSIVE jobs AS (
|
47
|
-
SELECT (j).*, pg_try_advisory_lock((j).job_id) AS locked
|
48
|
-
FROM (
|
49
|
-
SELECT j
|
50
|
-
FROM que_jobs AS j
|
51
|
-
WHERE queue = $1::text
|
52
|
-
AND run_at <= now()
|
53
|
-
ORDER BY priority, run_at, job_id
|
54
|
-
LIMIT 1
|
55
|
-
) AS t1
|
56
|
-
UNION ALL (
|
57
|
-
SELECT (j).*, pg_try_advisory_lock((j).job_id) AS locked
|
58
|
-
FROM (
|
59
|
-
SELECT (
|
60
|
-
SELECT j
|
61
|
-
FROM que_jobs AS j
|
62
|
-
WHERE queue = $1::text
|
63
|
-
AND run_at <= now()
|
64
|
-
AND (priority, run_at, job_id) > (jobs.priority, jobs.run_at, jobs.job_id)
|
65
|
-
ORDER BY priority, run_at, job_id
|
66
|
-
LIMIT 1
|
67
|
-
) AS j
|
68
|
-
FROM jobs
|
69
|
-
WHERE jobs.job_id IS NOT NULL
|
70
|
-
LIMIT 1
|
71
|
-
) AS t1
|
72
|
-
)
|
73
|
-
)
|
74
|
-
SELECT queue, priority, run_at, job_id, job_class, args, error_count
|
75
|
-
FROM jobs
|
76
|
-
WHERE locked
|
77
|
-
LIMIT 1
|
78
|
-
}.freeze,
|
79
|
-
|
80
|
-
:check_job => %{
|
81
|
-
SELECT 1 AS one
|
82
|
-
FROM que_jobs
|
83
|
-
WHERE queue = $1::text
|
84
|
-
AND priority = $2::smallint
|
85
|
-
AND run_at = $3::timestamptz
|
86
|
-
AND job_id = $4::bigint
|
87
|
-
}.freeze,
|
88
|
-
|
89
|
-
:set_error => %{
|
90
|
-
UPDATE que_jobs
|
91
|
-
SET error_count = $1::integer,
|
92
|
-
run_at = now() + $2::bigint * '1 second'::interval,
|
93
|
-
last_error = $3::text
|
94
|
-
WHERE queue = $4::text
|
95
|
-
AND priority = $5::smallint
|
96
|
-
AND run_at = $6::timestamptz
|
97
|
-
AND job_id = $7::bigint
|
98
|
-
}.freeze,
|
99
|
-
|
100
|
-
:insert_job => %{
|
101
|
-
INSERT INTO que_jobs
|
102
|
-
(queue, priority, run_at, job_class, args)
|
103
|
-
VALUES
|
104
|
-
(coalesce($1, '')::text, coalesce($2, 100)::smallint, coalesce($3, now())::timestamptz, $4::text, coalesce($5, '[]')::json)
|
105
|
-
RETURNING *
|
106
|
-
}.freeze,
|
107
|
-
|
108
|
-
:destroy_job => %{
|
109
|
-
DELETE FROM que_jobs
|
110
|
-
WHERE queue = $1::text
|
111
|
-
AND priority = $2::smallint
|
112
|
-
AND run_at = $3::timestamptz
|
113
|
-
AND job_id = $4::bigint
|
114
|
-
}.freeze,
|
115
|
-
|
116
|
-
:job_stats => %{
|
117
|
-
SELECT queue,
|
118
|
-
job_class,
|
119
|
-
count(*) AS count,
|
120
|
-
count(locks.job_id) AS count_working,
|
121
|
-
sum((error_count > 0)::int) AS count_errored,
|
122
|
-
max(error_count) AS highest_error_count,
|
123
|
-
min(run_at) AS oldest_run_at
|
124
|
-
FROM que_jobs
|
125
|
-
LEFT JOIN (
|
126
|
-
SELECT (classid::bigint << 32) + objid::bigint AS job_id
|
127
|
-
FROM pg_locks
|
128
|
-
WHERE locktype = 'advisory'
|
129
|
-
) locks USING (job_id)
|
130
|
-
GROUP BY queue, job_class
|
131
|
-
ORDER BY count(*) DESC
|
132
|
-
}.freeze,
|
133
|
-
|
134
|
-
:worker_states => %{
|
135
|
-
SELECT que_jobs.*,
|
136
|
-
pg.pid AS pg_backend_pid,
|
137
|
-
pg.state AS pg_state,
|
138
|
-
pg.state_change AS pg_state_changed_at,
|
139
|
-
pg.query AS pg_last_query,
|
140
|
-
pg.query_start AS pg_last_query_started_at,
|
141
|
-
pg.xact_start AS pg_transaction_started_at,
|
142
|
-
pg.waiting AS pg_waiting_on_lock
|
143
|
-
FROM que_jobs
|
144
|
-
JOIN (
|
145
|
-
SELECT (classid::bigint << 32) + objid::bigint AS job_id, pg_stat_activity.*
|
146
|
-
FROM pg_locks
|
147
|
-
JOIN pg_stat_activity USING (pid)
|
148
|
-
WHERE locktype = 'advisory'
|
149
|
-
) pg USING (job_id)
|
150
|
-
}.freeze
|
151
|
-
}.freeze
|
152
|
-
end
|
@@ -1,152 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Don't run these specs in JRuby until jruby-pg is compatible with ActiveRecord.
|
4
|
-
unless defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
5
|
-
|
6
|
-
require 'spec_helper'
|
7
|
-
require 'active_record'
|
8
|
-
|
9
|
-
if ActiveRecord.version.release >= Gem::Version.new('4.2')
|
10
|
-
ActiveRecord::Base.raise_in_transactional_callbacks = true
|
11
|
-
end
|
12
|
-
ActiveRecord::Base.establish_connection(QUE_URL)
|
13
|
-
|
14
|
-
Que.connection = ActiveRecord
|
15
|
-
QUE_ADAPTERS[:active_record] = Que.adapter
|
16
|
-
|
17
|
-
describe "Que using the ActiveRecord adapter" do
|
18
|
-
before { Que.adapter = QUE_ADAPTERS[:active_record] }
|
19
|
-
|
20
|
-
it_behaves_like "a multi-threaded Que adapter"
|
21
|
-
|
22
|
-
it "should use the same connection that ActiveRecord does" do
|
23
|
-
begin
|
24
|
-
class ActiveRecordJob < Que::Job
|
25
|
-
def run
|
26
|
-
$pid1 = Integer(Que.execute("select pg_backend_pid()").first['pg_backend_pid'])
|
27
|
-
$pid2 = Integer(ActiveRecord::Base.connection.select_value("select pg_backend_pid()"))
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
ActiveRecordJob.enqueue
|
32
|
-
Que::Job.work
|
33
|
-
|
34
|
-
$pid1.should == $pid2
|
35
|
-
ensure
|
36
|
-
$pid1 = $pid2 = nil
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
context "if the connection goes down and is reconnected" do
|
41
|
-
before do
|
42
|
-
Que::Job.enqueue
|
43
|
-
ActiveRecord::Base.connection.reconnect!
|
44
|
-
end
|
45
|
-
|
46
|
-
it "should recreate the prepared statements" do
|
47
|
-
expect { Que::Job.enqueue }.not_to raise_error
|
48
|
-
|
49
|
-
DB[:que_jobs].count.should == 2
|
50
|
-
end
|
51
|
-
|
52
|
-
it "should work properly even in a transaction" do
|
53
|
-
ActiveRecord::Base.transaction do
|
54
|
-
expect { Que::Job.enqueue }.not_to raise_error
|
55
|
-
end
|
56
|
-
|
57
|
-
DB[:que_jobs].count.should == 2
|
58
|
-
end
|
59
|
-
|
60
|
-
it "should log this extraordinary event" do
|
61
|
-
$logger.messages.clear
|
62
|
-
Que::Job.enqueue
|
63
|
-
$logger.messages.count.should == 1
|
64
|
-
message = JSON.load($logger.messages.first)
|
65
|
-
message['lib'].should == 'que'
|
66
|
-
message['event'].should == 'reprepare_statement'
|
67
|
-
message['name'].should == 'insert_job'
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
it "should instantiate args as ActiveSupport::HashWithIndifferentAccess" do
|
72
|
-
begin
|
73
|
-
# Mimic the setting in the Railtie.
|
74
|
-
Que.json_converter = :with_indifferent_access.to_proc
|
75
|
-
|
76
|
-
ArgsJob.enqueue :param => 2
|
77
|
-
Que::Job.work
|
78
|
-
$passed_args.first[:param].should == 2
|
79
|
-
$passed_args.first.should be_an_instance_of ActiveSupport::HashWithIndifferentAccess
|
80
|
-
ensure
|
81
|
-
Que.json_converter = Que::INDIFFERENTIATOR
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
it "should support Rails' special extensions for times" do
|
86
|
-
Que.mode = :async
|
87
|
-
Que.worker_count = 4
|
88
|
-
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
89
|
-
|
90
|
-
Que::Job.enqueue :run_at => 1.minute.ago
|
91
|
-
DB[:que_jobs].get(:run_at).should be_within(3).of Time.now - 60
|
92
|
-
|
93
|
-
Que.wake_interval = 0.005.seconds
|
94
|
-
sleep_until { DB[:que_jobs].empty? }
|
95
|
-
end
|
96
|
-
|
97
|
-
it "should wake up a Worker after queueing a job in async mode, waiting for a transaction to commit if necessary" do
|
98
|
-
Que.mode = :async
|
99
|
-
Que.worker_count = 4
|
100
|
-
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
101
|
-
|
102
|
-
# Wakes a worker immediately when not in a transaction.
|
103
|
-
Que::Job.enqueue
|
104
|
-
sleep_until { Que::Worker.workers.all?(&:sleeping?) && DB[:que_jobs].empty? }
|
105
|
-
|
106
|
-
ActiveRecord::Base.transaction do
|
107
|
-
Que::Job.enqueue
|
108
|
-
Que::Worker.workers.each { |worker| worker.should be_sleeping }
|
109
|
-
end
|
110
|
-
sleep_until { Que::Worker.workers.all?(&:sleeping?) && DB[:que_jobs].empty? }
|
111
|
-
|
112
|
-
# Do nothing when queueing with a specific :run_at.
|
113
|
-
BlockJob.enqueue :run_at => Time.now
|
114
|
-
Que::Worker.workers.each { |worker| worker.should be_sleeping }
|
115
|
-
end
|
116
|
-
|
117
|
-
it "should be able to survive an ActiveRecord::Rollback without raising an error" do
|
118
|
-
ActiveRecord::Base.transaction do
|
119
|
-
Que::Job.enqueue
|
120
|
-
raise ActiveRecord::Rollback, "Call tech support!"
|
121
|
-
end
|
122
|
-
DB[:que_jobs].count.should be 0
|
123
|
-
end
|
124
|
-
|
125
|
-
it "should be able to tell when it's in an ActiveRecord transaction" do
|
126
|
-
Que.adapter.should_not be_in_transaction
|
127
|
-
ActiveRecord::Base.transaction do
|
128
|
-
Que.adapter.should be_in_transaction
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
it "should not leak connections to other databases when using ActiveRecord's multiple database support" do
|
133
|
-
class SecondDatabaseModel < ActiveRecord::Base
|
134
|
-
establish_connection(QUE_URL)
|
135
|
-
end
|
136
|
-
|
137
|
-
SecondDatabaseModel.clear_active_connections!
|
138
|
-
SecondDatabaseModel.connection_handler.active_connections?.should == false
|
139
|
-
|
140
|
-
class SecondDatabaseModelJob < Que::Job
|
141
|
-
def run(*args)
|
142
|
-
SecondDatabaseModel.connection.execute("SELECT 1")
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
SecondDatabaseModelJob.enqueue
|
147
|
-
Que::Job.work
|
148
|
-
|
149
|
-
SecondDatabaseModel.connection_handler.active_connections?.should == false
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
require 'connection_pool'
|
5
|
-
|
6
|
-
Que.connection = QUE_SPEC_CONNECTION_POOL = ConnectionPool.new &NEW_PG_CONNECTION
|
7
|
-
QUE_ADAPTERS[:connection_pool] = Que.adapter
|
8
|
-
|
9
|
-
describe "Que using the ConnectionPool adapter" do
|
10
|
-
before { Que.adapter = QUE_ADAPTERS[:connection_pool] }
|
11
|
-
|
12
|
-
it_behaves_like "a multi-threaded Que adapter"
|
13
|
-
|
14
|
-
it "should be able to tell when it's already in a transaction" do
|
15
|
-
Que.adapter.should_not be_in_transaction
|
16
|
-
QUE_SPEC_CONNECTION_POOL.with do |conn|
|
17
|
-
conn.async_exec "BEGIN"
|
18
|
-
Que.adapter.should be_in_transaction
|
19
|
-
conn.async_exec "COMMIT"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/spec/adapters/pg_spec.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe "Que using a bare PG connection" do
|
6
|
-
it_behaves_like "a Que adapter"
|
7
|
-
|
8
|
-
it "should synchronize access to that connection" do
|
9
|
-
lock = Que.adapter.lock
|
10
|
-
q1, q2 = Queue.new, Queue.new
|
11
|
-
|
12
|
-
thread1 = Thread.new do
|
13
|
-
Que.adapter.checkout do
|
14
|
-
q1.push nil
|
15
|
-
q2.pop
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
q1.pop
|
20
|
-
|
21
|
-
thread2 = Thread.new do
|
22
|
-
Que.adapter.checkout do
|
23
|
-
q1.push nil
|
24
|
-
q2.pop
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
sleep_until { thread2.status == 'sleep' }
|
29
|
-
|
30
|
-
thread1.should be_alive
|
31
|
-
thread2.should be_alive
|
32
|
-
|
33
|
-
lock.send(:instance_variable_get, :@mon_owner).should == thread1
|
34
|
-
q2.push nil
|
35
|
-
q1.pop
|
36
|
-
lock.send(:instance_variable_get, :@mon_owner).should == thread2
|
37
|
-
q2.push nil
|
38
|
-
thread1.join
|
39
|
-
thread2.join
|
40
|
-
end
|
41
|
-
end
|
data/spec/adapters/pond_spec.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
require 'pond'
|
5
|
-
|
6
|
-
Que.connection = QUE_SPEC_POND = Pond.new &NEW_PG_CONNECTION
|
7
|
-
QUE_ADAPTERS[:pond] = Que.adapter
|
8
|
-
|
9
|
-
describe "Que using the Pond adapter" do
|
10
|
-
before { Que.adapter = QUE_ADAPTERS[:pond] }
|
11
|
-
|
12
|
-
it_behaves_like "a multi-threaded Que adapter"
|
13
|
-
|
14
|
-
it "should be able to tell when it's already in a transaction" do
|
15
|
-
Que.adapter.should_not be_in_transaction
|
16
|
-
QUE_SPEC_POND.checkout do |conn|
|
17
|
-
conn.async_exec "BEGIN"
|
18
|
-
Que.adapter.should be_in_transaction
|
19
|
-
conn.async_exec "COMMIT"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,57 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
Que.connection = SEQUEL_ADAPTER_DB = Sequel.connect(QUE_URL)
|
6
|
-
QUE_ADAPTERS[:sequel] = Que.adapter
|
7
|
-
|
8
|
-
describe "Que using the Sequel adapter" do
|
9
|
-
before { Que.adapter = QUE_ADAPTERS[:sequel] }
|
10
|
-
|
11
|
-
it_behaves_like "a multi-threaded Que adapter"
|
12
|
-
|
13
|
-
it "should use the same connection that Sequel does" do
|
14
|
-
begin
|
15
|
-
class SequelJob < Que::Job
|
16
|
-
def run
|
17
|
-
$pid1 = Integer(Que.execute("select pg_backend_pid()").first['pg_backend_pid'])
|
18
|
-
$pid2 = Integer(SEQUEL_ADAPTER_DB['select pg_backend_pid()'].get)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
SequelJob.enqueue
|
23
|
-
Que::Job.work
|
24
|
-
|
25
|
-
$pid1.should == $pid2
|
26
|
-
ensure
|
27
|
-
$pid1 = $pid2 = nil
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
it "should wake up a Worker after queueing a job in async mode, waiting for a transaction to commit if necessary" do
|
32
|
-
Que.mode = :async
|
33
|
-
Que.worker_count = 4
|
34
|
-
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
35
|
-
|
36
|
-
# Wakes a worker immediately when not in a transaction.
|
37
|
-
Que::Job.enqueue
|
38
|
-
sleep_until { Que::Worker.workers.all?(&:sleeping?) && DB[:que_jobs].empty? }
|
39
|
-
|
40
|
-
SEQUEL_ADAPTER_DB.transaction do
|
41
|
-
Que::Job.enqueue
|
42
|
-
Que::Worker.workers.each { |worker| worker.should be_sleeping }
|
43
|
-
end
|
44
|
-
sleep_until { Que::Worker.workers.all?(&:sleeping?) && DB[:que_jobs].empty? }
|
45
|
-
|
46
|
-
# Do nothing when queueing with a specific :run_at.
|
47
|
-
BlockJob.enqueue :run_at => Time.now
|
48
|
-
Que::Worker.workers.each { |worker| worker.should be_sleeping }
|
49
|
-
end
|
50
|
-
|
51
|
-
it "should be able to tell when it's in a Sequel transaction" do
|
52
|
-
Que.adapter.should_not be_in_transaction
|
53
|
-
SEQUEL_ADAPTER_DB.transaction do
|
54
|
-
Que.adapter.should be_in_transaction
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
data/spec/gemfiles/Gemfile1
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
|
3
|
-
gem 'que', path: '../..'
|
4
|
-
|
5
|
-
group :development, :test do
|
6
|
-
gem 'rake'
|
7
|
-
|
8
|
-
gem 'activerecord', '~> 3.2', :require => nil
|
9
|
-
gem 'sequel', '~> 3', :require => nil
|
10
|
-
gem 'connection_pool', :require => nil
|
11
|
-
gem 'pg', :require => nil, :platform => :ruby
|
12
|
-
gem 'pg_jruby', :require => nil, :platform => :jruby
|
13
|
-
end
|
14
|
-
|
15
|
-
group :test do
|
16
|
-
gem 'rspec', '~> 2.14.1'
|
17
|
-
gem 'pry'
|
18
|
-
end
|
data/spec/gemfiles/Gemfile2
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
|
3
|
-
gem 'que', path: "../.."
|
4
|
-
|
5
|
-
group :development, :test do
|
6
|
-
gem 'rake'
|
7
|
-
|
8
|
-
gem 'activerecord', '~> 4.0', :require => nil
|
9
|
-
gem 'sequel', '~> 4', :require => nil
|
10
|
-
gem 'connection_pool', :require => nil
|
11
|
-
gem 'pg', :require => nil, :platform => :ruby
|
12
|
-
gem 'pg_jruby', :require => nil, :platform => :jruby
|
13
|
-
end
|
14
|
-
|
15
|
-
group :test do
|
16
|
-
gem 'rspec', '~> 2.14.1'
|
17
|
-
gem 'pry'
|
18
|
-
end
|
data/spec/spec_helper.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'que'
|
4
|
-
require 'uri'
|
5
|
-
require 'pg'
|
6
|
-
require 'logger'
|
7
|
-
require 'json'
|
8
|
-
|
9
|
-
Dir['./spec/support/**/*.rb'].sort.each &method(:require)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# Handy constants for initializing PG connections:
|
14
|
-
QUE_URL = ENV['DATABASE_URL'] || 'postgres://postgres:@localhost/que-test'
|
15
|
-
|
16
|
-
NEW_PG_CONNECTION = proc do
|
17
|
-
uri = URI.parse(QUE_URL)
|
18
|
-
pg = PG::Connection.open :host => uri.host,
|
19
|
-
:user => uri.user,
|
20
|
-
:password => uri.password,
|
21
|
-
:port => uri.port || 5432,
|
22
|
-
:dbname => uri.path[1..-1]
|
23
|
-
|
24
|
-
# Avoid annoying NOTICE messages in specs.
|
25
|
-
pg.async_exec "SET client_min_messages TO 'warning'"
|
26
|
-
pg
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
# Adapters track which statements have been prepared for their connections,
|
32
|
-
# and if Que.connection= is called before each spec, we're constantly creating
|
33
|
-
# new adapters and losing that information, which is bad. So instead, we hang
|
34
|
-
# onto a few adapters and assign them using Que.adapter= as needed. The plain
|
35
|
-
# pg adapter is the default.
|
36
|
-
|
37
|
-
# Also, let Que initialize the adapter itself, to make sure that the
|
38
|
-
# recognition logic works. Similar code can be found in the adapter specs.
|
39
|
-
Que.connection = NEW_PG_CONNECTION.call
|
40
|
-
QUE_ADAPTERS = {:pg => Que.adapter}
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
# We use Sequel to examine the database in specs.
|
45
|
-
require 'sequel'
|
46
|
-
DB = Sequel.connect(QUE_URL)
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
# Reset the table to the most up-to-date version.
|
51
|
-
DB.drop_table? :que_jobs
|
52
|
-
Que::Migrations.migrate!
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# Set up a dummy logger.
|
57
|
-
Que.logger = $logger = Object.new
|
58
|
-
$logger_mutex = Mutex.new # Protect against rare errors on Rubinius/JRuby.
|
59
|
-
|
60
|
-
def $logger.messages
|
61
|
-
@messages ||= []
|
62
|
-
end
|
63
|
-
|
64
|
-
def $logger.method_missing(m, message)
|
65
|
-
$logger_mutex.synchronize { messages << message }
|
66
|
-
end
|
67
|
-
|
68
|
-
# Object includes Kernel#warn which is not what we expect, so remove:
|
69
|
-
def $logger.warn(message)
|
70
|
-
method_missing(:warn, message)
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
# Helper to display spec descriptions.
|
76
|
-
description_builder = -> hash do
|
77
|
-
if g = hash[:example_group]
|
78
|
-
"#{description_builder.call(g)} #{hash[:description_args].first}"
|
79
|
-
else
|
80
|
-
hash[:description_args].first
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
stdout = Logger.new(STDOUT)
|
85
|
-
|
86
|
-
RSpec.configure do |config|
|
87
|
-
config.around do |spec|
|
88
|
-
# Figure out which spec is about to run, for logging purposes.
|
89
|
-
data = example.metadata
|
90
|
-
desc = description_builder.call(data)
|
91
|
-
line = "rspec #{data[:file_path]}:#{data[:line_number]}"
|
92
|
-
|
93
|
-
# Optionally log to STDOUT which spec is about to run. This is noisy, but
|
94
|
-
# helpful in identifying hanging specs.
|
95
|
-
stdout.info "Running spec: #{desc} @ #{line}" if ENV['LOG_SPEC']
|
96
|
-
|
97
|
-
Que.adapter = QUE_ADAPTERS[:pg]
|
98
|
-
|
99
|
-
Que.worker_count = 0
|
100
|
-
Que.mode = :async
|
101
|
-
Que.wake_interval = nil
|
102
|
-
|
103
|
-
$logger.messages.clear
|
104
|
-
|
105
|
-
spec.run
|
106
|
-
|
107
|
-
Que.worker_count = 0
|
108
|
-
Que.mode = :off
|
109
|
-
Que.wake_interval = nil
|
110
|
-
|
111
|
-
DB[:que_jobs].delete
|
112
|
-
|
113
|
-
# A bit of lint: make sure that no advisory locks are left open.
|
114
|
-
unless DB[:pg_locks].where(:locktype => 'advisory').empty?
|
115
|
-
stdout.info "Advisory lock left open: #{desc} @ #{line}"
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
data/spec/support/helpers.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Helper for testing threaded code.
|
4
|
-
QUE_TEST_TIMEOUT ||= 2
|
5
|
-
def sleep_until(timeout = QUE_TEST_TIMEOUT)
|
6
|
-
deadline = Time.now + timeout
|
7
|
-
loop do
|
8
|
-
break if yield
|
9
|
-
raise "Thing never happened!" if Time.now > deadline
|
10
|
-
sleep 0.01
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def suppress_warnings
|
15
|
-
original_verbosity, $VERBOSE = $VERBOSE, nil
|
16
|
-
yield
|
17
|
-
ensure
|
18
|
-
$VERBOSE = original_verbosity
|
19
|
-
end
|
data/spec/support/jobs.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Common Job classes for use in specs.
|
4
|
-
|
5
|
-
# Handy for blocking in the middle of processing a job.
|
6
|
-
class BlockJob < Que::Job
|
7
|
-
def run
|
8
|
-
$q1.push nil
|
9
|
-
$q2.pop
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
RSpec.configure do |config|
|
14
|
-
config.before { $q1, $q2 = Queue.new, Queue.new }
|
15
|
-
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
class ErrorJob < Que::Job
|
20
|
-
def run
|
21
|
-
raise "ErrorJob!"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
class ArgsJob < Que::Job
|
28
|
-
def run(*args)
|
29
|
-
$passed_args = args
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
RSpec.configure do |config|
|
34
|
-
config.before { $passed_args = nil }
|
35
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
shared_examples "a Que adapter" do
|
4
|
-
it "should be able to execute arbitrary SQL and return indifferent hashes" do
|
5
|
-
result = Que.execute("SELECT 1 AS one")
|
6
|
-
result.should == [{'one'=>1}]
|
7
|
-
result.first[:one].should == 1
|
8
|
-
end
|
9
|
-
|
10
|
-
it "should be able to execute multiple SQL statements in one string" do
|
11
|
-
Que.execute("SELECT 1 AS one; SELECT 1 AS one")
|
12
|
-
end
|
13
|
-
|
14
|
-
it "should be able to queue and work a job" do
|
15
|
-
Que::Job.enqueue
|
16
|
-
result = Que::Job.work
|
17
|
-
result[:event].should == :job_worked
|
18
|
-
result[:job][:job_class].should == 'Que::Job'
|
19
|
-
end
|
20
|
-
|
21
|
-
it "should yield the same Postgres connection for the duration of the block" do
|
22
|
-
Que.adapter.checkout do |conn|
|
23
|
-
conn.should be_a PG::Connection
|
24
|
-
pid1 = Que.execute "SELECT pg_backend_pid()"
|
25
|
-
pid2 = Que.execute "SELECT pg_backend_pid()"
|
26
|
-
pid1.should == pid2
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
it "should allow nested checkouts" do
|
31
|
-
Que.adapter.checkout do |a|
|
32
|
-
Que.adapter.checkout do |b|
|
33
|
-
a.object_id.should == b.object_id
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|