0

I am using JUnit 5, running test with simple mvn clean test. I have a few services/utilities that require some waiting in test, in order to test their functionality. One of them is a simple circuit breaker.

public class DefaultHttpCircuitBreaker implements CircuitBreaker<HttpResponse> {

    private enum State {
        CLOSED,
        OPEN,
        HALF_OPEN
    }

    private final int failureThreshold;
    private final long retryPeriod;

    private State state;
    private int failureCount;
    private long lastFailureTime;
    private String lastFailureMessage;

    public DefaultHttpCircuitBreaker(int failureThreshold, long retryPeriod) {
        this.retryPeriod = retryPeriod;
        this.state = State.CLOSED;
        this.failureCount = 0;
        this.failureThreshold = failureThreshold;
    }

    @Override
    public RemoteServiceResponse<HttpResponse> attemptCall(Caller<HttpResponse> caller) {
        evaluateState();
        if (this.state == State.OPEN) {
            return RemoteServiceResponse.failure(lastFailureMessage);
        } else {
            try {
                HttpResponse response = caller.call();
                int result = response.getStatusLine().getStatusCode();
                if (result < 200 || result >= 300) {
                    recordFailure(response.getStatusLine().getReasonPhrase());
                    return RemoteServiceResponse.failure(lastFailureMessage);
                }
                resetCount();
                return RemoteServiceResponse.success(response);
            } catch (Exception e) {
                recordFailure(e.getMessage());
                return RemoteServiceResponse.communicationError(lastFailureMessage);
            }
        }
    }

    @Override
    public long getLastFailureTime() {
        return lastFailureTime;
    }

    @Override
    public int getFailureCount(){
        return failureCount;
    }

    private void recordFailure(String message) {
        failureCount++;
        this.lastFailureMessage = message;
        this.lastFailureTime = System.currentTimeMillis();
    }

    private void evaluateState() {
        if (failureCount >= failureThreshold) {
            if ((System.currentTimeMillis() - lastFailureTime) > retryPeriod) {
                this.state = State.HALF_OPEN;
            } else {
                this.state = State.OPEN;
            }
        } else {
            this.state = State.CLOSED;
        }
    }

    private void resetCount() {
        this.failureCount = 0;
        this.state = State.CLOSED;
        lastFailureTime = 0;
    }
}

As you can see, when the CircuitBreaker is open, and certain time has passed, it become half open, so it can attempt to do call once more.

And the test:

void testHalfOpenSuccess() throws InterruptedException {

    int failureThreshold = 5;
    String reasonPhrase = "testPhrase";
    long retryPeriod = 10000;
    StatusLine line = new BasicStatusLine(new ProtocolVersion("version", 1, 1), 300, reasonPhrase);
    Mockito.when(response.getStatusLine()).thenReturn(line);

    CircuitBreaker<HttpResponse> circuitBreaker = new DefaultHttpCircuitBreaker(failureThreshold, retryPeriod);

    long lastFailureTime = 0;
    for (int i = 0; i < failureThreshold; i++) {
        RemoteServiceResponse<HttpResponse> res = circuitBreaker.attemptCall(() -> client.execute(new HttpGet("testUrl")));
        lastFailureTime = circuitBreaker.getLastFailureTime();
        Assertions.assertTrue(res.isFail());
        Assertions.assertEquals(reasonPhrase, res.getErrMessage());
    }

    RemoteServiceResponse<HttpResponse> res = circuitBreaker.attemptCall(() -> client.execute(new HttpGet("testUrl")));
    Assertions.assertTrue(res.isFail());
    Assertions.assertEquals(reasonPhrase, res.getErrMessage());
    Assertions.assertEquals(lastFailureTime, circuitBreaker.getLastFailureTime());

    Thread.sleep(2 *retryPeriod); //NOSONAR
    Mockito.when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("version", 1, 1), 200, reasonPhrase));

    RemoteServiceResponse<HttpResponse> resSuccess = circuitBreaker.attemptCall(() -> client.execute(new HttpGet("testUrl")));
    Assertions.assertTrue(resSuccess.isSuccess());
    Assertions.assertEquals(0, circuitBreaker.getFailureCount());
    Assertions.assertEquals(0, circuitBreaker.getLastFailureTime());
}

This test attempts to call service threshold times, all should fail. Then it tries to call it instantly one more time just it to fail. However, then i sleep the thread so the retryPeriod has passed and the CircuitBreaker can attempt to call once more.

This test works. However, when I run it with mvn clean test it randomly fails 1 out of 10 times. What is reason for this? If I run this test using IntelliJ, it never fails. What is the reason maven fails test using Thread.sleep()? Same thing happens when I run test that uses wait() and notify(). However only the last defined test in class fails like this.

What fails is last Assertions.assertTrue(resSuccess.isSuccess());

What is the reason?

Johnyb
  • 980
  • 1
  • 12
  • 27
  • 2
    Could you give us the error message? My best guess would be that you are multithreading the tests and while you run it on its own there is no issue. When running it with other tests the Locks are not applied properly – SirHawrk Dec 06 '21 at 15:17
  • If you intend to use `circuitBreaker` in a multi-threaded setup, at least mark `state`, `failureCount`, `lastFailureTime` and `lastFailureMessage` as [volatile](https://stackoverflow.com/q/106591/3080094). – vanOekel Dec 06 '21 at 20:38
  • i do not, as you can see only thing i do is sleeping the current thread. – Johnyb Dec 07 '21 at 11:57
  • @SirHawrk The last Assertions.assertTrue(resSuccess.isSuccess()) fails. which it shouldnt. If i run whole test class in intellij (around 15 tests ) then it never fails too. Only sometimes fails when running it with maven. – Johnyb Dec 07 '21 at 11:57
  • That is not an error message. There should be a stacktrace with that failure. We can not see if you are using it in a multi-threaded setup. That was a guess from my side. – SirHawrk Dec 08 '21 at 10:36

0 Answers0