que 0.7.3 → 0.8.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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +3 -1
- data/docs/advanced_setup.md +13 -2
- data/lib/que.rb +8 -4
- data/lib/que/railtie.rb +1 -2
- data/lib/que/sql.rb +1 -1
- data/lib/que/version.rb +1 -1
- data/lib/que/worker.rb +56 -59
- data/spec/adapters/active_record_spec.rb +2 -0
- data/spec/adapters/sequel_spec.rb +1 -0
- data/spec/spec_helper.rb +9 -3
- data/spec/unit/logging_spec.rb +22 -0
- data/spec/unit/pool_spec.rb +271 -126
- data/spec/unit/work_spec.rb +4 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0030251d843d06847bfb8e9e12658b952edbdf2
|
4
|
+
data.tar.gz: 455c5a6732b86d0e3ade3c961613d206160f9fd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 669370c39a3b68c3cdc9ff6c9d8ebdd865690451175bc84062696a961a5cf5e3dbe81761fb97c08892731895ebd398001b73aed4158c99010bcdd2707bd10073
|
7
|
+
data.tar.gz: 8c7270f5fc20dee14e28a69468cd01a348e7e24c358ba48fb19a1c2d8562e3d05b747649a800edefda5a4d68fcf5a2c623ba1ffc09441dbdf20bed53c32c97c9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
### 0.8.0 (2014-07-12)
|
2
|
+
|
3
|
+
* A callable can now be set as the logger, like `Que.logger = proc { MyLogger.new }`. Que uses this in its Railtie for cleaner initialization, but it is also available for public use.
|
4
|
+
|
5
|
+
* `Que.mode=` and `Que.worker_count=` now function independently. That is, setting the worker_count to a nonzero number no longer sets mode = :async (triggering the pool to start working jobs), and setting it to zero no longer sets mode = :off. Similarly, setting the mode to :async no longer sets the worker_count to 4 from 0, and setting the mode to :off no longer sets the worker_count to 0. This behavior was changed because it was interfering with configuration during initialization of Rails applications, and because it was unexpected. (#47)
|
6
|
+
|
7
|
+
* Fixed a similar bug wherein setting a wake_interval during application startup would break worker awakening after the process was forked.
|
8
|
+
|
1
9
|
### 0.7.3 (2014-05-19)
|
2
10
|
|
3
11
|
* When mode = :sync, don't touch the database at all when running jobs inline. Needed for ActiveJob compatibility (#46).
|
data/README.md
CHANGED
@@ -102,6 +102,8 @@ To determine what happens when a job is queued, you can set Que's mode in your a
|
|
102
102
|
* `config.que.mode = :async` - In this mode, a pool of background workers is spun up, each running in their own thread. This is the default when running `bin/rails server`. See the docs for [more information on managing workers](https://github.com/chanks/que/blob/master/docs/managing_workers.md).
|
103
103
|
* `config.que.mode = :sync` - In this mode, any jobs you queue will be run in the same thread, synchronously (that is, `MyJob.enqueue` runs the job and won't return until it's completed). This makes your application's behavior easier to test, so it's the default in the test environment.
|
104
104
|
|
105
|
+
If you're using ActiveRecord to dump your database's schema, you'll probably want to [set schema_format to :sql](http://guides.rubyonrails.org/migrations.html#types-of-schema-dumps) so that Que's table structure is managed correctly.
|
106
|
+
|
105
107
|
## Contributing
|
106
108
|
|
107
109
|
1. Fork it
|
@@ -109,7 +111,7 @@ To determine what happens when a job is queued, you can set Que's mode in your a
|
|
109
111
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
110
112
|
4. Push to the branch (`git push origin my-new-feature`)
|
111
113
|
5. Create new Pull Request
|
112
|
-
|
114
|
+
|
113
115
|
A note on running specs - Que's worker system is multithreaded and therefore prone to race conditions (especially on Rubinius). As such, if you've touched that code, a single spec run passing isn't a guarantee that any changes you've made haven't introduced bugs. One thing I like to do before pushing changes is rerun the specs many times and watching for hangs. You can do this from the command line with something like:
|
114
116
|
|
115
117
|
for i in {1..1000}; do rspec -b --seed $i; done
|
data/docs/advanced_setup.md
CHANGED
@@ -20,14 +20,25 @@ There are other docs to read if you're using [Sequel](https://github.com/chanks/
|
|
20
20
|
|
21
21
|
### Forking Servers
|
22
22
|
|
23
|
-
If you want to run a worker pool in your web process and you're using a forking webserver like Unicorn or Puma in some configurations, you'll want to set `Que.mode = :off` in your application configuration and only start up the worker pool in the child processes. So, for Puma:
|
23
|
+
If you want to run a worker pool in your web process and you're using a forking webserver like Unicorn or Puma in some configurations, you'll want to set `Que.mode = :off` in your application configuration and only start up the worker pool in the child processes after the DB connection has been reestablished. So, for Puma:
|
24
24
|
|
25
25
|
# config/puma.rb
|
26
26
|
on_worker_boot do
|
27
|
-
|
27
|
+
ActiveRecord::Base.establish_connection
|
28
|
+
|
28
29
|
Que.mode = :async
|
29
30
|
end
|
30
31
|
|
32
|
+
And for Unicorn:
|
33
|
+
|
34
|
+
# config/unicorn.rb
|
35
|
+
after_fork do |server, worker|
|
36
|
+
ActiveRecord::Base.establish_connection
|
37
|
+
|
38
|
+
Que.mode = :async
|
39
|
+
end
|
40
|
+
|
41
|
+
|
31
42
|
### Managing the Jobs Table
|
32
43
|
|
33
44
|
After you've connected Que to the database, you can manage the jobs table:
|
data/lib/que.rb
CHANGED
@@ -17,8 +17,8 @@ module Que
|
|
17
17
|
end
|
18
18
|
|
19
19
|
class << self
|
20
|
-
attr_accessor :
|
21
|
-
attr_writer :adapter, :log_formatter
|
20
|
+
attr_accessor :error_handler
|
21
|
+
attr_writer :logger, :adapter, :log_formatter
|
22
22
|
|
23
23
|
def connection=(connection)
|
24
24
|
self.adapter =
|
@@ -83,11 +83,15 @@ module Que
|
|
83
83
|
level = data.delete(:level) || :info
|
84
84
|
data = {:lib => 'que', :hostname => Socket.gethostname, :pid => Process.pid, :thread => Thread.current.object_id}.merge(data)
|
85
85
|
|
86
|
-
if logger && output = log_formatter.call(data)
|
87
|
-
|
86
|
+
if (l = logger) && output = log_formatter.call(data)
|
87
|
+
l.send level, output
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
def logger
|
92
|
+
@logger.respond_to?(:call) ? @logger.call : @logger
|
93
|
+
end
|
94
|
+
|
91
95
|
def log_formatter
|
92
96
|
@log_formatter ||= JSON_MODULE.method(:dump)
|
93
97
|
end
|
data/lib/que/railtie.rb
CHANGED
@@ -2,6 +2,7 @@ module Que
|
|
2
2
|
class Railtie < Rails::Railtie
|
3
3
|
config.que = Que
|
4
4
|
|
5
|
+
Que.logger = proc { Rails.logger }
|
5
6
|
Que.mode = :sync if Rails.env.test?
|
6
7
|
Que.connection = ::ActiveRecord if defined? ::ActiveRecord
|
7
8
|
|
@@ -11,8 +12,6 @@ module Que
|
|
11
12
|
|
12
13
|
initializer 'que.setup' do
|
13
14
|
ActiveSupport.on_load :after_initialize do
|
14
|
-
Que.logger ||= Rails.logger
|
15
|
-
|
16
15
|
# Only start up the worker pool if running as a server.
|
17
16
|
Que.mode ||= :async if defined? Rails::Server
|
18
17
|
|
data/lib/que/sql.rb
CHANGED
@@ -48,7 +48,7 @@ module Que
|
|
48
48
|
:set_error => %{
|
49
49
|
UPDATE que_jobs
|
50
50
|
SET error_count = $1::integer,
|
51
|
-
run_at = now() + $2::
|
51
|
+
run_at = now() + $2::bigint * '1 second'::interval,
|
52
52
|
last_error = $3::text
|
53
53
|
WHERE queue = $4::text
|
54
54
|
AND priority = $5::smallint
|
data/lib/que/version.rb
CHANGED
data/lib/que/worker.rb
CHANGED
@@ -71,30 +71,33 @@ module Que
|
|
71
71
|
|
72
72
|
def work_loop
|
73
73
|
loop do
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
74
|
+
cycle = nil
|
75
|
+
|
76
|
+
if Que.mode == :async
|
77
|
+
time = Time.now
|
78
|
+
result = Job.work(queue)
|
79
|
+
|
80
|
+
case result[:event]
|
81
|
+
when :job_unavailable
|
82
|
+
cycle = false
|
83
|
+
result[:level] = :debug
|
84
|
+
when :job_race_condition
|
85
|
+
cycle = true
|
86
|
+
result[:level] = :debug
|
87
|
+
when :job_worked
|
88
|
+
cycle = true
|
89
|
+
result[:elapsed] = (Time.now - time).round(5)
|
90
|
+
when :job_errored
|
91
|
+
# For PG::Errors, assume we had a problem reaching the database, and
|
92
|
+
# don't hit it again right away.
|
93
|
+
cycle = !result[:error].is_a?(PG::Error)
|
94
|
+
result[:error] = {:class => result[:error].class.to_s, :message => result[:error].message}
|
95
|
+
else
|
96
|
+
raise "Unknown Event: #{result[:event].inspect}"
|
97
|
+
end
|
96
98
|
|
97
|
-
|
99
|
+
Que.log(result)
|
100
|
+
end
|
98
101
|
|
99
102
|
synchronize { @state = :sleeping unless cycle || @stop }
|
100
103
|
sleep if @state == :sleeping
|
@@ -111,35 +114,40 @@ module Que
|
|
111
114
|
# changed in Que.wake_interval= below.
|
112
115
|
@wake_interval = 5
|
113
116
|
|
117
|
+
# Four workers is a sensible default for most use cases.
|
118
|
+
@worker_count = 4
|
119
|
+
|
114
120
|
class << self
|
115
|
-
attr_reader :mode, :wake_interval
|
121
|
+
attr_reader :mode, :wake_interval, :worker_count
|
122
|
+
|
123
|
+
# In order to work in a forking webserver, we need to be able to accept
|
124
|
+
# worker_count and wake_interval settings without actually instantiating
|
125
|
+
# the relevant threads until the mode is actually set to :async in a
|
126
|
+
# post-fork hook (since forking will kill any running background threads).
|
116
127
|
|
117
128
|
def mode=(mode)
|
118
|
-
|
119
|
-
|
120
|
-
set_worker_count 4 if worker_count.zero?
|
121
|
-
when :sync, :off
|
122
|
-
set_worker_count 0
|
123
|
-
end
|
124
|
-
end
|
129
|
+
Que.log :event => 'mode_change', :value => mode.to_s
|
130
|
+
@mode = mode
|
125
131
|
|
126
|
-
|
127
|
-
|
132
|
+
if mode == :async
|
133
|
+
set_up_workers
|
134
|
+
wrangler
|
135
|
+
end
|
128
136
|
end
|
129
137
|
|
130
138
|
def worker_count=(count)
|
131
|
-
|
132
|
-
|
133
|
-
|
139
|
+
Que.log :event => 'worker_count_change', :value => count.to_s
|
140
|
+
@worker_count = count
|
141
|
+
set_up_workers if mode == :async
|
134
142
|
end
|
135
143
|
|
136
|
-
def
|
137
|
-
workers
|
144
|
+
def workers
|
145
|
+
@workers ||= []
|
138
146
|
end
|
139
147
|
|
140
148
|
def wake_interval=(interval)
|
141
149
|
@wake_interval = interval
|
142
|
-
wrangler.wakeup
|
150
|
+
wrangler.wakeup if mode == :async
|
143
151
|
end
|
144
152
|
|
145
153
|
def wake!
|
@@ -152,30 +160,19 @@ module Que
|
|
152
160
|
|
153
161
|
private
|
154
162
|
|
163
|
+
def set_up_workers
|
164
|
+
if worker_count > workers.count
|
165
|
+
workers.push *(worker_count - workers.count).times.map{new(ENV['QUE_QUEUE'] || '')}
|
166
|
+
elsif worker_count < workers.count
|
167
|
+
workers.pop(workers.count - worker_count).each(&:stop).each(&:wait_until_stopped)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
155
171
|
def wrangler
|
156
172
|
@wrangler ||= Thread.new do
|
157
173
|
loop do
|
158
174
|
sleep *@wake_interval
|
159
|
-
wake! if @wake_interval
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def set_mode(mode)
|
165
|
-
if mode != @mode
|
166
|
-
Que.log :event => 'mode_change', :value => mode.to_s
|
167
|
-
@mode = mode
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def set_worker_count(count)
|
172
|
-
if count != worker_count
|
173
|
-
Que.log :event => 'worker_count_change', :value => count.to_s
|
174
|
-
|
175
|
-
if count > worker_count
|
176
|
-
workers.push *(count - worker_count).times.map{new(ENV['QUE_QUEUE'] || '')}
|
177
|
-
elsif count < worker_count
|
178
|
-
workers.pop(worker_count - count).each(&:stop).each(&:wait_until_stopped)
|
175
|
+
wake! if @wake_interval && mode == :async
|
179
176
|
end
|
180
177
|
end
|
181
178
|
end
|
@@ -71,6 +71,7 @@ unless defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
|
71
71
|
|
72
72
|
it "should support Rails' special extensions for times" do
|
73
73
|
Que.mode = :async
|
74
|
+
Que.worker_count = 4
|
74
75
|
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
75
76
|
|
76
77
|
Que::Job.enqueue :run_at => 1.minute.ago
|
@@ -82,6 +83,7 @@ unless defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
|
82
83
|
|
83
84
|
it "should wake up a Worker after queueing a job in async mode, waiting for a transaction to commit if necessary" do
|
84
85
|
Que.mode = :async
|
86
|
+
Que.worker_count = 4
|
85
87
|
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
86
88
|
|
87
89
|
# Wakes a worker immediately when not in a transaction.
|
@@ -28,6 +28,7 @@ describe "Que using the Sequel adapter" do
|
|
28
28
|
|
29
29
|
it "should wake up a Worker after queueing a job in async mode, waiting for a transaction to commit if necessary" do
|
30
30
|
Que.mode = :async
|
31
|
+
Que.worker_count = 4
|
31
32
|
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
32
33
|
|
33
34
|
# Wakes a worker immediately when not in a transaction.
|
data/spec/spec_helper.rb
CHANGED
@@ -92,15 +92,21 @@ RSpec.configure do |config|
|
|
92
92
|
# helpful in identifying hanging specs.
|
93
93
|
stdout.info "Running spec: #{desc} @ #{line}" if ENV['LOG_SPEC']
|
94
94
|
|
95
|
-
$logger.messages.clear
|
96
95
|
Que.adapter = QUE_ADAPTERS[:pg]
|
97
96
|
|
97
|
+
Que.worker_count = 0
|
98
|
+
Que.mode = :async
|
99
|
+
Que.wake_interval = nil
|
100
|
+
|
101
|
+
$logger.messages.clear
|
102
|
+
|
98
103
|
spec.run
|
99
104
|
|
100
|
-
|
105
|
+
Que.worker_count = 0
|
101
106
|
Que.mode = :off
|
102
107
|
Que.wake_interval = nil
|
103
|
-
|
108
|
+
|
109
|
+
DB[:que_jobs].delete
|
104
110
|
|
105
111
|
# A bit of lint: make sure that no advisory locks are left open.
|
106
112
|
unless DB[:pg_locks].where(:locktype => 'advisory').empty?
|
data/spec/unit/logging_spec.rb
CHANGED
@@ -14,8 +14,30 @@ describe "Logging" do
|
|
14
14
|
message['thread'].should == Thread.current.object_id
|
15
15
|
end
|
16
16
|
|
17
|
+
it "should allow a callable to be set as the logger" do
|
18
|
+
begin
|
19
|
+
# Make sure we can get through a work cycle without a logger.
|
20
|
+
Que.logger = proc { $logger }
|
21
|
+
|
22
|
+
Que::Job.enqueue
|
23
|
+
worker = Que::Worker.new
|
24
|
+
sleep_until { worker.sleeping? }
|
25
|
+
|
26
|
+
DB[:que_jobs].should be_empty
|
27
|
+
|
28
|
+
worker.stop
|
29
|
+
worker.wait_until_stopped
|
30
|
+
|
31
|
+
$logger.messages.count.should be 2
|
32
|
+
$logger.messages.map{|m| JSON.load(m)['event']}.should == ['job_worked', 'job_unavailable']
|
33
|
+
ensure
|
34
|
+
Que.logger = $logger
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
17
38
|
it "should not raise an error when no logger is present" do
|
18
39
|
begin
|
40
|
+
# Make sure we can get through a work cycle without a logger.
|
19
41
|
Que.logger = nil
|
20
42
|
|
21
43
|
Que::Job.enqueue
|
data/spec/unit/pool_spec.rb
CHANGED
@@ -4,140 +4,277 @@ describe "Managing the Worker pool" do
|
|
4
4
|
it "should log mode changes" do
|
5
5
|
Que.mode = :sync
|
6
6
|
Que.mode = :off
|
7
|
+
Que.mode = :off
|
7
8
|
|
8
|
-
$logger.messages.count.should be
|
9
|
-
m1, m2 = $logger.messages.map{|m| JSON.load(m)}
|
9
|
+
$logger.messages.count.should be 3
|
10
|
+
m1, m2, m3 = $logger.messages.map { |m| JSON.load(m) }
|
10
11
|
|
11
12
|
m1['event'].should == 'mode_change'
|
12
13
|
m1['value'].should == 'sync'
|
13
14
|
|
14
15
|
m2['event'].should == 'mode_change'
|
15
16
|
m2['value'].should == 'off'
|
17
|
+
|
18
|
+
m3['event'].should == 'mode_change'
|
19
|
+
m3['value'].should == 'off'
|
16
20
|
end
|
17
21
|
|
18
|
-
describe "Que.mode
|
19
|
-
|
20
|
-
|
22
|
+
describe "Que.mode=" do
|
23
|
+
describe ":off" do
|
24
|
+
it "with worker_count 0 should not instantiate workers or hit the db" do
|
25
|
+
Que.connection = nil
|
26
|
+
Que.worker_count = 0
|
27
|
+
Que.mode = :off
|
28
|
+
Que::Worker.workers.should == []
|
29
|
+
end
|
21
30
|
|
22
|
-
|
23
|
-
|
24
|
-
|
31
|
+
it "with worker_count > 0 should not instantiate workers or hit the db" do
|
32
|
+
Que.connection = nil
|
33
|
+
Que.mode = :off
|
34
|
+
Que.worker_count = 5
|
35
|
+
Que.mode = :off
|
36
|
+
Que::Worker.workers.should == []
|
37
|
+
end
|
25
38
|
end
|
26
39
|
|
27
|
-
|
28
|
-
|
29
|
-
|
40
|
+
describe ":sync" do
|
41
|
+
it "with worker_count 0 should not instantiate workers or hit the db" do
|
42
|
+
Que.connection = nil
|
43
|
+
Que.worker_count = 0
|
44
|
+
Que.mode = :sync
|
45
|
+
Que::Worker.workers.should == []
|
46
|
+
end
|
47
|
+
|
48
|
+
it "with worker_count > 0 should not instantiate workers or hit the db" do
|
49
|
+
Que.connection = nil
|
50
|
+
Que.mode = :sync
|
51
|
+
Que.worker_count = 5
|
52
|
+
Que.mode = :sync
|
53
|
+
Que::Worker.workers.should == []
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should make jobs run in the same thread as they are queued" do
|
57
|
+
Que.mode = :sync
|
58
|
+
|
59
|
+
ArgsJob.enqueue(5, :testing => "synchronous").should be_an_instance_of ArgsJob
|
60
|
+
$passed_args.should == [5, {:testing => "synchronous"}]
|
61
|
+
DB[:que_jobs].count.should be 0
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should work fine with enqueuing jobs without a DB connection" do
|
65
|
+
Que.connection = nil
|
66
|
+
Que.mode = :sync
|
67
|
+
|
68
|
+
ArgsJob.enqueue(5, :testing => "synchronous").should be_an_instance_of ArgsJob
|
69
|
+
$passed_args.should == [5, {:testing => "synchronous"}]
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should not affect jobs that are queued with specific run_ats" do
|
73
|
+
Que.mode = :sync
|
30
74
|
|
31
|
-
|
32
|
-
|
75
|
+
ArgsJob.enqueue(5, :testing => "synchronous", :run_at => Time.now + 60)
|
76
|
+
DB[:que_jobs].select_map(:job_class).should == ["ArgsJob"]
|
77
|
+
end
|
33
78
|
end
|
34
79
|
|
35
|
-
|
36
|
-
|
80
|
+
describe ":async" do
|
81
|
+
it "with worker_count 0 should not instantiate workers or hit the db" do
|
82
|
+
Que.connection = nil
|
83
|
+
Que.worker_count = 0
|
84
|
+
Que.mode = :async
|
85
|
+
Que::Worker.workers.map{|w| [w.state, w.thread.status]}.should == []
|
86
|
+
end
|
87
|
+
|
88
|
+
it "with worker_count > 0 should instantiate workers and hit the db" do
|
89
|
+
Que::Job.enqueue
|
90
|
+
Que.worker_count = 5
|
91
|
+
Que.mode = :async
|
92
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
93
|
+
DB[:que_jobs].count.should == 0
|
94
|
+
Que::Worker.workers.map{|w| [w.state, w.thread.status]}.should == [[:sleeping, 'sleep']] * 5
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should wake a worker every Que.wake_interval seconds" do
|
98
|
+
Que.worker_count = 4
|
99
|
+
Que.mode = :async
|
100
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
101
|
+
Que.wake_interval = 0.01 # 10 ms
|
102
|
+
Que::Job.enqueue
|
103
|
+
sleep_until { DB[:que_jobs].count == 0 }
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should work jobs in the queue defined by QUE_QUEUE" do
|
107
|
+
begin
|
108
|
+
Que::Job.enqueue 1
|
109
|
+
Que::Job.enqueue 2, :queue => 'my_queue'
|
110
|
+
|
111
|
+
ENV['QUE_QUEUE'] = 'my_queue'
|
37
112
|
|
38
|
-
|
39
|
-
|
113
|
+
Que.mode = :async
|
114
|
+
Que.worker_count = 2
|
115
|
+
|
116
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
117
|
+
DB[:que_jobs].count.should be 1
|
118
|
+
|
119
|
+
job = DB[:que_jobs].first
|
120
|
+
job[:queue].should == ''
|
121
|
+
job[:args].should == '[1]'
|
122
|
+
ensure
|
123
|
+
ENV.delete('QUE_QUEUE')
|
124
|
+
end
|
125
|
+
end
|
40
126
|
end
|
41
127
|
end
|
42
128
|
|
43
|
-
describe "Que.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
129
|
+
describe "Que.worker_count=" do
|
130
|
+
describe "when the mode is :off" do
|
131
|
+
it "should record the setting but not instantiate any workers" do
|
132
|
+
Que.worker_count.should == 0
|
133
|
+
Que.connection = nil
|
134
|
+
Que.mode = :off
|
135
|
+
Que::Worker.workers.should == []
|
136
|
+
|
137
|
+
Que.worker_count = 4
|
138
|
+
Que.worker_count.should == 4
|
139
|
+
Que::Worker.workers.should == []
|
140
|
+
|
141
|
+
Que.worker_count = 6
|
142
|
+
Que.worker_count.should == 6
|
143
|
+
Que::Worker.workers.should == []
|
144
|
+
|
145
|
+
Que.worker_count = 2
|
146
|
+
Que.worker_count.should == 2
|
147
|
+
Que::Worker.workers.should == []
|
148
|
+
|
149
|
+
Que.worker_count = 0
|
150
|
+
Que.worker_count.should == 0
|
151
|
+
Que::Worker.workers.should == []
|
152
|
+
end
|
51
153
|
end
|
52
154
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
155
|
+
describe "when the mode is :sync" do
|
156
|
+
it "should record the setting but not instantiate any workers" do
|
157
|
+
Que.worker_count.should == 0
|
158
|
+
Que.connection = nil
|
159
|
+
Que.mode = :sync
|
160
|
+
Que::Worker.workers.should == []
|
58
161
|
|
59
|
-
|
60
|
-
|
61
|
-
|
162
|
+
Que.worker_count = 4
|
163
|
+
Que.worker_count.should == 4
|
164
|
+
Que::Worker.workers.should == []
|
62
165
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
Que.worker_count.should be 1
|
67
|
-
sleep_until { Que::Worker.workers.all?(&:sleeping?) }
|
166
|
+
Que.worker_count = 6
|
167
|
+
Que.worker_count.should == 6
|
168
|
+
Que::Worker.workers.should == []
|
68
169
|
|
69
|
-
|
70
|
-
|
170
|
+
Que.worker_count = 2
|
171
|
+
Que.worker_count.should == 2
|
172
|
+
Que::Worker.workers.should == []
|
173
|
+
|
174
|
+
Que.worker_count = 0
|
175
|
+
Que.worker_count.should == 0
|
176
|
+
Que::Worker.workers.should == []
|
177
|
+
end
|
71
178
|
end
|
72
179
|
|
73
|
-
|
74
|
-
|
75
|
-
|
180
|
+
describe "when the mode is :async" do
|
181
|
+
it "should start hitting the DB when transitioning to a non-zero value" do
|
182
|
+
Que.mode = :async
|
183
|
+
Que::Job.enqueue
|
184
|
+
Que.worker_count = 4
|
185
|
+
sleep_until { Que::Worker.workers.all?(&:sleeping?) }
|
186
|
+
Que::Worker.workers.map{|w| [w.state, w.thread.status]}.should == [[:sleeping, 'sleep']] * 4
|
187
|
+
DB[:que_jobs].count.should == 0
|
188
|
+
end
|
76
189
|
|
77
|
-
|
190
|
+
it "should stop hitting the DB when transitioning to zero" do
|
191
|
+
Que.mode = :async
|
192
|
+
Que.worker_count = 4
|
193
|
+
sleep_until { Que::Worker.workers.all?(&:sleeping?) }
|
194
|
+
Que.connection = nil
|
195
|
+
Que.worker_count = 0
|
196
|
+
$logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
|
197
|
+
[['mode_change', 'async'], ['worker_count_change', '4']] + [['job_unavailable', nil]] * 4 + [['worker_count_change', '0']]
|
198
|
+
end
|
78
199
|
|
79
|
-
|
80
|
-
|
81
|
-
|
200
|
+
it "should be able to scale down the number of workers gracefully" do
|
201
|
+
Que.mode = :async
|
202
|
+
Que.worker_count = 4
|
82
203
|
|
83
|
-
|
84
|
-
|
85
|
-
|
204
|
+
workers = Que::Worker.workers.dup
|
205
|
+
workers.count.should be 4
|
206
|
+
sleep_until { Que::Worker.workers.all?(&:sleeping?) }
|
86
207
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
workers.count.should be 4
|
91
|
-
sleep_until { Que::Worker.workers.all?(&:sleeping?) }
|
208
|
+
Que.worker_count = 2
|
209
|
+
Que::Worker.workers.count.should be 2
|
210
|
+
sleep_until { Que::Worker.workers.all?(&:sleeping?) }
|
92
211
|
|
93
|
-
|
94
|
-
|
95
|
-
|
212
|
+
workers[0..1].should == Que::Worker.workers
|
213
|
+
workers[2..3].each do |worker|
|
214
|
+
worker.should be_an_instance_of Que::Worker
|
215
|
+
worker.thread.status.should == false
|
216
|
+
end
|
96
217
|
|
97
|
-
|
98
|
-
|
99
|
-
worker.should be_an_instance_of Que::Worker
|
100
|
-
worker.thread.status.should == false
|
218
|
+
$logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
|
219
|
+
[['mode_change', 'async'], ['worker_count_change', '4']] + [['job_unavailable', nil]] * 4 + [['worker_count_change', '2']]
|
101
220
|
end
|
102
221
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
Que.mode = :async
|
109
|
-
workers = Que::Worker.workers.dup
|
110
|
-
workers.count.should be 4
|
222
|
+
it "should be able to scale up the number of workers gracefully" do
|
223
|
+
Que.mode = :async
|
224
|
+
Que.worker_count = 4
|
225
|
+
workers = Que::Worker.workers.dup
|
226
|
+
workers.count.should be 4
|
111
227
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
228
|
+
sleep_until { Que::Worker.workers.all?(&:sleeping?) }
|
229
|
+
Que.worker_count = 6
|
230
|
+
Que::Worker.workers.count.should be 6
|
231
|
+
sleep_until { Que::Worker.workers.all?(&:sleeping?) }
|
116
232
|
|
117
|
-
|
233
|
+
workers.should == Que::Worker.workers[0..3]
|
118
234
|
|
119
|
-
|
120
|
-
|
235
|
+
$logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
|
236
|
+
[['mode_change', 'async'], ['worker_count_change', '4']] + [['job_unavailable', nil]] * 4 + [['worker_count_change', '6']] + [['job_unavailable', nil]] * 2
|
237
|
+
end
|
121
238
|
end
|
239
|
+
end
|
122
240
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
workers.count.should be 4
|
127
|
-
|
128
|
-
sleep_until { Que::Worker.workers.all?(&:sleeping?) }
|
241
|
+
describe "Que.wake!" do
|
242
|
+
it "when mode = :off should do nothing" do
|
243
|
+
Que.connection = nil
|
129
244
|
Que.mode = :off
|
130
|
-
Que.worker_count
|
245
|
+
Que.worker_count = 4
|
246
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
247
|
+
Que.wake!
|
248
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
249
|
+
$logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
|
250
|
+
[['mode_change', 'off'], ['worker_count_change', '4']]
|
251
|
+
end
|
131
252
|
|
132
|
-
|
133
|
-
|
253
|
+
it "when mode = :sync should do nothing" do
|
254
|
+
Que.connection = nil
|
255
|
+
Que.mode = :sync
|
256
|
+
Que.worker_count = 4
|
257
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
258
|
+
Que.wake!
|
259
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
260
|
+
$logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
|
261
|
+
[['mode_change', 'sync'], ['worker_count_change', '4']]
|
262
|
+
end
|
134
263
|
|
264
|
+
it "when mode = :async and worker_count = 0 should do nothing" do
|
265
|
+
Que.connection = nil
|
266
|
+
Que.mode = :async
|
267
|
+
Que.worker_count = 0
|
268
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
269
|
+
Que.wake!
|
270
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
135
271
|
$logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
|
136
|
-
[['mode_change', 'async'], ['worker_count_change', '
|
272
|
+
[['mode_change', 'async'], ['worker_count_change', '0']]
|
137
273
|
end
|
138
274
|
|
139
|
-
it "
|
275
|
+
it "when mode = :async and worker_count > 0 should wake up a single worker" do
|
140
276
|
Que.mode = :async
|
277
|
+
Que.worker_count = 4
|
141
278
|
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
142
279
|
|
143
280
|
BlockJob.enqueue
|
@@ -153,17 +290,55 @@ describe "Managing the Worker pool" do
|
|
153
290
|
DB[:que_jobs].count.should be 0
|
154
291
|
end
|
155
292
|
|
156
|
-
it "
|
293
|
+
it "when mode = :async and worker_count > 0 should be thread-safe" do
|
157
294
|
Que.mode = :async
|
295
|
+
Que.worker_count = 4
|
296
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
158
297
|
threads = 4.times.map { Thread.new { 100.times { Que.wake! } } }
|
159
298
|
threads.each(&:join)
|
160
299
|
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe "Que.wake_all!" do
|
303
|
+
it "when mode = :off should do nothing" do
|
304
|
+
Que.connection = nil
|
305
|
+
Que.mode = :off
|
306
|
+
Que.worker_count = 4
|
307
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
308
|
+
Que.wake_all!
|
309
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
310
|
+
$logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
|
311
|
+
[['mode_change', 'off'], ['worker_count_change', '4']]
|
312
|
+
end
|
161
313
|
|
162
|
-
it "
|
163
|
-
|
164
|
-
Que.
|
314
|
+
it "when mode = :sync should do nothing" do
|
315
|
+
Que.connection = nil
|
316
|
+
Que.mode = :sync
|
317
|
+
Que.worker_count = 4
|
318
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
319
|
+
Que.wake_all!
|
320
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
321
|
+
$logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
|
322
|
+
[['mode_change', 'sync'], ['worker_count_change', '4']]
|
323
|
+
end
|
324
|
+
|
325
|
+
it "when mode = :async and worker_count = 0 should do nothing" do
|
326
|
+
Que.connection = nil
|
327
|
+
Que.mode = :async
|
328
|
+
Que.worker_count = 0
|
329
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
330
|
+
Que.wake_all!
|
331
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
332
|
+
$logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
|
333
|
+
[['mode_change', 'async'], ['worker_count_change', '0']]
|
334
|
+
end
|
335
|
+
|
336
|
+
# This spec requires at least four connections.
|
337
|
+
it "when mode = :async and worker_count > 0 should wake up all workers" do
|
338
|
+
Que.adapter = QUE_ADAPTERS[:pond]
|
165
339
|
|
166
340
|
Que.mode = :async
|
341
|
+
Que.worker_count = 4
|
167
342
|
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
168
343
|
|
169
344
|
4.times { BlockJob.enqueue }
|
@@ -175,44 +350,14 @@ describe "Managing the Worker pool" do
|
|
175
350
|
|
176
351
|
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
177
352
|
DB[:que_jobs].count.should be 0
|
178
|
-
end if QUE_ADAPTERS[:
|
353
|
+
end if QUE_ADAPTERS[:pond]
|
179
354
|
|
180
|
-
it "
|
355
|
+
it "when mode = :async and worker_count > 0 should be thread-safe" do
|
181
356
|
Que.mode = :async
|
357
|
+
Que.worker_count = 4
|
358
|
+
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
182
359
|
threads = 4.times.map { Thread.new { 100.times { Que.wake_all! } } }
|
183
360
|
threads.each(&:join)
|
184
361
|
end
|
185
|
-
|
186
|
-
it "should wake a worker every Que.wake_interval seconds" do
|
187
|
-
Que.mode = :async
|
188
|
-
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
189
|
-
Que.wake_interval = 0.01 # 10 ms
|
190
|
-
Que::Job.enqueue
|
191
|
-
sleep_until { DB[:que_jobs].count == 0 }
|
192
|
-
end
|
193
|
-
|
194
|
-
it "should work jobs in the queue defined by QUE_QUEUE" do
|
195
|
-
begin
|
196
|
-
Que::Job.enqueue 1
|
197
|
-
Que::Job.enqueue 2, :queue => 'my_queue'
|
198
|
-
|
199
|
-
ENV['QUE_QUEUE'] = 'my_queue'
|
200
|
-
|
201
|
-
Que.mode = :async
|
202
|
-
sleep_until { Que::Worker.workers.all? &:sleeping? }
|
203
|
-
DB[:que_jobs].count.should be 1
|
204
|
-
|
205
|
-
job = DB[:que_jobs].first
|
206
|
-
job[:queue].should == ''
|
207
|
-
job[:args].should == '[1]'
|
208
|
-
ensure
|
209
|
-
ENV.delete('QUE_QUEUE')
|
210
|
-
|
211
|
-
if @worker
|
212
|
-
@worker.stop
|
213
|
-
@worker.wait_until_stopped
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
362
|
end
|
218
363
|
end
|
data/spec/unit/work_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'active_support/core_ext/date' # For the .seconds.from_now below
|
2
3
|
|
3
4
|
describe Que::Job, '.work' do
|
4
5
|
it "should pass a job's arguments to the run method and delete it from the database" do
|
@@ -237,7 +238,7 @@ describe Que::Job, '.work' do
|
|
237
238
|
|
238
239
|
it "should respect a custom retry interval" do
|
239
240
|
class RetryIntervalJob < ErrorJob
|
240
|
-
@retry_interval =
|
241
|
+
@retry_interval = 3155760000000 # 100,000 years from now
|
241
242
|
end
|
242
243
|
|
243
244
|
RetryIntervalJob.enqueue
|
@@ -251,7 +252,7 @@ describe Que::Job, '.work' do
|
|
251
252
|
job = DB[:que_jobs].first
|
252
253
|
job[:error_count].should be 1
|
253
254
|
job[:last_error].should =~ /\AErrorJob!\n/
|
254
|
-
job[:run_at].should be_within(3).of Time.now +
|
255
|
+
job[:run_at].to_f.should be_within(3).of Time.now.to_f + RetryIntervalJob.retry_interval
|
255
256
|
|
256
257
|
DB[:que_jobs].update :error_count => 5,
|
257
258
|
:run_at => Time.now - 60
|
@@ -265,7 +266,7 @@ describe Que::Job, '.work' do
|
|
265
266
|
job = DB[:que_jobs].first
|
266
267
|
job[:error_count].should be 6
|
267
268
|
job[:last_error].should =~ /\AErrorJob!\n/
|
268
|
-
job[:run_at].should be_within(3).of Time.now +
|
269
|
+
job[:run_at].to_f.should be_within(3).of Time.now.to_f + RetryIntervalJob.retry_interval
|
269
270
|
end
|
270
271
|
|
271
272
|
it "should respect a custom retry interval formula" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: que
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Hanks
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|