Skip to content

Commit a820344

Browse files
authored
Merge pull request #1621 from mattrjacobs/stuck-circuit-breaker
Fixed bug where an unsubscription of a command in half-open state leaves circuit permanently open
2 parents fa9c54d + b5c1d20 commit a820344

File tree

3 files changed

+56
-1
lines changed

3 files changed

+56
-1
lines changed

hystrix-core/src/main/java/com/netflix/hystrix/AbstractCommand.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ public void call() {
382382
final Action0 unsubscribeCommandCleanup = new Action0() {
383383
@Override
384384
public void call() {
385+
circuitBreaker.markNonSuccess();
385386
if (_cmd.commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.UNSUBSCRIBED)) {
386387
if (!_cmd.executionResult.containsTerminalEvent()) {
387388
_cmd.eventNotifier.markEvent(HystrixEventType.CANCELLED, _cmd.commandKey);

hystrix-core/src/main/java/com/netflix/hystrix/HystrixCircuitBreaker.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,11 @@ public boolean attemptExecution() {
272272
return true;
273273
} else {
274274
if (isAfterSleepWindow()) {
275+
//only the first request after sleep window should execute
276+
//if the executing command succeeds, the status will transition to CLOSED
277+
//if the executing command fails, the status will transition to OPEN
278+
//if the executing command gets unsubscribed, the status will transition to OPEN
275279
if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {
276-
//only the first request after sleep window should execute
277280
return true;
278281
} else {
279282
return false;

hystrix-core/src/test/java/com/netflix/hystrix/HystrixCircuitBreakerTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.netflix.hystrix.strategy.HystrixPlugins;
3333
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
3434
import rx.Observable;
35+
import rx.Subscription;
3536

3637
/**
3738
* These tests each use a different command key to ensure that running them in parallel doesn't allow the state
@@ -565,6 +566,56 @@ public void testLowVolumeDoesNotTripCircuit() {
565566
}
566567
}
567568

569+
@Test
570+
public void testUnsubscriptionDoesNotLeaveCircuitStuckHalfOpen() {
571+
String key = "cmd-J";
572+
try {
573+
int sleepWindow = 200;
574+
575+
// fail
576+
HystrixCommand<Boolean> cmd1 = new FailureCommand(key, 1, sleepWindow);
577+
HystrixCommand<Boolean> cmd2 = new FailureCommand(key, 1, sleepWindow);
578+
HystrixCommand<Boolean> cmd3 = new FailureCommand(key, 1, sleepWindow);
579+
HystrixCommand<Boolean> cmd4 = new FailureCommand(key, 1, sleepWindow);
580+
cmd1.execute();
581+
cmd2.execute();
582+
cmd3.execute();
583+
cmd4.execute();
584+
585+
HystrixCircuitBreaker cb = cmd1.circuitBreaker;
586+
587+
// everything has failed in the test window so we should return false now
588+
Thread.sleep(100);
589+
assertFalse(cb.allowRequest());
590+
assertTrue(cb.isOpen());
591+
592+
//this should occur after the sleep window, so get executed
593+
//however, it is unsubscribed, so never updates state on the circuit-breaker
594+
HystrixCommand<Boolean> cmd5 = new SuccessCommand(key, 5000, sleepWindow);
595+
596+
//wait for sleep window to pass
597+
Thread.sleep(sleepWindow + 50);
598+
599+
Observable<Boolean> o = cmd5.observe();
600+
Subscription s = o.subscribe();
601+
s.unsubscribe();
602+
603+
//wait for 10 sleep windows, then try a successful command. this should return the circuit to CLOSED
604+
605+
Thread.sleep(10 * sleepWindow);
606+
HystrixCommand<Boolean> cmd6 = new SuccessCommand(key, 1, sleepWindow);
607+
cmd6.execute();
608+
609+
Thread.sleep(100);
610+
assertTrue(cb.allowRequest());
611+
assertFalse(cb.isOpen());
612+
613+
} catch (Exception e) {
614+
e.printStackTrace();
615+
fail("Error occurred: " + e.getMessage());
616+
}
617+
}
618+
568619
/**
569620
* Utility method for creating {@link HystrixCommandMetrics} for unit tests.
570621
*/

0 commit comments

Comments
 (0)