circuit_breakage 0.1.0 → 0.1.1

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: 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