autoscaler 0.11.0 → 0.12.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: c7c64a64f5b36a7615b3fc8587d78ef94f608649
4
- data.tar.gz: bd6f84ada4beb4cd9a6d933809da089aeeee6ae7
3
+ metadata.gz: f7f8b0bdbb93db7c32f3b0c3f4fe3f55902eed07
4
+ data.tar.gz: 379df2b6120350848c660040c705c22e11c1d1c3
5
5
  SHA512:
6
- metadata.gz: e5ae3abf03d4ce9dc2c1bf7d7510f4d4c1b132e5f180dac7882630bd9a0bcf36ba43baf43338896b980909215ee0cadd66687ea438561aa880f9ec84f702cedb
7
- data.tar.gz: 6f26a32336472672ee927d55578c4ce1da108163d58ed4c7494b92ea5c3e2c027d6013d42dab4e05d151b1a7772f1d82e717e3f7ce80ad6794204842219c13bf
6
+ metadata.gz: 1f7c387a19bf6908ab7264aaff7566ca8b905c6957e7ffde3d55e6b77e27fa982434e872c66127cf980b618785dc1119ed49577f02574d937bd6e03f62e4b841
7
+ data.tar.gz: 6353aeb2213fc015299017b73c82b6a53b1f328b90dbe221593390c6ede3e3880c9b1884fefbd0c7aec118f937ea16cda48696ef80f2b2415b9c1ecdce984ad1
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0
4
+
5
+ - Rewrite ThreadServer use a singleton monitor thread; it was incorrectly creating one per middleware instance, which was per-job
6
+
3
7
  ## 0.11.0
4
8
 
5
9
  - Replace celluloid monitor with thread based middleware for Sidekiq 4.0
@@ -8,7 +12,7 @@
8
12
  ## 0.10.0
9
13
 
10
14
  - Require Sidekiq 3.5
11
- - You may use `HerokuPlatformScaler` and `HEROKU_ACCESS_TOKEN` in place of `HerkouScaler` and `HEROKU_API_KEY`
15
+ - You may use `HerokuPlatformScaler` and `HEROKU_ACCESS_TOKEN` in place of `HerokuScaler` and `HEROKU_API_KEY`
12
16
  - QueueSystem#workers returns the number of engaged SK processes.
13
17
  - Linear Scaling Strategy will not scale down past number of active workers. Assumes 1-1 SK process/dyno mapping.
14
18
  - Calls the SideKiq quiet api when shutting down
data/README.md CHANGED
@@ -17,7 +17,7 @@ This gem uses the [Heroku Platform-Api](https://github.com/heroku/platform-api)
17
17
  HEROKU_ACCESS_TOKEN=.....
18
18
  HEROKU_APP=....
19
19
 
20
- Support is still present for [Heroku-Api](https://github.com/heroku/heroku.rb) via `HerkouScaler` and `HEROKU_API_KEY`, but may be removed in a future major version.
20
+ Support is still present for [Heroku-Api](https://github.com/heroku/heroku.rb) via `HerokuScaler` and `HEROKU_API_KEY`, but may be removed in a future major version.
21
21
 
22
22
  Install the middleware in your `Sidekiq.configure_` blocks
23
23
 
@@ -6,7 +6,8 @@ require 'thread'
6
6
  module Autoscaler
7
7
  module Sidekiq
8
8
  # Sidekiq server middleware
9
- # spawns a thread to monitor the sidekiq server for scale-down
9
+ # Sidekiq creates a new instance of the middleware for each worker
10
+ # spawns a Monitor thread to monitor the sidekiq server for scale-down
10
11
  class ThreadServer
11
12
  # @param [scaler] scaler object that actually performs scaling operations (e.g. {HerokuPlatformScaler})
12
13
  # @param [Strategy,Numeric] timeout strategy object that determines target workers, or a timeout in seconds to be passed to {DelayedShutdown}+{BinaryScalingStrategy}
@@ -15,76 +16,101 @@ module Autoscaler
15
16
  @scaler = scaler
16
17
  @strategy = strategy(timeout)
17
18
  @system = QueueSystem.new(specified_queues)
18
- @mutex = Mutex.new
19
- @done = false
20
19
  end
21
20
 
22
21
  # Sidekiq middleware api entry point
23
22
  def call(worker, msg, queue, _ = nil)
24
23
  yield
25
24
  ensure
26
- active_now!
27
- wait_for_downscale
25
+ monitor.active_now!
26
+ monitor.wait_for_downscale(@scaler, @strategy, @system)
28
27
  end
29
28
 
30
- # Start the monitoring thread if it's not running
31
- def wait_for_downscale
32
- @thread ||= Thread.new do
33
- begin
34
- run
35
- rescue
36
- @thread = nil
37
- end
29
+ private
30
+
31
+ def strategy(timeout)
32
+ if timeout.respond_to?(:call)
33
+ timeout
34
+ else
35
+ DelayedShutdown.new(BinaryScalingStrategy.new, timeout)
38
36
  end
39
37
  end
40
38
 
41
- # Thread core loop
42
- # Periodically update the desired number of workers
43
- # @param [Numeric] interval polling interval, mostly for testing
44
- def run(interval = 15)
45
- active_now!
39
+ # Manages the single thread that watches for scaledown
40
+ # Singleton (see bottom) but instanceable for testing
41
+ class Monitor
42
+ def initialize
43
+ @mutex = Mutex.new
44
+ @done = false
45
+ end
46
+
47
+ # Start the monitoring thread if it's not running
48
+ # @param [scaler] scaler object that actually performs scaling operations (e.g. {HerokuPlatformScaler})
49
+ # @param [Strategy] strategy object that determines target workers
50
+ # @param [QueueSystem] system queue system that determines which sidekiq queues are watched
51
+ def wait_for_downscale(scaler, strategy, system)
52
+ @thread ||= Thread.new do
53
+ begin
54
+ run(scaler, strategy, system)
55
+ rescue
56
+ @thread = nil
57
+ end
58
+ end
59
+ end
46
60
 
47
- workers = :unknown
61
+ # Thread core loop
62
+ # Periodically update the desired number of workers
63
+ # @param [scaler] scaler object that actually performs scaling operations (e.g. {HerokuPlatformScaler})
64
+ # @param [Strategy] strategy object that determines target workers
65
+ # @param [QueueSystem] system queue system that determines which sidekiq queues are watched
66
+ # @param [Numeric] interval polling interval, mostly for testing
67
+ def run(scaler, strategy, system, interval = 15)
68
+ active_now!
48
69
 
49
- begin
50
- sleep(interval)
51
- target_workers = @strategy.call(@system, idle_time)
52
- workers = @scaler.workers = target_workers unless workers == target_workers
53
- end while !@done && workers > 0
54
- ::Sidekiq::ProcessSet.new.each(&:quiet!)
55
- end
70
+ workers = :unknown
56
71
 
57
- # Shut down the thread, pause until complete
58
- def terminate
59
- @done = true
60
- if @thread
61
- t = @thread
62
- @thread = nil
63
- t.value
72
+ begin
73
+ sleep(interval)
74
+ target_workers = strategy.call(system, idle_time)
75
+ workers = scaler.workers = target_workers unless workers == target_workers
76
+ end while !@done && workers > 0
77
+ ::Sidekiq::ProcessSet.new.each(&:quiet!)
64
78
  end
65
- end
66
79
 
67
- private
80
+ # Shut down the thread, pause until complete
81
+ def terminate
82
+ @done = true
83
+ if @thread
84
+ t = @thread
85
+ @thread = nil
86
+ t.value
87
+ end
88
+ end
68
89
 
69
- def active_now!
70
- @mutex.synchronize do
71
- @activity = Time.now
90
+ # Record last active time
91
+ def active_now!
92
+ @mutex.synchronize do
93
+ @activity = Time.now
94
+ end
72
95
  end
73
- end
74
96
 
75
- def idle_time
76
- @mutex.synchronize do
77
- Time.now - @activity
97
+ private
98
+
99
+ def idle_time
100
+ @mutex.synchronize do
101
+ Time.now - @activity
102
+ end
78
103
  end
79
104
  end
80
105
 
81
- def strategy(timeout)
82
- if timeout.respond_to?(:call)
83
- timeout
84
- else
85
- DelayedShutdown.new(BinaryScalingStrategy.new, timeout)
86
- end
106
+ Singleton = Monitor.new
107
+
108
+ def monitor
109
+ Singleton
87
110
  end
111
+
112
+ private_constant :Monitor
113
+ private_constant :Singleton
88
114
  end
89
115
  end
90
116
  end
@@ -1,4 +1,4 @@
1
1
  module Autoscaler
2
2
  # version number
3
- VERSION = "0.11.0"
3
+ VERSION = "0.12.0"
4
4
  end
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'autoscaler/sidekiq/queue_system'
2
3
  require 'autoscaler/sidekiq/thread_server'
3
4
  require 'timeout'
4
5
 
@@ -8,35 +9,55 @@ describe Autoscaler::Sidekiq::ThreadServer do
8
9
  Sidekiq.redis {|c| c.flushdb }
9
10
  end
10
11
 
11
- let(:cut) {Autoscaler::Sidekiq::ThreadServer}
12
+ let(:middleware) {Autoscaler::Sidekiq::ThreadServer}
13
+ let(:monitor) {Autoscaler::Sidekiq::ThreadServer.const_get(:Monitor)}
12
14
  let(:scaler) {TestScaler.new(1)}
15
+ let(:strategy0) {lambda{|s,t| 0}}
16
+ let(:strategy1) {lambda{|s,t| 1}}
17
+ let(:system) {Autoscaler::Sidekiq::QueueSystem.new}
13
18
 
14
19
  describe "thread core" do
15
20
  it "scales with no work" do
16
- server = cut.new(scaler, lambda{|s,t| 0})
17
- Timeout.timeout(1) { server.run(0.5) }
21
+ server = monitor.new
22
+ Timeout.timeout(1) { server.run(scaler, strategy0, system, 0.5) }
18
23
  expect(scaler.workers).to eq 0
19
24
  server.terminate
20
25
  end
21
26
 
22
27
  it "does not scale with pending work" do
23
- server = cut.new(scaler, lambda{|s,t| 1})
24
- expect {Timeout.timeout(1) { server.run(0.5) }}.to raise_error Timeout::Error
28
+ server = monitor.new
29
+ expect {Timeout.timeout(1) { server.run(scaler, strategy1, system, 0.5) }}.to raise_error Timeout::Error
25
30
  expect(scaler.workers).to eq 1
26
31
  server.terminate
27
32
  end
28
33
 
29
34
  it "will downscale with initial workers zero" do
30
35
  scaler = TestScaler.new(0)
31
- server = cut.new(scaler, lambda{|s,t| 0})
32
- Timeout.timeout(1) { server.run(0.5) }
36
+ server = monitor.new
37
+ Timeout.timeout(1) { server.run(scaler, strategy0, system, 0.5) }
33
38
  expect(scaler.workers).to eq 0
34
39
  server.terminate
35
40
  end
41
+
42
+ it "only starts a single thread " do
43
+ scaler = TestScaler.new(0)
44
+ mon = monitor.new
45
+ $counter = 0
46
+ def mon.run(a, b, c, i = 15)
47
+ $counter += 1
48
+ end
49
+ t1 = Thread.new { mon.wait_for_downscale(scaler, strategy0, system) }
50
+ t2 = Thread.new { mon.wait_for_downscale(scaler, strategy0, system) }
51
+ sleep(0.1)
52
+ t1.value
53
+ t2.value
54
+ expect($counter).to eq 1
55
+ mon.terminate
56
+ end
36
57
  end
37
58
 
38
59
  describe "Middleware interface" do
39
- let(:server) {cut.new(scaler, 0, ['queue'])}
60
+ let(:server) {middleware.new(scaler, 0, ['queue'])}
40
61
 
41
62
  it('yields') {expect(server.call(Object.new, {}, 'queue') {:foo}).to eq :foo}
42
63
  it('yields with a redis pool') {expect(server.call(Object.new, {}, 'queue', Sidekiq.method(:redis)) {:foo}).to eq :foo}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: autoscaler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Love
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-11-17 00:00:00.000000000 Z
12
+ date: 2016-11-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sidekiq