Wiremock - sometimes it throws "Software caused connection abort: recv failed"

Viewed 1606

I encountered a very strange situation when using Spring and Wiremock for integration testing: suddenly, one specific test started failing intermittently. A snippet of the error below:

org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:10314/my/endpoint": Software caused connection abort: recv failed; nested exception is java.net.SocketException: Software caused connection abort: recv failed
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:785) ~[spring-web-5.3.7.jar:5.3.7]
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711) ~[spring-web-5.3.7.jar:5.3.7]
    at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:468) ~[spring-web-5.3.7.jar:5.3.7]
... more logs here ...

The context is as follows: I have added a new test that uses wiremock to stub responses:

wireMockServer.stubFor(WireMock.post("/my/endpoint")
    .withRequestBody(containing(aJsonRequestBodyHere))
    .willReturn(aResponse()
            .withBody(aJsonResponseHere)
            .withStatus(HttpStatus.OK.value())
            .withHeader(HttpHeader.CONTENT_TYPE.toString(), CONTENT_TYPE_APPLICATION_JSON)));

The call to this stub endpoint is made as follows:

given()
    .when()
    .get("my/endpoint")
    .then()
    .body(containsString(theExpectedJsonResponse)))
    .statusCode(200);

The strange part:

  • the same test runs without any problems on my local machine - if run alone
  • when running all the tests on my machine, sometimes, the same test fails, sometimes does not
  • only this test is failing each time; no other test fails
  • when tests are run on Jenkins, it fails 100%
3 Answers

After digging a little on this, I have came across this and this articles that described my situation almost 100%.

The root cause seemed to be the fact that tests were executing to fast - maybe the ones that did not do too many things - and Wiremock did not have time to setup correctly for the next test. I have tested this assumption by adding a Thread.sleep(2000) at the beginning of the test and then run many times all the test - all tests passed without problem.

The solution is presented in the first article: register a Transformer class that will intercept all responses and add a Connection=close header to them.

In more details: I have added a Transformer class that extends ResponseDefinitionTransformer and adds the Connection header on each response. Then I have created @Configuration annotated class and registered this Transformer

Transformer class (taken from the first article):

public class NoKeepAliveTransformer extends ResponseDefinitionTransformer {

    @Override
    public ResponseDefinition transform(Request request, ResponseDefinition responseDefinition, FileSource files, Parameters parameters) {
        return ResponseDefinitionBuilder.like(responseDefinition)
                .withHeader(HttpHeaders.CONNECTION, "close")
                .build();
    }

    @Override
    public String getName() {
        return "keep-alive-disabler";
    }
}

Configuration class:

@Configuration
public class WiremockConfiguration {

    @Bean
    WireMockConfigurationCustomizer optionsCustomizer() {
        return options -> options.extensions(NoKeepAliveTransformer.class);
    }
}

TL;DR

the workaround with .withHeader(HttpHeaders.CONNECTION, "close") on the response fixes:

org.springframework.web.client.ResourceAccessException:
"I/O error on POST request for "http://127.0.0.1:8080/wiremock/my_stubbed_service": Software caused connection abort: recv failed"

Tested with com.github.tomakehurst:wiremock:2.27.2 and com.github.tomakehurst:wiremock-jre8:2.30.1

Faced the same problem in integrational tests while sending requests through Feign client using WireMock server.

According to Tom's comment in the previous answer decided to fix the client's configuration so that feign retried to send requests. Found the solution described in this article.

The simplest way for Feign is to add @Configuration class with feign.Retryer @Bean:

@Configuration
public class FeignConfig {
    @Bean
    public Retryer retryer() {
        return new Retryer.Default(200L,1000L,5);
    }
}
Related