que 0.11.3 → 2.2.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.
- 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
|