que 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 321a6fede008be064052ea5d884a489e47414869
4
- data.tar.gz: 8dc8a7f432dd4329c86aeef470d8d396e60b82e6
3
+ metadata.gz: f0030251d843d06847bfb8e9e12658b952edbdf2
4
+ data.tar.gz: 455c5a6732b86d0e3ade3c961613d206160f9fd9
5
5
  SHA512:
6
- metadata.gz: 416d2717f3f4fad284cd9a3852717cb04c0ed86d7cbafa6ad88c27a120294f88a5034c2ae80143ca87a3ff4736c0e209bca64ad295f726db2b6d6292a3a3a3d1
7
- data.tar.gz: 780e42518a559ea84efe70a42d58378fd22b9a023d4e9e1a14ceb74ba12e981d877303f029f2c319a510415c3f430ba290549b9d14c42c258e3ffff3855afbc5
6
+ metadata.gz: 669370c39a3b68c3cdc9ff6c9d8ebdd865690451175bc84062696a961a5cf5e3dbe81761fb97c08892731895ebd398001b73aed4158c99010bcdd2707bd10073
7
+ data.tar.gz: 8c7270f5fc20dee14e28a69468cd01a348e7e24c358ba48fb19a1c2d8562e3d05b747649a800edefda5a4d68fcf5a2c623ba1ffc09441dbdf20bed53c32c97c9
@@ -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
@@ -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
- # Reestablish your database connection, etc...
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 :logger, :error_handler
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
- logger.send level, output
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
@@ -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
 
@@ -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::integer * '1 second'::interval,
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
@@ -1,3 +1,3 @@
1
1
  module Que
2
- Version = '0.7.3'
2
+ Version = '0.8.0'
3
3
  end
@@ -71,30 +71,33 @@ module Que
71
71
 
72
72
  def work_loop
73
73
  loop do
74
- time = Time.now
75
- cycle = nil
76
- result = Job.work(queue)
77
-
78
- case result[:event]
79
- when :job_unavailable
80
- cycle = false
81
- result[:level] = :debug
82
- when :job_race_condition
83
- cycle = true
84
- result[:level] = :debug
85
- when :job_worked
86
- cycle = true
87
- result[:elapsed] = (Time.now - time).round(5)
88
- when :job_errored
89
- # For PG::Errors, assume we had a problem reaching the database, and
90
- # don't hit it again right away.
91
- cycle = !result[:error].is_a?(PG::Error)
92
- result[:error] = {:class => result[:error].class.to_s, :message => result[:error].message}
93
- else
94
- raise "Unknown Event: #{result[:event].inspect}"
95
- end
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
- Que.log(result)
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
- case set_mode(mode)
119
- when :async
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
- def workers
127
- @workers ||= []
132
+ if mode == :async
133
+ set_up_workers
134
+ wrangler
135
+ end
128
136
  end
129
137
 
130
138
  def worker_count=(count)
131
- set_mode(count > 0 ? :async : :off)
132
- set_worker_count(count)
133
- wrangler # Make sure the wrangler thread has been instantiated.
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 worker_count
137
- workers.count
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.
@@ -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
- DB[:que_jobs].delete
105
+ Que.worker_count = 0
101
106
  Que.mode = :off
102
107
  Que.wake_interval = nil
103
- sleep_until { Que::Worker.workers.all?(&:sleeping?) }
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?
@@ -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
@@ -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 2
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 = :sync" do
19
- it "should make jobs run in the same thread as they are queued" do
20
- Que.mode = :sync
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
- ArgsJob.enqueue(5, :testing => "synchronous").should be_an_instance_of ArgsJob
23
- $passed_args.should == [5, {:testing => "synchronous"}]
24
- DB[:que_jobs].count.should be 0
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
- it "should work fine with enqueuing jobs without a DB connection" do
28
- Que.connection = nil
29
- Que.mode = :sync
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
- ArgsJob.enqueue(5, :testing => "synchronous").should be_an_instance_of ArgsJob
32
- $passed_args.should == [5, {:testing => "synchronous"}]
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
- it "should not affect jobs that are queued with specific run_ats" do
36
- Que.mode = :sync
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
- ArgsJob.enqueue(5, :testing => "synchronous", :run_at => Time.now + 60)
39
- DB[:que_jobs].select_map(:job_class).should == ["ArgsJob"]
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.mode = :async" do
44
- it "should spin up 4 workers" do
45
- Que.mode = :async
46
- Que.worker_count.should be 4
47
- sleep_until { Que::Worker.workers.all?(&:sleeping?) }
48
-
49
- $logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
50
- [['mode_change', 'async'], ['worker_count_change', '4']] + [['job_unavailable', nil]] * 4
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
- it "should be done automatically when setting a worker count" do
54
- Que.worker_count = 2
55
- Que.mode.should == :async
56
- Que.worker_count.should == 2
57
- sleep_until { Que::Worker.workers.all?(&:sleeping?) }
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
- $logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
60
- [['mode_change', 'async'], ['worker_count_change', '2']] + [['job_unavailable', nil]] * 2
61
- end
162
+ Que.worker_count = 4
163
+ Que.worker_count.should == 4
164
+ Que::Worker.workers.should == []
62
165
 
63
- it "should not affect the number of workers if a worker_count has already been set" do
64
- Que.worker_count = 1
65
- Que.mode = :async
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
- $logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
70
- [['mode_change', 'async'], ['worker_count_change', '1'], ['job_unavailable', nil]]
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
- it "then Que.worker_count = 0 should set the mode to :off" do
74
- Que.mode = :async
75
- Que.worker_count.should be 4
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
- sleep_until { Que::Worker.workers.all?(&:sleeping?) }
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
- Que.worker_count = 0
80
- Que.worker_count.should == 0
81
- Que.mode.should == :off
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
- $logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
84
- [['mode_change', 'async'], ['worker_count_change', '4']] + [['job_unavailable', nil]] * 4 + [['mode_change', 'off'], ['worker_count_change', '0']]
85
- end
204
+ workers = Que::Worker.workers.dup
205
+ workers.count.should be 4
206
+ sleep_until { Que::Worker.workers.all?(&:sleeping?) }
86
207
 
87
- it "then Que.worker_count = 2 should gracefully decrease the number of workers" do
88
- Que.mode = :async
89
- workers = Que::Worker.workers.dup
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
- Que.worker_count = 2
94
- Que.worker_count.should be 2
95
- sleep_until { Que::Worker.workers.all?(&:sleeping?) }
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
- workers[0..1].should == Que::Worker.workers
98
- workers[2..3].each do |worker|
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
- $logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
104
- [['mode_change', 'async'], ['worker_count_change', '4']] + [['job_unavailable', nil]] * 4 + [['worker_count_change', '2']]
105
- end
106
-
107
- it "then Que.worker_count = 6 should gracefully increase the number of workers" do
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
- sleep_until { Que::Worker.workers.all?(&:sleeping?) }
113
- Que.worker_count = 6
114
- Que.worker_count.should be 6
115
- sleep_until { Que::Worker.workers.all?(&:sleeping?) }
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
- workers.should == Que::Worker.workers[0..3]
233
+ workers.should == Que::Worker.workers[0..3]
118
234
 
119
- $logger.messages.map{|m| JSON.load(m).values_at('event', 'value')}.should ==
120
- [['mode_change', 'async'], ['worker_count_change', '4']] + [['job_unavailable', nil]] * 4 + [['worker_count_change', '6']] + [['job_unavailable', nil]] * 2
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
- it "then Que.mode = :off should gracefully shut down workers" do
124
- Que.mode = :async
125
- workers = Que::Worker.workers.dup
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.should be 0
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
- workers.count.should be 4
133
- workers.each { |worker| worker.thread.status.should be false }
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', '4']] + [['job_unavailable', nil]] * 4 + [['mode_change', 'off'], ['worker_count_change', '0']]
272
+ [['mode_change', 'async'], ['worker_count_change', '0']]
137
273
  end
138
274
 
139
- it "then Que.wake! should wake up a single worker" do
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 "then Que.wake! should be thread-safe" do
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 "then Que.wake_all! should wake up all workers" do
163
- # This spec requires at least four connections.
164
- Que.adapter = QUE_ADAPTERS[:connection_pool]
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[:connection_pool]
353
+ end if QUE_ADAPTERS[:pond]
179
354
 
180
- it "then Que.wake_all! should be thread-safe" do
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
@@ -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 = 5
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 + 5
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 + 5
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.7.3
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-05-19 00:00:00.000000000 Z
11
+ date: 2014-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler