autoscaler 0.11.0 → 0.12.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: 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