cjstehno / ersatz

🤖 A simulated HTTP server for testing client code with configurable responses.
https://cjstehno.github.io/ersatz
Apache License 2.0
47 stars 5 forks source link

Client code sometimes randomly fails with groovy "service unavailable" error #120

Closed rmorrise closed 4 years ago

rmorrise commented 5 years ago

A "Service Unavailable" error shows up randomly in our build environment for Spock tests in our Grails 3 app that use Ersatz.

I'd like to dig in further, but I'm not sure where to start. I appreciate any help you can offer.

It's always the same test, but it will fail on one build and then pass on a rebuild with the same source code. Both builds can be run on the same Jenkins slave node.

groovyx.net.http.HttpException: Service Unavailable
at groovyx.net.http.NativeHandlers.failure(NativeHandlers.java:69)
at groovyx.net.http.HttpBuilder$ResponseHandlerFunction.apply(HttpBuilder.java:2305)
at groovyx.net.http.JavaHttpBuilder$Action.lambda$execute$2(JavaHttpBuilder.java:168)
at groovyx.net.http.JavaHttpBuilder$ThreadLocalAuth.with(JavaHttpBuilder.java:331)
at groovyx.net.http.JavaHttpBuilder$Action.execute(JavaHttpBuilder.java:122)
at groovyx.net.http.JavaHttpBuilder.createAndExecute(JavaHttpBuilder.java:374)
at groovyx.net.http.JavaHttpBuilder.doGet(JavaHttpBuilder.java:381)
at groovyx.net.http.HttpObjectConfigImpl.nullInterceptor(HttpObjectConfigImpl.java:47)
at groovyx.net.http.HttpBuilder.get(HttpBuilder.java:346)
at groovyx.net.http.HttpBuilder.get(HttpBuilder.java:1297)
at groovyx.net.http.HttpBuilder$get$0.call(Unknown Source)
at com.mycompany.MyService.fetchData(...)

Our setup and cleanup code for the spec looks like this:

    ErsatzServer ersatz

    def setup() {
        ersatz = new ErsatzServer()
        ersatz.start()
        service.destinationUrl = ersatz.httpUrl + '/'
    }

    def cleanup() {
        ersatz.verify()
        ersatz.stop()
    }
rmorrise commented 5 years ago

Worth noting: the code under test uses GPars to make two HTTP calls at the same time:

        GParsPool.withPool(2) {
            // Make closures to fetch the results of the search
            def firstNameFetch = { ->
                fetchResults(firstNameFilter(partialLabel))
            }

            def lastNameFetch = { ->
                fetchResults(lastNameFilter(partialLabel))
            }

            // For the two closures, run them in parallel ...
            results = [firstNameFetch, lastNameFetch].collectParallel { fetchClosure -> fetchClosure() }
            // Flatten the results ...
                    .flatten()
        }
        ersatz.expectations {
            get(startsWith('/search')) {
                called 2
                header 'Content-Type', 'application/json'
                responder {
                    code 404
                }
            }
        }
cjstehno commented 5 years ago

What versions of Ersatz and HttpBuilder-NG are you using? Also what Groovy/Grails versions?

Also, do you really mean to respond with a 404?

A full working example would help, though honestly, I have a lot going on right now so I may not get to this anytime soon.

rmorrise commented 5 years ago

Understood, thanks for letting me know. I'll try to work up an example in any case.

The version info:

grailsVersion=3.3.4
gradleWrapperVersion=3.5

Groovy version: 2.4.13

    compile 'io.github.http-builder-ng:http-builder-ng-apache:1.0.3'
    // use the shadow jar, so spring doesn't try to configure an embedded undertow server during integration testing
    testCompile 'com.stehno.ersatz:ersatz:1.9.0:safe@jar'
cjstehno commented 4 years ago

Any luck coming up with an example test case for this?

cjstehno commented 4 years ago

I did some testing with multiple client threads hitting an ErsatzServer and found that there was an issue with how the call counter is handled in that scenario. I ended up with a different error than what you are mentioning though the difference may be in how HttpBuilder-NG translates/handles the error. When v1.10 comes out, I suggest you try it out and see if your problem is resolved. If not feel fee to reopen this issue or create a new one.