que 0.14.3 → 1.0.0.beta

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