que 0.14.3 → 1.0.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +108 -14
  4. data/LICENSE.txt +1 -1
  5. data/README.md +49 -45
  6. data/bin/command_line_interface.rb +239 -0
  7. data/bin/que +8 -82
  8. data/docs/README.md +2 -0
  9. data/docs/active_job.md +6 -0
  10. data/docs/advanced_setup.md +7 -64
  11. data/docs/command_line_interface.md +45 -0
  12. data/docs/error_handling.md +65 -18
  13. data/docs/inspecting_the_queue.md +30 -80
  14. data/docs/job_helper_methods.md +27 -0
  15. data/docs/logging.md +3 -22
  16. data/docs/managing_workers.md +6 -61
  17. data/docs/middleware.md +15 -0
  18. data/docs/migrating.md +4 -7
  19. data/docs/multiple_queues.md +8 -4
  20. data/docs/shutting_down_safely.md +1 -1
  21. data/docs/using_plain_connections.md +39 -15
  22. data/docs/using_sequel.md +5 -3
  23. data/docs/writing_reliable_jobs.md +15 -24
  24. data/lib/que.rb +98 -182
  25. data/lib/que/active_job/extensions.rb +97 -0
  26. data/lib/que/active_record/connection.rb +51 -0
  27. data/lib/que/active_record/model.rb +48 -0
  28. data/lib/que/connection.rb +179 -0
  29. data/lib/que/connection_pool.rb +78 -0
  30. data/lib/que/job.rb +107 -156
  31. data/lib/que/job_cache.rb +240 -0
  32. data/lib/que/job_methods.rb +168 -0
  33. data/lib/que/listener.rb +176 -0
  34. data/lib/que/locker.rb +466 -0
  35. data/lib/que/metajob.rb +47 -0
  36. data/lib/que/migrations.rb +24 -17
  37. data/lib/que/migrations/4/down.sql +48 -0
  38. data/lib/que/migrations/4/up.sql +265 -0
  39. data/lib/que/poller.rb +267 -0
  40. data/lib/que/rails/railtie.rb +14 -0
  41. data/lib/que/result_queue.rb +35 -0
  42. data/lib/que/sequel/model.rb +51 -0
  43. data/lib/que/utils/assertions.rb +62 -0
  44. data/lib/que/utils/constantization.rb +19 -0
  45. data/lib/que/utils/error_notification.rb +68 -0
  46. data/lib/que/utils/freeze.rb +20 -0
  47. data/lib/que/utils/introspection.rb +50 -0
  48. data/lib/que/utils/json_serialization.rb +21 -0
  49. data/lib/que/utils/logging.rb +78 -0
  50. data/lib/que/utils/middleware.rb +33 -0
  51. data/lib/que/utils/queue_management.rb +18 -0
  52. data/lib/que/utils/transactions.rb +34 -0
  53. data/lib/que/version.rb +1 -1
  54. data/lib/que/worker.rb +128 -167
  55. data/que.gemspec +13 -2
  56. metadata +37 -80
  57. data/.rspec +0 -2
  58. data/.travis.yml +0 -64
  59. data/Gemfile +0 -24
  60. data/docs/customizing_que.md +0 -200
  61. data/lib/generators/que/install_generator.rb +0 -24
  62. data/lib/generators/que/templates/add_que.rb +0 -13
  63. data/lib/que/adapters/active_record.rb +0 -40
  64. data/lib/que/adapters/base.rb +0 -133
  65. data/lib/que/adapters/connection_pool.rb +0 -16
  66. data/lib/que/adapters/pg.rb +0 -21
  67. data/lib/que/adapters/pond.rb +0 -16
  68. data/lib/que/adapters/sequel.rb +0 -20
  69. data/lib/que/railtie.rb +0 -16
  70. data/lib/que/rake_tasks.rb +0 -59
  71. data/lib/que/sql.rb +0 -170
  72. data/spec/adapters/active_record_spec.rb +0 -175
  73. data/spec/adapters/connection_pool_spec.rb +0 -22
  74. data/spec/adapters/pg_spec.rb +0 -41
  75. data/spec/adapters/pond_spec.rb +0 -22
  76. data/spec/adapters/sequel_spec.rb +0 -57
  77. data/spec/gemfiles/Gemfile.current +0 -19
  78. data/spec/gemfiles/Gemfile.old +0 -19
  79. data/spec/gemfiles/Gemfile.older +0 -19
  80. data/spec/gemfiles/Gemfile.oldest +0 -19
  81. data/spec/spec_helper.rb +0 -129
  82. data/spec/support/helpers.rb +0 -25
  83. data/spec/support/jobs.rb +0 -35
  84. data/spec/support/shared_examples/adapter.rb +0 -42
  85. data/spec/support/shared_examples/multi_threaded_adapter.rb +0 -46
  86. data/spec/unit/configuration_spec.rb +0 -31
  87. data/spec/unit/connection_spec.rb +0 -14
  88. data/spec/unit/customization_spec.rb +0 -251
  89. data/spec/unit/enqueue_spec.rb +0 -245
  90. data/spec/unit/helper_spec.rb +0 -12
  91. data/spec/unit/logging_spec.rb +0 -101
  92. data/spec/unit/migrations_spec.rb +0 -84
  93. data/spec/unit/pool_spec.rb +0 -365
  94. data/spec/unit/run_spec.rb +0 -14
  95. data/spec/unit/states_spec.rb +0 -50
  96. data/spec/unit/stats_spec.rb +0 -46
  97. data/spec/unit/transaction_spec.rb +0 -36
  98. data/spec/unit/work_spec.rb +0 -596
  99. data/spec/unit/worker_spec.rb +0 -167
  100. data/tasks/benchmark.rb +0 -3
  101. data/tasks/rspec.rb +0 -14
  102. data/tasks/safe_shutdown.rb +0 -67
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Que
4
- module Adapters
5
- class ConnectionPool < Base
6
- def initialize(pool)
7
- @pool = pool
8
- super
9
- end
10
-
11
- def checkout(&block)
12
- @pool.with(&block)
13
- end
14
- end
15
- end
16
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'monitor'
4
-
5
- module Que
6
- module Adapters
7
- class PG < Base
8
- attr_reader :lock
9
-
10
- def initialize(pg)
11
- @pg = pg
12
- @lock = Monitor.new # Must be re-entrant.
13
- super
14
- end
15
-
16
- def checkout
17
- @lock.synchronize { yield @pg }
18
- end
19
- end
20
- end
21
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Que
4
- module Adapters
5
- class Pond < Base
6
- def initialize(pond)
7
- @pond = pond
8
- super
9
- end
10
-
11
- def checkout(&block)
12
- @pond.checkout(&block)
13
- end
14
- end
15
- end
16
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Que
4
- module Adapters
5
- class Sequel < Base
6
- def initialize(db)
7
- @db = db
8
- super
9
- end
10
-
11
- def checkout(&block)
12
- @db.synchronize(&block)
13
- end
14
-
15
- def wake_worker_after_commit
16
- @db.after_commit { Que.wake! }
17
- end
18
- end
19
- end
20
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Que
4
- class Railtie < Rails::Railtie
5
- config.que = Que
6
-
7
- Que.logger = proc { Rails.logger }
8
- Que.mode = :sync if Rails.env.test?
9
- Que.connection = ::ActiveRecord if defined? ::ActiveRecord
10
- Que.json_converter = :with_indifferent_access.to_proc
11
-
12
- rake_tasks do
13
- load 'que/rake_tasks.rb'
14
- end
15
- end
16
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- namespace :que do
4
- desc "Process Que's jobs using a worker pool"
5
- task :work => :environment do
6
- $stdout.sync = true
7
-
8
- $stdout.puts "The que:work rake task has been deprecated and will be removed in Que 1.0. Please transition to the que command line interface instead."
9
-
10
- if defined?(::Rails) && Rails.respond_to?(:application)
11
- # ActiveSupport's dependency autoloading isn't threadsafe, and Que uses
12
- # multiple threads, which means that eager loading is necessary. Rails
13
- # explicitly prevents eager loading when the environment task is invoked,
14
- # so we need to manually eager load the app here.
15
- Rails.application.eager_load!
16
- end
17
-
18
- Que.logger.level = Logger.const_get((ENV['QUE_LOG_LEVEL'] || 'INFO').upcase)
19
- Que.worker_count = (ENV['QUE_WORKER_COUNT'] || 4).to_i
20
- Que.wake_interval = (ENV['QUE_WAKE_INTERVAL'] || 0.1).to_f
21
- Que.queue_name = ENV['QUE_QUEUE'] if ENV['QUE_QUEUE']
22
- Que.mode = :async
23
-
24
- # When changing how signals are caught, be sure to test the behavior with
25
- # the rake task in tasks/safe_shutdown.rb.
26
-
27
- stop = false
28
- %w( INT TERM ).each do |signal|
29
- trap(signal) {stop = true}
30
- end
31
-
32
- at_exit do
33
- $stdout.puts "Finishing Que's current jobs before exiting..."
34
- Que.worker_count = 0
35
- Que.mode = :off
36
- $stdout.puts "Que's jobs finished, exiting..."
37
- end
38
-
39
- loop do
40
- sleep 0.01
41
- break if stop
42
- end
43
- end
44
-
45
- desc "Migrate Que's job table to the most recent version (creating it if it doesn't exist)"
46
- task :migrate => :environment do
47
- Que.migrate!
48
- end
49
-
50
- desc "Drop Que's job table"
51
- task :drop => :environment do
52
- Que.drop!
53
- end
54
-
55
- desc "Clear Que's job table"
56
- task :clear => :environment do
57
- Que.clear!
58
- end
59
- end
@@ -1,170 +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 = error_count + 1,
92
- run_at = now() + $1::bigint * '1 second'::interval,
93
- last_error = $2::text
94
- WHERE queue = $3::text
95
- AND priority = $4::smallint
96
- AND run_at = $5::timestamptz
97
- AND job_id = $6::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_95 => %{
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
-
152
- :worker_states_96 => %{
153
- SELECT que_jobs.*,
154
- pg.pid AS pg_backend_pid,
155
- pg.state AS pg_state,
156
- pg.state_change AS pg_state_changed_at,
157
- pg.query AS pg_last_query,
158
- pg.query_start AS pg_last_query_started_at,
159
- pg.xact_start AS pg_transaction_started_at,
160
- pg.wait_event_type IS NOT NULL AS pg_waiting_on_lock
161
- FROM que_jobs
162
- JOIN (
163
- SELECT (classid::bigint << 32) + objid::bigint AS job_id, pg_stat_activity.*
164
- FROM pg_locks
165
- JOIN pg_stat_activity USING (pid)
166
- WHERE locktype = 'advisory'
167
- ) pg USING (job_id)
168
- }.freeze,
169
- }.freeze
170
- end
@@ -1,175 +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') && ActiveRecord.version.release < Gem::Version.new('5.0')
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
- around do |example|
42
- Que::Job.enqueue
43
- ::ActiveRecord::Base.connection_pool.with_connection do |conn|
44
- ActiveRecord::Base.connection.reconnect!
45
- example.run
46
- end
47
- end
48
-
49
- it "should recreate the prepared statements" do
50
- expect { Que::Job.enqueue }.not_to raise_error
51
-
52
- DB[:que_jobs].count.should == 2
53
- end
54
-
55
- it "should work properly even in a transaction" do
56
- ActiveRecord::Base.transaction do
57
- expect { Que::Job.enqueue }.not_to raise_error
58
- end
59
-
60
- DB[:que_jobs].count.should == 2
61
- end
62
-
63
- it "should log this extraordinary event" do
64
- pending
65
- $logger.messages.clear
66
- Que::Job.enqueue
67
-
68
- if $logger.messages.count != 1
69
- puts $logger.messages.inspect
70
- end
71
-
72
- $logger.messages.count.should == 1
73
- message = JSON.load($logger.messages.first)
74
- message['lib'].should == 'que'
75
- message['event'].should == 'reprepare_statement'
76
- message['name'].should == 'insert_job'
77
- end
78
- end
79
-
80
- it "should instantiate args as ActiveSupport::HashWithIndifferentAccess" do
81
- begin
82
- # Mimic the setting in the Railtie.
83
- Que.json_converter = :with_indifferent_access.to_proc
84
-
85
- ArgsJob.enqueue :param => 2
86
- Que::Job.work
87
- $passed_args.first[:param].should == 2
88
- $passed_args.first.should be_an_instance_of ActiveSupport::HashWithIndifferentAccess
89
- ensure
90
- Que.json_converter = Que::INDIFFERENTIATOR
91
- end
92
- end
93
-
94
- it "should support Rails' special extensions for times" do
95
- Que.mode = :async
96
- Que.worker_count = 4
97
- sleep_until { Que::Worker.workers.all? &:sleeping? }
98
-
99
- Que::Job.enqueue :run_at => 1.minute.ago
100
- DB[:que_jobs].get(:run_at).should be_within(3).of Time.now - 60
101
-
102
- Que.wake_interval = 0.005.seconds
103
- sleep_until { DB[:que_jobs].empty? }
104
- end
105
-
106
- it "should wake up a Worker after queueing a job in async mode, waiting for a transaction to commit if necessary" do
107
- pending
108
-
109
- Que.mode = :async
110
- Que.worker_count = 4
111
- sleep_until { Que::Worker.workers.all? &:sleeping? }
112
-
113
- # Wakes a worker immediately when not in a transaction.
114
- Que::Job.enqueue
115
- sleep_until { Que::Worker.workers.all?(&:sleeping?) && DB[:que_jobs].empty? }
116
-
117
- # Wakes a worker on transaction commit when in a transaction.
118
- ActiveRecord::Base.transaction do
119
- Que::Job.enqueue
120
- Que::Worker.workers.each { |worker| worker.should be_sleeping }
121
- end
122
- sleep_until { Que::Worker.workers.all?(&:sleeping?) && DB[:que_jobs].empty? }
123
-
124
- # Does nothing when in a nested transaction.
125
- # TODO: ideally this would wake after the outer transaction commits
126
- if ActiveRecord.version.release >= Gem::Version.new('5.0')
127
- ActiveRecord::Base.transaction do
128
- ActiveRecord::Base.transaction(requires_new: true) do
129
- Que::Job.enqueue
130
- Que::Worker.workers.each { |worker| worker.should be_sleeping }
131
- end
132
- end
133
- end
134
-
135
- # Do nothing when queueing with a specific :run_at.
136
- BlockJob.enqueue :run_at => Time.now
137
- Que::Worker.workers.each { |worker| worker.should be_sleeping }
138
- end
139
-
140
- it "should be able to survive an ActiveRecord::Rollback without raising an error" do
141
- ActiveRecord::Base.transaction do
142
- Que::Job.enqueue
143
- raise ActiveRecord::Rollback, "Call tech support!"
144
- end
145
- DB[:que_jobs].count.should be 0
146
- end
147
-
148
- it "should be able to tell when it's in an ActiveRecord transaction" do
149
- Que.adapter.should_not be_in_transaction
150
- ActiveRecord::Base.transaction do
151
- Que.adapter.should be_in_transaction
152
- end
153
- end
154
-
155
- it "should not leak connections to other databases when using ActiveRecord's multiple database support" do
156
- class SecondDatabaseModel < ActiveRecord::Base
157
- establish_connection(QUE_URL)
158
- end
159
-
160
- SecondDatabaseModel.clear_active_connections!
161
- SecondDatabaseModel.connection_handler.active_connections?.should == false
162
-
163
- class SecondDatabaseModelJob < Que::Job
164
- def run(*args)
165
- SecondDatabaseModel.connection.execute("SELECT 1")
166
- end
167
- end
168
-
169
- SecondDatabaseModelJob.enqueue
170
- Que::Job.work
171
-
172
- SecondDatabaseModel.connection_handler.active_connections?.should == false
173
- end
174
- end
175
- end