autoscale 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +81 -0
- data/Guardfile +12 -0
- data/README.md +81 -0
- data/examples/complex.rb +39 -0
- data/examples/simple.rb +28 -0
- data/lib/autoscaler.rb +5 -0
- data/lib/autoscaler/binary_scaling_strategy.rb +26 -0
- data/lib/autoscaler/counter_cache_memory.rb +35 -0
- data/lib/autoscaler/counter_cache_redis.rb +50 -0
- data/lib/autoscaler/delayed_shutdown.rb +44 -0
- data/lib/autoscaler/heroku_scaler.rb +81 -0
- data/lib/autoscaler/ignore_scheduled_and_retrying.rb +13 -0
- data/lib/autoscaler/linear_scaling_strategy.rb +39 -0
- data/lib/autoscaler/sidekiq.rb +11 -0
- data/lib/autoscaler/sidekiq/activity.rb +62 -0
- data/lib/autoscaler/sidekiq/celluloid_monitor.rb +67 -0
- data/lib/autoscaler/sidekiq/client.rb +50 -0
- data/lib/autoscaler/sidekiq/entire_queue_system.rb +41 -0
- data/lib/autoscaler/sidekiq/monitor_middleware_adapter.rb +46 -0
- data/lib/autoscaler/sidekiq/queue_system.rb +20 -0
- data/lib/autoscaler/sidekiq/sleep_wait_server.rb +51 -0
- data/lib/autoscaler/sidekiq/specified_queue_system.rb +48 -0
- data/lib/autoscaler/stub_scaler.rb +25 -0
- data/lib/autoscaler/version.rb +4 -0
- data/spec/autoscaler/binary_scaling_strategy_spec.rb +19 -0
- data/spec/autoscaler/counter_cache_memory_spec.rb +21 -0
- data/spec/autoscaler/counter_cache_redis_spec.rb +49 -0
- data/spec/autoscaler/delayed_shutdown_spec.rb +23 -0
- data/spec/autoscaler/heroku_scaler_spec.rb +49 -0
- data/spec/autoscaler/ignore_scheduled_and_retrying_spec.rb +33 -0
- data/spec/autoscaler/linear_scaling_strategy_spec.rb +85 -0
- data/spec/autoscaler/sidekiq/activity_spec.rb +34 -0
- data/spec/autoscaler/sidekiq/celluloid_monitor_spec.rb +39 -0
- data/spec/autoscaler/sidekiq/client_spec.rb +35 -0
- data/spec/autoscaler/sidekiq/entire_queue_system_spec.rb +65 -0
- data/spec/autoscaler/sidekiq/monitor_middleware_adapter_spec.rb +16 -0
- data/spec/autoscaler/sidekiq/sleep_wait_server_spec.rb +45 -0
- data/spec/autoscaler/sidekiq/specified_queue_system_spec.rb +63 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/test_system.rb +11 -0
- metadata +187 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'heroku-api'
|
2
|
+
|
3
|
+
module Autoscaler
|
4
|
+
# A minimal scaler to use as stub for local testing
|
5
|
+
class StubScaler
|
6
|
+
# @param [String] type used to distinguish messages from multiple stubs
|
7
|
+
def initialize(type = 'worker')
|
8
|
+
@type = type
|
9
|
+
@workers = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :type
|
13
|
+
|
14
|
+
# Read the current worker count
|
15
|
+
# @return [Numeric] number of workers
|
16
|
+
attr_reader :workers
|
17
|
+
|
18
|
+
# Set the number of workers
|
19
|
+
# @param [Numeric] n number of workers
|
20
|
+
def workers=(n)
|
21
|
+
p "Scaling #{type} to #{n}"
|
22
|
+
@workers = n
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_system'
|
3
|
+
require 'autoscaler/binary_scaling_strategy'
|
4
|
+
|
5
|
+
describe Autoscaler::BinaryScalingStrategy do
|
6
|
+
let(:cut) {Autoscaler::BinaryScalingStrategy}
|
7
|
+
|
8
|
+
it "scales with no work" do
|
9
|
+
system = TestSystem.new(0)
|
10
|
+
strategy = cut.new
|
11
|
+
strategy.call(system, 1).should == 0
|
12
|
+
end
|
13
|
+
|
14
|
+
it "does not scale with pending work" do
|
15
|
+
system = TestSystem.new(1)
|
16
|
+
strategy = cut.new(2)
|
17
|
+
strategy.call(system, 1).should == 2
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'autoscaler/counter_cache_memory'
|
3
|
+
|
4
|
+
describe Autoscaler::CounterCacheMemory do
|
5
|
+
let(:cut) {Autoscaler::CounterCacheMemory}
|
6
|
+
|
7
|
+
it {expect{cut.new.counter}.to raise_error(cut::Expired)}
|
8
|
+
it {cut.new.counter{1}.should == 1}
|
9
|
+
|
10
|
+
it 'set and store' do
|
11
|
+
cache = cut.new
|
12
|
+
cache.counter = 1
|
13
|
+
cache.counter.should == 1
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'times out' do
|
17
|
+
cache = cut.new(0)
|
18
|
+
cache.counter = 1
|
19
|
+
expect{cache.counter.should}.to raise_error(cut::Expired)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'autoscaler/counter_cache_redis'
|
3
|
+
|
4
|
+
describe Autoscaler::CounterCacheRedis do
|
5
|
+
before do
|
6
|
+
@redis = Sidekiq.redis = REDIS
|
7
|
+
Sidekiq.redis {|c| c.flushdb }
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:cut) {Autoscaler::CounterCacheRedis}
|
11
|
+
subject {cut.new(Sidekiq.method(:redis))}
|
12
|
+
|
13
|
+
it {expect{subject.counter}.to raise_error(cut::Expired)}
|
14
|
+
it {subject.counter{1}.should == 1}
|
15
|
+
|
16
|
+
it 'set and store' do
|
17
|
+
subject.counter = 2
|
18
|
+
subject.counter.should == 2
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'does not conflict with multiple worker types' do
|
22
|
+
other_worker_cache = cut.new(@redis, 300, 'other_worker')
|
23
|
+
subject.counter = 1
|
24
|
+
other_worker_cache.counter = 2
|
25
|
+
|
26
|
+
subject.counter.should == 1
|
27
|
+
other_worker_cache.counter = 2
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'times out' do
|
31
|
+
cache = cut.new(Sidekiq.method(:redis), 1) # timeout 0 invalid
|
32
|
+
cache.counter = 3
|
33
|
+
sleep(2)
|
34
|
+
expect{cache.counter}.to raise_error(cut::Expired)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'passed a connection pool' do
|
38
|
+
cache = cut.new(@redis)
|
39
|
+
cache.counter = 4
|
40
|
+
cache.counter.should == 4
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'passed a plain connection' do
|
44
|
+
connection = Redis.connect(:url => 'http://localhost:9736', :namespace => 'autoscaler')
|
45
|
+
cache = cut.new connection
|
46
|
+
cache.counter = 5
|
47
|
+
cache.counter.should == 5
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_system'
|
3
|
+
require 'autoscaler/delayed_shutdown'
|
4
|
+
|
5
|
+
describe Autoscaler::DelayedShutdown do
|
6
|
+
let(:cut) {Autoscaler::DelayedShutdown}
|
7
|
+
|
8
|
+
it "returns normal values" do
|
9
|
+
strategy = cut.new(lambda{|s,t| 2}, 0)
|
10
|
+
strategy.call(nil, 1).should == 2
|
11
|
+
end
|
12
|
+
|
13
|
+
it "delays zeros" do
|
14
|
+
strategy = cut.new(lambda{|s,t| 0}, 60)
|
15
|
+
strategy.call(nil, 1).should == 1
|
16
|
+
end
|
17
|
+
|
18
|
+
it "eventually returns zero" do
|
19
|
+
strategy = cut.new(lambda{|s,t| 0}, 60)
|
20
|
+
strategy.stub(:level_idle_time).and_return(61)
|
21
|
+
strategy.call(nil, 61).should == 0
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'autoscaler/heroku_scaler'
|
3
|
+
require 'heroku/api/errors'
|
4
|
+
|
5
|
+
describe Autoscaler::HerokuScaler, :online => true do
|
6
|
+
let(:cut) {Autoscaler::HerokuScaler}
|
7
|
+
let(:client) {cut.new}
|
8
|
+
subject {client}
|
9
|
+
|
10
|
+
its(:workers) {should == 0}
|
11
|
+
|
12
|
+
describe 'scaled' do
|
13
|
+
around do |example|
|
14
|
+
client.workers = 1
|
15
|
+
example.yield
|
16
|
+
client.workers = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
its(:workers) {should == 1}
|
20
|
+
end
|
21
|
+
|
22
|
+
shared_examples 'exception handler' do |exception_class|
|
23
|
+
before do
|
24
|
+
client.should_receive(:client){
|
25
|
+
raise exception_class.new(Exception.new('oops'))
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "default handler" do
|
30
|
+
it {expect{client.workers}.to_not raise_error}
|
31
|
+
it {client.workers.should == 0}
|
32
|
+
it {expect{client.workers = 2}.to_not raise_error}
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "custom handler" do
|
36
|
+
before do
|
37
|
+
@caught = false
|
38
|
+
client.exception_handler = lambda {|exception| @caught = true}
|
39
|
+
end
|
40
|
+
|
41
|
+
it {client.workers; @caught.should be_true}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'exception handling', :focus => true do
|
46
|
+
it_behaves_like 'exception handler', Excon::Errors::SocketError
|
47
|
+
it_behaves_like 'exception handler', Heroku::API::Errors::Error
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_system'
|
3
|
+
require 'autoscaler/ignore_scheduled_and_retrying'
|
4
|
+
|
5
|
+
describe Autoscaler::IgnoreScheduledAndRetrying do
|
6
|
+
let(:cut) {Autoscaler::IgnoreScheduledAndRetrying}
|
7
|
+
|
8
|
+
it "passes through enqueued" do
|
9
|
+
system = Struct.new(:enqueued).new(3)
|
10
|
+
strategy = proc {|system, time| system.enqueued}
|
11
|
+
cut.new(strategy).call(system, 0).should == 3
|
12
|
+
end
|
13
|
+
|
14
|
+
it "passes through workers" do
|
15
|
+
system = Struct.new(:workers).new(3)
|
16
|
+
strategy = proc {|system, time| system.workers}
|
17
|
+
cut.new(strategy).call(system, 0).should == 3
|
18
|
+
end
|
19
|
+
|
20
|
+
it "ignores scheduled" do
|
21
|
+
system = Struct.new(:scheduled).new(3)
|
22
|
+
strategy = proc {|system, time| system.scheduled}
|
23
|
+
cut.new(strategy).call(system, 0).should == 0
|
24
|
+
end
|
25
|
+
|
26
|
+
it "ignores retrying" do
|
27
|
+
system = Struct.new(:retrying).new(3)
|
28
|
+
strategy = proc {|system, time| system.retrying}
|
29
|
+
cut.new(strategy).call(system, 0).should == 0
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_system'
|
3
|
+
require 'autoscaler/linear_scaling_strategy'
|
4
|
+
|
5
|
+
describe Autoscaler::LinearScalingStrategy do
|
6
|
+
let(:cut) {Autoscaler::LinearScalingStrategy}
|
7
|
+
|
8
|
+
it "deactivates with no work" do
|
9
|
+
system = TestSystem.new(0)
|
10
|
+
strategy = cut.new(1)
|
11
|
+
strategy.call(system, 1).should == 0
|
12
|
+
end
|
13
|
+
|
14
|
+
it "activates with some work" do
|
15
|
+
system = TestSystem.new(1)
|
16
|
+
strategy = cut.new(1)
|
17
|
+
strategy.call(system, 1).should be > 0
|
18
|
+
end
|
19
|
+
|
20
|
+
it "minimally scales with minimal work" do
|
21
|
+
system = TestSystem.new(1)
|
22
|
+
strategy = cut.new(2, 2)
|
23
|
+
strategy.call(system, 1).should == 1
|
24
|
+
end
|
25
|
+
|
26
|
+
it "maximally scales with too much work" do
|
27
|
+
system = TestSystem.new(5)
|
28
|
+
strategy = cut.new(2, 2)
|
29
|
+
strategy.call(system, 1).should == 2
|
30
|
+
end
|
31
|
+
|
32
|
+
it "proportionally scales with some work" do
|
33
|
+
system = TestSystem.new(5)
|
34
|
+
strategy = cut.new(5, 2)
|
35
|
+
strategy.call(system, 1).should == 3
|
36
|
+
end
|
37
|
+
|
38
|
+
it "doesn't scale unless minimum is met" do
|
39
|
+
system = TestSystem.new(2)
|
40
|
+
strategy = cut.new(10, 4, 0.5)
|
41
|
+
strategy.call(system, 1).should == 0
|
42
|
+
end
|
43
|
+
|
44
|
+
it "scales proprotionally with a minimum" do
|
45
|
+
system = TestSystem.new(3)
|
46
|
+
strategy = cut.new(10, 4, 0.5)
|
47
|
+
strategy.call(system, 1).should == 1
|
48
|
+
end
|
49
|
+
|
50
|
+
it "scales maximally with a minimum" do
|
51
|
+
system = TestSystem.new(25)
|
52
|
+
strategy = cut.new(5, 4, 0.5)
|
53
|
+
strategy.call(system, 1).should == 5
|
54
|
+
end
|
55
|
+
|
56
|
+
it "scales proportionally with a minimum > 1" do
|
57
|
+
system = TestSystem.new(12)
|
58
|
+
strategy = cut.new(5, 4, 2)
|
59
|
+
strategy.call(system, 1).should == 2
|
60
|
+
end
|
61
|
+
|
62
|
+
it "scales maximally with a minimum factor > 1" do
|
63
|
+
system = TestSystem.new(30)
|
64
|
+
strategy = cut.new(5, 4, 2)
|
65
|
+
strategy.call(system, 1).should == 5
|
66
|
+
end
|
67
|
+
|
68
|
+
it "doesn't scale down engaged workers" do
|
69
|
+
system = TestSystem.new(0, 2)
|
70
|
+
strategy = cut.new(5, 4)
|
71
|
+
strategy.call(system, 1).should == 2
|
72
|
+
end
|
73
|
+
|
74
|
+
it "doesn't scale above max workers even if engaged workers is greater" do
|
75
|
+
system = TestSystem.new(40, 6)
|
76
|
+
strategy = cut.new(5, 4)
|
77
|
+
strategy.call(system, 1).should == 5
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns zero if requested capacity is zero" do
|
81
|
+
system = TestSystem.new(0, 0)
|
82
|
+
strategy = cut.new(0, 0)
|
83
|
+
strategy.call(system, 5).should == 0
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'autoscaler/sidekiq/activity'
|
3
|
+
|
4
|
+
describe Autoscaler::Sidekiq::Activity do
|
5
|
+
before do
|
6
|
+
@redis = Sidekiq.redis = REDIS
|
7
|
+
Sidekiq.redis {|c| c.flushdb }
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:cut) {Autoscaler::Sidekiq::Activity}
|
11
|
+
let(:activity) {cut.new(0)}
|
12
|
+
|
13
|
+
context "when another process is working" do
|
14
|
+
let(:other_process) {cut.new(10)}
|
15
|
+
before do
|
16
|
+
activity.idle!('queue')
|
17
|
+
other_process.working!('other_queue')
|
18
|
+
end
|
19
|
+
it {activity.should be_idle(['queue'])}
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'passed a connection pool' do
|
23
|
+
activity = cut.new(5, @redis)
|
24
|
+
activity.working!('queue')
|
25
|
+
activity.should_not be_idle(['queue'])
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'passed a plain connection' do
|
29
|
+
connection = Redis.connect(:url => 'http://localhost:9736', :namespace => 'autoscaler')
|
30
|
+
activity = cut.new(5, connection)
|
31
|
+
activity.working!('queue')
|
32
|
+
activity.should_not be_idle(['queue'])
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_system'
|
3
|
+
require 'autoscaler/sidekiq/celluloid_monitor'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
describe Autoscaler::Sidekiq::CelluloidMonitor do
|
7
|
+
before do
|
8
|
+
@redis = Sidekiq.redis = REDIS
|
9
|
+
Sidekiq.redis {|c| c.flushdb }
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:cut) {Autoscaler::Sidekiq::CelluloidMonitor}
|
13
|
+
let(:scaler) {TestScaler.new(1)}
|
14
|
+
|
15
|
+
it "scales with no work" do
|
16
|
+
system = TestSystem.new(0)
|
17
|
+
manager = cut.new(scaler, lambda{|s,t| 0}, system)
|
18
|
+
Timeout.timeout(1) { manager.wait_for_downscale(0.5) }
|
19
|
+
scaler.workers.should == 0
|
20
|
+
manager.terminate
|
21
|
+
end
|
22
|
+
|
23
|
+
it "does not scale with pending work" do
|
24
|
+
system = TestSystem.new(1)
|
25
|
+
manager = cut.new(scaler, lambda{|s,t| 1}, system)
|
26
|
+
expect {Timeout.timeout(1) { manager.wait_for_downscale(0.5) }}.to raise_error Timeout::Error
|
27
|
+
scaler.workers.should == 1
|
28
|
+
manager.terminate
|
29
|
+
end
|
30
|
+
|
31
|
+
it "will downscale with initial workers zero" do
|
32
|
+
system = TestSystem.new(0)
|
33
|
+
scaler = TestScaler.new(0)
|
34
|
+
manager = cut.new(scaler, lambda{|s,t| 0}, system)
|
35
|
+
Timeout.timeout(1) { manager.wait_for_downscale(0.5) }
|
36
|
+
scaler.workers.should == 0
|
37
|
+
manager.terminate
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_system'
|
3
|
+
require 'autoscaler/sidekiq/client'
|
4
|
+
|
5
|
+
describe Autoscaler::Sidekiq::Client do
|
6
|
+
let(:cut) {Autoscaler::Sidekiq::Client}
|
7
|
+
let(:scaler) {TestScaler.new(0)}
|
8
|
+
let(:client) {cut.new('queue' => scaler)}
|
9
|
+
|
10
|
+
describe 'call' do
|
11
|
+
it 'scales' do
|
12
|
+
client.call(Class, {}, 'queue') {}
|
13
|
+
scaler.workers.should == 1
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'scales with a redis pool' do
|
17
|
+
client.call(Class, {}, 'queue', ::Sidekiq.method(:redis)) {}
|
18
|
+
scaler.workers.should == 1
|
19
|
+
end
|
20
|
+
|
21
|
+
it('yields') {client.call(Class, {}, 'queue') {:foo}.should == :foo}
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'initial workers' do
|
25
|
+
it 'works with default arguments' do
|
26
|
+
client.set_initial_workers
|
27
|
+
scaler.workers.should == 0
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'scales when necessary' do
|
31
|
+
client.set_initial_workers {|q| TestSystem.new(1)}
|
32
|
+
scaler.workers.should == 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'autoscaler/sidekiq/entire_queue_system'
|
3
|
+
|
4
|
+
describe Autoscaler::Sidekiq::EntireQueueSystem do
|
5
|
+
let(:cut) {Autoscaler::Sidekiq::EntireQueueSystem}
|
6
|
+
|
7
|
+
before do
|
8
|
+
@redis = Sidekiq.redis = REDIS
|
9
|
+
Sidekiq.redis {|c| c.flushdb }
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_work_in_set(queue, set)
|
13
|
+
payload = Sidekiq.dump_json('queue' => queue)
|
14
|
+
Sidekiq.redis { |c| c.zadd(set, (Time.now.to_f + 30.to_f).to_s, payload)}
|
15
|
+
end
|
16
|
+
|
17
|
+
def with_scheduled_work_in(queue)
|
18
|
+
with_work_in_set(queue, 'schedule')
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_retry_work_in(queue)
|
22
|
+
with_work_in_set(queue, 'retry')
|
23
|
+
end
|
24
|
+
|
25
|
+
subject {cut.new}
|
26
|
+
|
27
|
+
it {subject.queue_names.should == []}
|
28
|
+
it {subject.workers.should == 0}
|
29
|
+
|
30
|
+
describe 'no queued work' do
|
31
|
+
it "with no work" do
|
32
|
+
subject.stub(:sidekiq_queues).and_return({'queue' => 0, 'another_queue' => 0})
|
33
|
+
subject.queued.should == 0
|
34
|
+
end
|
35
|
+
|
36
|
+
it "with no work and no queues" do
|
37
|
+
subject.queued.should == 0
|
38
|
+
end
|
39
|
+
|
40
|
+
it "with no scheduled work" do
|
41
|
+
subject.scheduled.should == 0
|
42
|
+
end
|
43
|
+
|
44
|
+
it "with no retry work" do
|
45
|
+
subject.retrying.should == 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'with queued work' do
|
50
|
+
it "with enqueued work" do
|
51
|
+
subject.stub(:sidekiq_queues).and_return({'queue' => 1})
|
52
|
+
subject.queued.should == 1
|
53
|
+
end
|
54
|
+
|
55
|
+
it "with schedule work" do
|
56
|
+
with_scheduled_work_in('queue')
|
57
|
+
subject.scheduled.should == 1
|
58
|
+
end
|
59
|
+
|
60
|
+
it "with retry work" do
|
61
|
+
with_retry_work_in('queue')
|
62
|
+
subject.retrying.should == 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|