Skip to content

Commit 3d983bb

Browse files
Merge pull request #27 from luizalabs/ro-circuit_breaker_race_condition-fix
Delete failure cache key when circuit opens
2 parents 145cc75 + 21f5615 commit 3d983bb

File tree

3 files changed

+32
-1
lines changed

3 files changed

+32
-1
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ python:
33
- "2.7"
44
- "3.4"
55
- "3.5"
6+
- "3.6"
67

78
cache:
89
directories:

django_toolkit/fallbacks/circuit_breaker.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ def total_failures(self):
3636
def open_circuit(self):
3737
self.cache.set(self.circuit_cache_key, True, self.circuit_timeout)
3838

39+
# Delete the cache key to mitigate multiple sequentials openings
40+
# when a key is created accidentally without timeout (from an incr
41+
# operation)
42+
self.cache.delete(self.failure_cache_key)
43+
3944
logger.critical(
4045
'Open circuit for {failure_cache_key} {cicuit_cache_key}'.format(
4146
failure_cache_key=self.failure_cache_key,
@@ -68,6 +73,8 @@ def __exit__(self, exc_type, exc_value, traceback):
6873
raise self.max_failure_exception
6974

7075
def _increase_failure_count(self):
76+
# Between the cache.add and cache.incr, the cache MAY expire,
77+
# which will lead to a circuit that will eventually open
7178
self.cache.add(self.failure_cache_key, 0, self.max_failure_timeout)
7279
total = self.cache.incr(self.failure_cache_key)
7380

tests/fallbacks/test_circuit_breaker.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ def fail_function():
2020

2121
class TestCircuitBreaker:
2222

23+
@pytest.fixture(autouse=True)
24+
def clear_cache(self):
25+
cache.clear()
26+
2327
def test_success_result(self):
2428
with CircuitBreaker(
2529
cache=cache,
@@ -113,4 +117,23 @@ def test_should_not_increment_fail_when_circuit_is_open(self):
113117

114118
fail_function()
115119

116-
assert cache.get(failure_cache_key) == max_failures
120+
assert not cache.get(failure_cache_key)
121+
122+
def test_should_delete_count_key_when_circuit_is_open(self):
123+
failure_cache_key = 'circuit'
124+
125+
cache.set(failure_cache_key, 1)
126+
127+
with pytest.raises(MyException):
128+
with CircuitBreaker(
129+
cache=cache,
130+
failure_cache_key=failure_cache_key,
131+
max_failures=2,
132+
max_failure_exception=MyException,
133+
catch_exceptions=(ValueError,),
134+
) as circuit_breaker:
135+
fail_function()
136+
137+
assert circuit_breaker.is_circuit_open
138+
139+
assert cache.get(failure_cache_key) is None

0 commit comments

Comments
 (0)