Fix hang caused by race condition in test for reset of kept-alive connection

Previously, a race between the server starting to reject requests
on a kept-alive connection and the request reaching the blocking
servlet could result in a response never being sent.

This commit updates the test to disable blocking once graceful
shutdown with an in-flight request has being. Awaitility is then used
to make a request on an idle kept-alive connection until it fails
due to the connection reset. This may not happen immediately due to
the aforementioned race.
pull/21440/head
Andy Wilkinson 5 years ago
parent 453ca01338
commit 86e6ec04b2

@ -66,6 +66,7 @@ import org.apache.jasper.servlet.JspServlet;
import org.apache.tomcat.JarScanFilter;
import org.apache.tomcat.JarScanType;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@ -599,17 +600,21 @@ class TomcatServletWebServerFactoryTests extends AbstractServletWebServerFactory
assertThat(keepAliveRequest.get()).isInstanceOf(HttpResponse.class);
Future<Object> request = initiateGetRequest(port, "/blocking");
blockingServlet.awaitQueue();
blockingServlet.setBlocking(false);
this.webServer.shutDownGracefully((result) -> {
});
Future<Object> idleConnectionRequest = initiateGetRequest(httpClient, port, "/blocking");
blockingServlet.admitOne();
Object response = request.get();
assertThat(response).isInstanceOf(HttpResponse.class);
Object idleConnectionRequestResult = idleConnectionRequest.get();
Object idleConnectionRequestResult = Awaitility.await().until(() -> {
Future<Object> idleConnectionRequest = initiateGetRequest(httpClient, port, "/blocking");
Object result = idleConnectionRequest.get();
return result;
}, (result) -> result instanceof Exception);
assertThat(idleConnectionRequestResult).isInstanceOfAny(SocketException.class, NoHttpResponseException.class);
if (idleConnectionRequestResult instanceof SocketException) {
assertThat((SocketException) idleConnectionRequestResult).hasMessage("Connection reset");
}
blockingServlet.admitOne();
Object response = request.get();
assertThat(response).isInstanceOf(HttpResponse.class);
this.webServer.stop();
}

@ -1445,22 +1445,26 @@ public abstract class AbstractServletWebServerFactoryTests {
private final BlockingQueue<CyclicBarrier> barriers = new ArrayBlockingQueue<>(10);
protected volatile boolean blocking = true;
public BlockingServlet() {
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
CyclicBarrier barrier = new CyclicBarrier(2);
this.barriers.add(barrier);
try {
barrier.await();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (BrokenBarrierException ex) {
throw new ServletException(ex);
if (this.blocking) {
CyclicBarrier barrier = new CyclicBarrier(2);
this.barriers.add(barrier);
try {
barrier.await();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (BrokenBarrierException ex) {
throw new ServletException(ex);
}
}
}
@ -1491,6 +1495,10 @@ public abstract class AbstractServletWebServerFactoryTests {
}
}
public void setBlocking(boolean blocking) {
this.blocking = blocking;
}
}
static class BlockingAsyncServlet extends HttpServlet {

Loading…
Cancel
Save