resque-heroku-scaler 0.3.2 → 0.4.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.
@@ -29,30 +29,21 @@ module Resque
29
29
 
30
30
  return if required == active
31
31
 
32
- log "Scale workers from #{active} to #{required}"
33
-
34
32
  if required > active
33
+ log "Scale workers from #{active} to #{required}"
35
34
  scale_workers(required)
36
35
  return
37
36
  end
37
+
38
+ return if pending?
38
39
 
39
- signal_workers
40
- stop = timeout
41
- wait_for_workers until ready_to_scale(active) or timeout?(stop)
42
- scale_workers(required)
43
-
44
- ensure
45
- resume_workers
40
+ scale_down(active)
46
41
  end
47
42
 
48
43
  def wait_for_scale
49
44
  sleep Resque::Plugins::HerokuScaler::Config.scale_interval
50
45
  end
51
46
 
52
- def wait_for_workers
53
- sleep Resque::Plugins::HerokuScaler::Config.poll_interval
54
- end
55
-
56
47
  def scale_for(pending)
57
48
  Resque::Plugins::HerokuScaler::Config.scale_for(pending)
58
49
  end
@@ -61,32 +52,62 @@ module Resque
61
52
  Resque::Plugins::HerokuScaler::Manager.workers = qty
62
53
  end
63
54
 
55
+ def scale_down(active)
56
+ log "Scale #{active} workers down"
57
+
58
+ lock
59
+
60
+ timeout = Time.now + Resque::Plugins::HerokuScaler::Config.scale_timeout
61
+ until locked == active or Time.now >= timeout
62
+ sleep Resque::Plugins::HerokuScaler::Config.poll_interval
63
+ end
64
+
65
+ scale_workers(0)
66
+
67
+ timeout = Time.now + Resque::Plugins::HerokuScaler::Config.scale_timeout
68
+ until Time.now >= timeout
69
+ if offline?
70
+ log "#{active} workers scaled down successfully"
71
+ prune
72
+ break
73
+ end
74
+ sleep Resque::Plugins::HerokuScaler::Config.poll_interval
75
+ end
76
+
77
+ ensure
78
+ unlock
79
+ end
80
+
64
81
  def workers
65
82
  Resque::Plugins::HerokuScaler::Manager.workers
66
83
  end
67
-
68
- def signal_workers
69
- Resque.redis.set(:scale, true)
84
+
85
+ def offline?
86
+ workers.zero?
70
87
  end
71
88
 
72
- def resume_workers
73
- Resque.redis.del(:scale)
89
+ def pending?
90
+ pending > 0
74
91
  end
75
92
 
76
- def timeout?(stop)
77
- Time.now >= stop
93
+ def pending
94
+ Resque.info[:pending]
78
95
  end
79
96
 
80
- def timeout
81
- Time.now + Resque::Plugins::HerokuScaler::Config.scale_timeout
97
+ def lock
98
+ Resque.lock
82
99
  end
83
100
 
84
- def pending
85
- Resque.info[:pending].to_i
101
+ def unlock
102
+ Resque.unlock
86
103
  end
87
-
88
- def ready_to_scale(active)
89
- Resque.info[:scaling] == active
104
+
105
+ def locked
106
+ Resque.info[:locked]
107
+ end
108
+
109
+ def prune
110
+ Resque.prune
90
111
  end
91
112
 
92
113
  def configure
@@ -15,7 +15,7 @@ module Resque
15
15
  end
16
16
 
17
17
  def scale_interval
18
- @scale_interval || 60
18
+ @scale_interval || 5
19
19
  end
20
20
 
21
21
  def poll_interval
@@ -1,14 +1,23 @@
1
1
  module Resque
2
2
 
3
3
  alias_method :original_info, :info
4
-
5
- def scaling
6
- Worker.scaling
7
- end
8
4
 
9
5
  def info
10
- info_with_scale = original_info
11
- info_with_scale[:scaling] = scaling.size
12
- return info_with_scale
6
+ info_with_locked = original_info
7
+ info_with_locked[:locked] = Worker.locked.size
8
+ return info_with_locked
13
9
  end
10
+
11
+ def lock
12
+ Worker.lock
13
+ end
14
+
15
+ def unlock
16
+ Worker.unlock
17
+ end
18
+
19
+ def prune
20
+ Worker.prune
21
+ end
22
+
14
23
  end
@@ -1,7 +1,7 @@
1
1
  module Resque
2
2
  module Plugins
3
3
  module HerokuScaler
4
- Version = VERSION = "0.3.2"
4
+ Version = VERSION = "0.4.0"
5
5
  end
6
6
  end
7
7
  end
@@ -1,18 +1,23 @@
1
1
  module Resque
2
-
2
+
3
3
  class Worker
4
- alias_method :original_unregister_worker, :unregister_worker
5
-
4
+
6
5
  def work(interval = 5.0, &block)
7
6
  interval = Float(interval)
8
7
  $0 = "resque: Starting"
9
8
  startup
10
9
 
11
10
  loop do
12
- wait_for_scale if scaling?
13
11
  break if shutdown?
12
+
13
+ if should_lock?
14
+ lock
15
+ break
16
+ end
17
+
18
+ pause if should_pause?
14
19
 
15
- if not paused? and job = reserve
20
+ if job = reserve(interval)
16
21
  log "got: #{job.inspect}"
17
22
  job.worker = self
18
23
  run_hook :before_fork, job
@@ -32,54 +37,57 @@ module Resque
32
37
  @child = nil
33
38
  else
34
39
  break if interval.zero?
35
- log! "Sleeping for #{interval} seconds"
40
+ log! "Timed out after #{interval} seconds"
36
41
  procline paused? ? "Paused" : "Waiting for #{@queues.join(',')}"
37
- sleep interval
38
42
  end
39
43
  end
40
44
 
41
45
  ensure
42
46
  unregister_worker
47
+ wait_for_shutdown if locked?
43
48
  end
44
49
 
45
- def wait_for_scale
46
- redis.set("scale:#{self}", true)
47
- sleep 1 while scaling? and not shutdown?
48
- redis.del("scale:#{self}")
50
+ def should_lock?
51
+ redis.exists(:lock)
49
52
  end
50
53
 
51
- def unregister_worker
52
- redis.del("scale:#{self}")
53
- original_unregister_worker
54
+ def lock
55
+ redis.sadd(:locked, self)
56
+ @locked = true
54
57
  end
55
58
 
56
- def scaling?
57
- redis.exists(:scale)
59
+ def locked?
60
+ @locked
58
61
  end
59
62
 
60
- def self.scaling
61
- names = all
62
- return [] unless names.any?
63
+ def should_unlock?
64
+ return false if should_lock?
65
+ locked?
66
+ end
63
67
 
64
- names.map! { |name| "scale:#{name}" }
68
+ def wait_for_shutdown
69
+ sleep 0.1 until shutdown? or should_unlock?
70
+ end
65
71
 
66
- reportedly_scaling = {}
72
+ def self.locked
73
+ Array(redis.smembers(:locked))
74
+ end
67
75
 
68
- begin
69
- reportedly_scaling = redis.mapped_mget(*names).reject do |key, value|
70
- value.nil? || value.empty?
71
- end
72
- rescue Redis::Distributed::CannotDistribute
73
- names.each do |name|
74
- value = redis.get name
75
- reportedly_scaling[name] = value unless value.nil? || value.empty?
76
- end
77
- end
76
+ def self.lock
77
+ redis.set(:lock, true)
78
+ end
78
79
 
79
- reportedly_scaling.keys.map do |key|
80
- find key.sub("scale:", '')
81
- end.compact
80
+ def self.unlock
81
+ redis.del(:lock)
82
+ redis.del(:locked)
82
83
  end
83
- end
84
84
 
85
- end
85
+ def self.prune
86
+ all_workers = Worker.all
87
+ all_workers.each do |worker|
88
+ worker.unregister_worker
89
+ end
90
+ end
91
+
92
+ end
93
+ end
data/test/config_test.rb CHANGED
@@ -10,7 +10,7 @@ class ConfigTest < MiniTest::Unit::TestCase
10
10
  end
11
11
 
12
12
  def test_scale_interval_default
13
- assert_equal 60, @config.scale_interval
13
+ assert_equal 5, @config.scale_interval
14
14
  end
15
15
 
16
16
  def test_poll_interval_default
@@ -44,7 +44,7 @@ class ConfigTest < MiniTest::Unit::TestCase
44
44
  def test_scale_interval
45
45
  @config.scale_interval = 99
46
46
  assert_equal 99, @config.scale_interval
47
- @config.scale_interval = 60
47
+ @config.scale_interval = 5
48
48
  end
49
49
 
50
50
  def test_poll_interval
@@ -6,8 +6,8 @@ class HerokuScalerTest < MiniTest::Unit::TestCase
6
6
  @config = Resque::Plugins::HerokuScaler::Config
7
7
 
8
8
  @redis = mock('Mock Redis')
9
- @redis.stubs(:set).with(:scale, true).returns(true)
10
- @redis.stubs(:del).with(:scale).returns(true)
9
+ @redis.stubs(:set).with(:lock, true).returns(true)
10
+ @redis.stubs(:del).with(:lock).returns(true)
11
11
  Resque.stubs(:redis).returns(@redis)
12
12
 
13
13
  @manager = mock('Mock Manager')
@@ -15,14 +15,14 @@ class HerokuScalerTest < MiniTest::Unit::TestCase
15
15
  end
16
16
 
17
17
  def test_no_scale_for_zero_jobs
18
- Resque.stubs(:info).returns({ :pending => 0, :scaling => 0 })
18
+ Resque.stubs(:info).returns({ :pending => 0, :locked => 0 })
19
19
  @manager.expects(:workers).returns(0)
20
20
  @manager.expects(:workers=).never
21
21
  @scaler.scale()
22
22
  end
23
23
 
24
24
  def test_scale_up_for_pending_job
25
- Resque.stubs(:info).returns({ :pending => 1, :scaling => 0 })
25
+ Resque.stubs(:info).returns({ :pending => 1, :locked => 0 })
26
26
  @manager.expects(:workers).returns(0)
27
27
  @manager.expects(:workers=).with(1)
28
28
  @scaler.scale()
@@ -30,17 +30,25 @@ class HerokuScalerTest < MiniTest::Unit::TestCase
30
30
 
31
31
  def test_scale_down_timeout
32
32
  @config.scale_timeout = 1
33
- Resque.stubs(:info).returns({ :pending => 0, :scaling => 0 })
33
+ Resque.stubs(:info).returns({ :pending => 0, :locked => 1 })
34
34
  @manager.expects(:workers).returns(1)
35
35
  @manager.expects(:workers=).with(0)
36
+ @scaler.expects(:offline?).returns(false)
37
+ @scaler.expects(:prune).never
38
+ @scaler.expects(:lock).returns(true)
39
+ @scaler.expects(:unlock).returns(true)
36
40
  @scaler.scale()
37
41
  @config.scale_timeout = 90
38
42
  end
39
43
 
40
44
  def test_scale_down_for_zero_jobs
41
- Resque.stubs(:info).returns({ :pending => 0, :scaling => 1 })
45
+ Resque.stubs(:info).returns({ :pending => 0, :locked => 1 })
42
46
  @manager.expects(:workers).returns(1)
43
47
  @manager.expects(:workers=).with(0)
48
+ @scaler.expects(:offline?).returns(true)
49
+ @scaler.expects(:prune).returns(true)
50
+ @scaler.expects(:lock).returns(true)
51
+ @scaler.expects(:unlock).returns(true)
44
52
  @scaler.scale()
45
53
  end
46
54
 
data/test/worker_test.rb CHANGED
@@ -1,26 +1,15 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class WorkerTest < MiniTest::Unit::TestCase
4
- def test_wait_for_scale
5
- Resque.redis.stubs(:exists).with(:scale).returns(true)
6
- Resque.redis.expects(:set).with(regexp_matches(/^scale:(.)*$/), true)
7
- Resque.redis.expects(:del).with(regexp_matches(/^scale:(.)*$/))
4
+ def test_lock
5
+ Resque.redis.stubs(:exists).with(:lock).returns(true)
6
+ Resque.redis.expects(:sadd).with(:locked, kind_of(Resque::Worker))
8
7
 
9
- worker = Resque::Worker.new(['*'])
10
- worker.stubs(:shutdown?).returns(true)
8
+ worker = Resque::Worker.new(['*'])
11
9
  worker.stubs(:register_worker).returns(true)
12
10
  worker.stubs(:unregister_worker).returns(true)
13
- worker.work(0)
14
- end
15
-
16
- def test_unregister_worker
17
- Resque.redis.expects(:del).with(regexp_matches(/^scale:(.)*$/))
18
- Resque.redis.stubs(:del).with(regexp_matches(/^worker:(.)*$/))
19
- Resque.redis.stubs(:del).with(regexp_matches(/^stat:(.)*$/))
20
-
21
- worker = Resque::Worker.new(['*'])
22
- worker.stubs(:shutdown?).returns(true)
23
- worker.stubs(:register_worker).returns(true)
11
+ worker.stubs(:shutdown?).returns(false)
12
+ worker.expects(:wait_for_shutdown).returns(true)
24
13
  worker.work(0)
25
14
  end
26
15
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-heroku-scaler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,30 +9,30 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-20 00:00:00.000000000Z
12
+ date: 2012-06-28 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: resque
16
- requirement: &70164343744920 !ruby/object:Gem::Requirement
16
+ requirement: &70351775059460 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 1.19.0
21
+ version: 1.20.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70164343744920
24
+ version_requirements: *70351775059460
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: heroku
27
- requirement: &70164343742680 !ruby/object:Gem::Requirement
27
+ requirement: &70351775058060 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
31
31
  - !ruby/object:Gem::Version
32
- version: 2.18.1
32
+ version: 2.28.7
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70164343742680
35
+ version_requirements: *70351775058060
36
36
  description: ! ' This gem provides autoscaling for Resque workers on Heroku.
37
37
 
38
38
  '