ExerciseWarm-up
Warm-up
Build the three states
10 minintermediate2–10 yrs
One concept, guided. Near-impossible to fail.
What this teaches
- A breaker stops calls rather than retrying them
- Half-open is how it learns the dependency recovered
- One trial call per cool-down, and a failed trial re-opens immediately
- Injecting the clock makes every transition testable and exact
Starter
Starter.java
import java.util.*;
import java.util.function.*;
/**
* Warm-up: a circuit breaker is a small state machine. Build it.
*
* Note the Clock. Time is injected rather than slept on, so every transition
* is exact — and that is how you should test a breaker in real code too. A
* test with Thread.sleep in it is slow and flaky for no benefit.
*/
public class Starter {
static class Dependency {
boolean down = true;
int calls = 0;
String fetch() {
calls++;
if (down) throw new IllegalStateException("connect timed out");
return "ok";
}
}
static class OpenCircuitException extends RuntimeException {
OpenCircuitException() { super("circuit is open"); }
}
static class Clock {
long now = 0;
void advance(long millis) { now += millis; }
}
enum State { CLOSED, OPEN, HALF_OPEN }
// TODO 1: write the breaker.
//
// static class CircuitBreaker {
// CircuitBreaker(int threshold, long openMillis, Clock clock)
// State state()
// <T> T call(Supplier<T> action)
// }
//
// Rules, in the order they matter:
// - CLOSED: call through. Count failures. At the threshold, go OPEN and
// record when.
// - OPEN: throw OpenCircuitException WITHOUT calling the dependency.
// - After openMillis has passed, state() reports HALF_OPEN.
// - HALF_OPEN: let exactly one call through. Success closes the breaker;
// failure re-opens it and restarts the cool-down.
public static void main(String[] args) {
Dependency dependency = new Dependency();
int failures = 0;
for (int i = 0; i < 20; i++) {
try { dependency.fetch(); } catch (RuntimeException e) { failures++; }
}
System.out.println("no breaker: " + dependency.calls + " calls, " + failures + " failures");
// TODO 2: run the same twenty attempts through your breaker with a
// threshold of 3. Predict how many reach the dependency first.
// TODO 3: the rejected calls still failed. Write one sentence saying
// what the breaker bought, in terms of threads rather than errors.
// TODO 4: advance the clock past the cool-down and print state().
// Then make one call while the dependency is still down, and print
// the state again. Explain why a failed trial does not get a second
// attempt.
// TODO 5: set dependency.down = false, advance the clock again, and
// watch it close. Note that nothing was configured and nobody was
// paged.
// TODO 6: what happens to your breaker if the dependency HANGS
// instead of throwing — no exception, no return? Say what has to
// exist for the breaker to work at all.
}
}Run it locally:
cd exercises/java/resilience/circuit-breaker/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out StarterDone when
- Your breaker opens after the threshold and rejects without calling
- It reaches half-open by advancing the clock, not by sleeping
- A failed trial re-opens it; a successful one closes it
- You can say what the rejected calls bought, given they still failed