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 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