circuit_breakage 0.1.0 → 0.1.1

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: 1b30cd904f63ca99c1f7b95a89bfef2a5c29237d
4
- data.tar.gz: c55ce5e3e2bf0463175dcd2c25ace63543ef2e1e
3
+ metadata.gz: 264f3254581c4bdf631cfb997e2a66a177a19d92
4
+ data.tar.gz: 59a4cca4490e79cd3195c9a04b5f1a07a85c5e9a
5
5
  SHA512:
6
- metadata.gz: 7dfae02fe9c5876c2a1caa6454231dd33f325e9ddd6814b8a9fbc107e302fb15e44afb8cb6d55a0f09991caf6d6194ad1fc28af26387b9afc0db0d89687b6db7
7
- data.tar.gz: 4fa1879643290ddc117d4ff8a3aac6cd0137230dba40953b11f6ab468fdcab9a7e69e407e121e40ab56dc8a74e4ea697484bdaf761ebd8ae4c0c5c26a64af5cf
6
+ metadata.gz: 64348b4b18f1f35e66a1c1b45993093b36cb31ea6ddd5b2e814f32898dfe1de1e869c976a777bea536e7efea2a9051734676821b85221b5c014c1e266f01ad72
7
+ data.tar.gz: ae0ecdcedc18b541b266b6cf800a6710c487c24549f8bbbb56f8a75e8e2b56d4a9ce42e8591392ed43991b15df2e8b9b9faa6b0a6bb1935d16ba292508f2db68
@@ -11,13 +11,23 @@ module CircuitBreakage
11
11
  attr_reader :connection, :key
12
12
 
13
13
  def initialize(connection, key, block)
14
- raise NotImplementedError.new("Still working on it!")
15
-
16
14
  @connection = connection
17
15
  @key = key
18
16
  super(block)
19
17
  end
20
18
 
19
+ [:state, :failure_count, :last_failed].each do |attr|
20
+ attr_key = "#{@key}/attrs/#{attr}"
21
+
22
+ define_method(attr) do
23
+ @connection.get(attr_key)
24
+ end
25
+
26
+ define_method("#{attr}=") do |value|
27
+ @connection.set(attr_key, value)
28
+ end
29
+ end
30
+
21
31
  private
22
32
 
23
33
  def do_retry(*args)
@@ -30,13 +40,13 @@ module CircuitBreakage
30
40
  mutex_key = "#{@key}/locks/#{lock}"
31
41
 
32
42
  acquired = @connection.setnx(mutex_key, Time.now.to_i)
33
- if acquired = 0 # mutex is already acquired
43
+ if acquired == 0 # mutex is already acquired
34
44
  locked_at = @connection.get(mutex_key)
35
- return if locked_at + LOCK_TIMEOUT < Time.now.to_i # unexpired lock
45
+ raise CircuitBreakage::CircuitOpen if locked_at + LOCK_TIMEOUT < Time.now.to_i
36
46
  locked_at_second_check = @connection.getset(mutex_key, Time.now.to_i)
37
- return if locked_at_second_check != locked_at # expired lock, but somebody beat us to it
47
+ raise CircuitBreakage::CircuitOpen if locked_at_second_check != locked_at
48
+ # If we get here, then the lock is expired, and we're the first to re-acquire it.
38
49
  end
39
- # If we get this far, we have successfully acquired the mutex.
40
50
 
41
51
  begin
42
52
  block.call
@@ -45,17 +55,5 @@ module CircuitBreakage
45
55
  end
46
56
  end
47
57
 
48
- [:state, :failure_count, :last_failed].each do |attr|
49
- attr_key = "#{@key}/attrs/#{attr}"
50
-
51
- define_method(attr) do
52
- @connection.get(attr_key)
53
- end
54
-
55
- define_method("#{attr}=") do |value|
56
- @connection.set(attr_key, value)
57
- end
58
- end
59
-
60
58
  end
61
59
  end
@@ -1,3 +1,3 @@
1
1
  module CircuitBreakage
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/spec/breaker_spec.rb CHANGED
@@ -14,7 +14,7 @@ module CircuitBreakage
14
14
  let(:arg) { 'This is an argument.' }
15
15
 
16
16
  context 'when the circuit is closed' do
17
- before { breaker.closed! }
17
+ before { breaker.state = 'closed' }
18
18
 
19
19
  it 'calls the block' do
20
20
  # The default block just returns the arg.
@@ -37,7 +37,7 @@ module CircuitBreakage
37
37
  context 'and the failure count exceeds the failure threshold' do
38
38
  before { breaker.failure_count = breaker.failure_threshold }
39
39
 
40
- it { is_expected.to change { breaker.open? }.to(true) }
40
+ it { is_expected.to change { breaker.state }.to('open') }
41
41
  end
42
42
  end
43
43
 
@@ -52,7 +52,7 @@ module CircuitBreakage
52
52
  end
53
53
 
54
54
  context 'when the circuit is open' do
55
- before { breaker.open! }
55
+ before { breaker.state = 'open' }
56
56
 
57
57
  context 'before the retry_time' do
58
58
  before { breaker.last_failed = Time.now - breaker.duration + 30 }
@@ -64,62 +64,10 @@ module CircuitBreakage
64
64
  before { breaker.last_failed = Time.now - breaker.duration - 30 }
65
65
 
66
66
  it 'calls the block' do
67
- # This is the same as being half open, see below for further tests.
68
67
  expect(breaker.call(arg)).to eq arg
69
68
  end
70
69
  end
71
70
  end
72
-
73
- context 'when the circuit is half open' do
74
- before do
75
- # For the circuit to be tripped in the first place, the failure count
76
- # must have reached the failure threshold.
77
- breaker.failure_count = breaker.failure_threshold
78
- breaker.half_open!
79
- end
80
-
81
- it 'calls the block' do
82
- expect(breaker.call(arg)).to eq arg
83
- end
84
-
85
- context 'and the call succeeds' do
86
- before { breaker.failure_count = 3 }
87
-
88
- it { is_expected.to change { breaker.closed? }.to(true) }
89
- it { is_expected.to change { breaker.failure_count }.to(0) }
90
- end
91
-
92
- context 'and the call fails' do
93
- let(:block) { -> { raise 'some error' } }
94
-
95
- it { is_expected.to change { breaker.open? }.to(true) }
96
- it { is_expected.to change { breaker.last_failed } }
97
- end
98
- end
99
- end
100
-
101
- # #half_open?, #half_open!, #closed?, and #closed! are all exactly the same
102
- # as #open? and #open!, so we're just going to test the open methods.
103
-
104
- describe '#open!' do
105
- it 'opens the circuit' do
106
- breaker.open!
107
- expect(breaker).to be_open
108
- end
109
- end
110
-
111
- describe '#open?' do
112
- subject { breaker.open? }
113
-
114
- context 'when open' do
115
- before { breaker.open! }
116
- it { is_expected.to be_truthy }
117
- end
118
-
119
- context 'when not open' do
120
- before { breaker.closed! }
121
- it { is_expected.to be_falsey }
122
- end
123
71
  end
124
72
  end
125
73
  end
@@ -4,7 +4,7 @@
4
4
  module CircuitBreakage
5
5
  describe RedisBackedBreaker do
6
6
  let(:breaker) { RedisBackedBreaker.new(connection, key, block) }
7
- let(:connection) { MockCache.new }
7
+ let(:connection) { MockRedis.new }
8
8
  let(:key) { 'test/data' }
9
9
  let(:block) { ->(x) { return x } }
10
10
 
@@ -13,7 +13,7 @@ module CircuitBreakage
13
13
  let(:arg) { 'This is an argument.' }
14
14
 
15
15
  context 'when the circuit is closed' do
16
- before { breaker.closed! }
16
+ before { breaker.state = 'closed' }
17
17
 
18
18
  it 'calls the block' do
19
19
  # The default block just returns the arg.
@@ -36,7 +36,7 @@ module CircuitBreakage
36
36
  context 'and the failure count exceeds the failure threshold' do
37
37
  before { breaker.failure_count = breaker.failure_threshold }
38
38
 
39
- it { is_expected.to change { breaker.open? }.to(true) }
39
+ it { is_expected.to change { breaker.state }.to('open') }
40
40
  end
41
41
  end
42
42
 
@@ -51,7 +51,7 @@ module CircuitBreakage
51
51
  end
52
52
 
53
53
  context 'when the circuit is open' do
54
- before { breaker.open! }
54
+ before { breaker.state = 'open' }
55
55
 
56
56
  context 'before the retry_time' do
57
57
  before { breaker.last_failed = Time.now - breaker.duration + 30 }
@@ -60,81 +60,50 @@ module CircuitBreakage
60
60
  end
61
61
 
62
62
  context 'after the retry time' do
63
- before { breaker.last_failed = Time.now - breaker.duration - 30 }
64
-
65
63
  it 'calls the block' do
66
- # This is the same as being half open, see below for further tests.
64
+ breaker.last_failed = Time.at(0)
67
65
  expect(breaker.call(arg)).to eq arg
68
66
  end
69
- end
70
- end
71
-
72
- context 'when the circuit is half open' do
73
- before do
74
- # For the circuit to be tripped in the first place, the failure count
75
- # must have reached the failure threshold.
76
- breaker.failure_count = breaker.failure_threshold
77
- breaker.half_open!
78
- end
79
67
 
80
- it 'calls the block' do
81
- expect(breaker.call(arg)).to eq arg
82
- end
83
-
84
- context 'and the call succeeds' do
85
- before { breaker.failure_count = 3 }
86
-
87
- it { is_expected.to change { breaker.closed? }.to(true) }
88
- it { is_expected.to change { breaker.failure_count }.to(0) }
89
- end
90
-
91
- context 'and the call fails' do
92
- let(:block) { -> { raise 'some error' } }
93
-
94
- it { is_expected.to change { breaker.open? }.to(true) }
95
- it { is_expected.to change { breaker.last_failed } }
68
+ # TODO: It would be nice to test some of the concurrency scenarios,
69
+ # but that's pretty tricky to do politely in a test suite.
96
70
  end
97
71
  end
98
72
  end
99
-
100
- # #half_open?, #half_open!, #closed?, and #closed! are all exactly the same
101
- # as #open? and #open!, so we're just going to test the open methods.
102
-
103
- describe '#open!' do
104
- it 'opens the circuit' do
105
- breaker.open!
106
- expect(breaker).to be_open
107
- end
108
- end
109
-
110
- describe '#open?' do
111
- subject { breaker.open? }
112
-
113
- context 'when open' do
114
- before { breaker.open! }
115
- it { is_expected.to be_truthy }
116
- end
117
-
118
- context 'when not open' do
119
- before { breaker.closed! }
120
- it { is_expected.to be_falsey }
121
- end
122
- end
123
73
  end
124
74
  end
125
75
 
126
- class MockCache
76
+ class MockRedis
127
77
  attr_accessor :stored_data
128
78
 
129
79
  def initialize
130
80
  @stored_data = {}
131
81
  end
132
82
 
133
- def write(key, val)
83
+ def get(key)
84
+ @stored_data[key]
85
+ end
86
+
87
+ def set(key, val)
134
88
  @stored_data[key] = val
135
89
  end
136
90
 
137
- def fetch(key)
138
- @stored_data[key]
91
+ def setnx(key, val)
92
+ if @stored_data[key].nil?
93
+ @stored_data[key] = val
94
+ return 1
95
+ else
96
+ return 0
97
+ end
98
+ end
99
+
100
+ def getset(key, val)
101
+ old_val = @stored_data[key]
102
+ @stored_data[key] = val
103
+ return old_val
104
+ end
105
+
106
+ def del(key)
107
+ @stored_data.delete(key)
139
108
  end
140
109
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: circuit_breakage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Hyland