Commit 3cadde09 authored by Andy Wilkinson's avatar Andy Wilkinson

Protect against available port actually being unavailable

Closes gh-19355
parent f75c73eb
...@@ -288,7 +288,7 @@ public class TomcatServletWebServerFactoryTests extends AbstractServletWebServer ...@@ -288,7 +288,7 @@ public class TomcatServletWebServerFactoryTests extends AbstractServletWebServer
} }
@Test @Test
public void primaryConnectorPortClashThrowsWebServerException() throws IOException { public void primaryConnectorPortClashThrowsWebServerException() throws Exception {
doWithBlockedPort((port) -> { doWithBlockedPort((port) -> {
TomcatServletWebServerFactory factory = getFactory(); TomcatServletWebServerFactory factory = getFactory();
factory.setPort(port); factory.setPort(port);
...@@ -300,7 +300,7 @@ public class TomcatServletWebServerFactoryTests extends AbstractServletWebServer ...@@ -300,7 +300,7 @@ public class TomcatServletWebServerFactoryTests extends AbstractServletWebServer
} }
@Test @Test
public void startupFailureDoesNotResultInUnstoppedThreadsBeingReported() throws IOException { public void startupFailureDoesNotResultInUnstoppedThreadsBeingReported() throws Exception {
super.portClashOfPrimaryConnectorResultsInPortInUseException(); super.portClashOfPrimaryConnectorResultsInPortInUseException();
String string = this.outputCapture.toString(); String string = this.outputCapture.toString();
assertThat(string).doesNotContain("appears to have started a thread named [main]"); assertThat(string).doesNotContain("appears to have started a thread named [main]");
......
...@@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets; ...@@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets;
import java.security.KeyStore; import java.security.KeyStore;
import java.time.Duration; import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.Callable;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
...@@ -92,12 +93,15 @@ public abstract class AbstractReactiveWebServerFactoryTests { ...@@ -92,12 +93,15 @@ public abstract class AbstractReactiveWebServerFactoryTests {
protected abstract AbstractReactiveWebServerFactory getFactory(); protected abstract AbstractReactiveWebServerFactory getFactory();
@Test @Test
public void specificPort() { public void specificPort() throws Exception {
AbstractReactiveWebServerFactory factory = getFactory(); AbstractReactiveWebServerFactory factory = getFactory();
int specificPort = SocketUtils.findAvailableTcpPort(41000); int specificPort = doWithRetry(() -> {
factory.setPort(specificPort); int port = SocketUtils.findAvailableTcpPort(41000);
this.webServer = factory.getWebServer(new EchoHandler()); factory.setPort(port);
this.webServer.start(); this.webServer = factory.getWebServer(new EchoHandler());
this.webServer.start();
return port;
});
Mono<String> result = getWebClient().build().post().uri("/test").contentType(MediaType.TEXT_PLAIN) Mono<String> result = getWebClient().build().post().uri("/test").contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Hello World")).exchange() .body(BodyInserters.fromObject("Hello World")).exchange()
.flatMap((response) -> response.bodyToMono(String.class)); .flatMap((response) -> response.bodyToMono(String.class));
...@@ -113,6 +117,7 @@ public abstract class AbstractReactiveWebServerFactoryTests { ...@@ -113,6 +117,7 @@ public abstract class AbstractReactiveWebServerFactoryTests {
@Test @Test
public void basicSslFromFileSystem() { public void basicSslFromFileSystem() {
testBasicSslWithKeyStore("src/test/resources/test.jks", "password"); testBasicSslWithKeyStore("src/test/resources/test.jks", "password");
} }
protected final void testBasicSslWithKeyStore(String keyStore, String keyPassword) { protected final void testBasicSslWithKeyStore(String keyStore, String keyPassword) {
...@@ -335,6 +340,19 @@ public abstract class AbstractReactiveWebServerFactoryTests { ...@@ -335,6 +340,19 @@ public abstract class AbstractReactiveWebServerFactoryTests {
assertThat(body).isEqualTo("https"); assertThat(body).isEqualTo("https");
} }
private <T> T doWithRetry(Callable<T> action) throws Exception {
Exception lastFailure = null;
for (int i = 0; i < 10; i++) {
try {
return action.call();
}
catch (Exception ex) {
lastFailure = ex;
}
}
throw new IllegalStateException("Action was not successful in 10 attempts", lastFailure);
}
protected static class EchoHandler implements HttpHandler { protected static class EchoHandler implements HttpHandler {
public EchoHandler() { public EchoHandler() {
......
...@@ -44,6 +44,7 @@ import java.util.EnumSet; ...@@ -44,6 +44,7 @@ import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
...@@ -248,10 +249,13 @@ public abstract class AbstractServletWebServerFactoryTests { ...@@ -248,10 +249,13 @@ public abstract class AbstractServletWebServerFactoryTests {
@Test @Test
public void specificPort() throws Exception { public void specificPort() throws Exception {
AbstractServletWebServerFactory factory = getFactory(); AbstractServletWebServerFactory factory = getFactory();
int specificPort = SocketUtils.findAvailableTcpPort(41000); int specificPort = doWithRetry(() -> {
factory.setPort(specificPort); int port = SocketUtils.findAvailableTcpPort(41000);
this.webServer = factory.getWebServer(exampleServletRegistration()); factory.setPort(port);
this.webServer.start(); this.webServer = factory.getWebServer(exampleServletRegistration());
this.webServer.start();
return port;
});
assertThat(getResponse("http://localhost:" + specificPort + "/hello")).isEqualTo("Hello World"); assertThat(getResponse("http://localhost:" + specificPort + "/hello")).isEqualTo("Hello World");
assertThat(this.webServer.getPort()).isEqualTo(specificPort); assertThat(this.webServer.getPort()).isEqualTo(specificPort);
} }
...@@ -822,7 +826,7 @@ public abstract class AbstractServletWebServerFactoryTests { ...@@ -822,7 +826,7 @@ public abstract class AbstractServletWebServerFactoryTests {
} }
@Test @Test
public void portClashOfPrimaryConnectorResultsInPortInUseException() throws IOException { public void portClashOfPrimaryConnectorResultsInPortInUseException() throws Exception {
doWithBlockedPort((port) -> { doWithBlockedPort((port) -> {
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> { assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
AbstractServletWebServerFactory factory = getFactory(); AbstractServletWebServerFactory factory = getFactory();
...@@ -834,7 +838,7 @@ public abstract class AbstractServletWebServerFactoryTests { ...@@ -834,7 +838,7 @@ public abstract class AbstractServletWebServerFactoryTests {
} }
@Test @Test
public void portClashOfSecondaryConnectorResultsInPortInUseException() throws IOException { public void portClashOfSecondaryConnectorResultsInPortInUseException() throws Exception {
doWithBlockedPort((port) -> { doWithBlockedPort((port) -> {
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> { assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
AbstractServletWebServerFactory factory = getFactory(); AbstractServletWebServerFactory factory = getFactory();
...@@ -1128,19 +1132,28 @@ public abstract class AbstractServletWebServerFactoryTests { ...@@ -1128,19 +1132,28 @@ public abstract class AbstractServletWebServerFactoryTests {
return bean; return bean;
} }
protected final void doWithBlockedPort(BlockedPortAction action) throws IOException { private <T> T doWithRetry(Callable<T> action) throws Exception {
int port = SocketUtils.findAvailableTcpPort(40000); Exception lastFailure = null;
ServerSocket serverSocket = new ServerSocket();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
try { try {
serverSocket.bind(new InetSocketAddress(port)); return action.call();
break;
} }
catch (Exception ex) { catch (Exception ex) {
lastFailure = ex;
} }
} }
throw new IllegalStateException("Action was not successful in 10 attempts", lastFailure);
}
protected final void doWithBlockedPort(BlockedPortAction action) throws Exception {
ServerSocket serverSocket = new ServerSocket();
int blockedPort = doWithRetry(() -> {
int port = SocketUtils.findAvailableTcpPort(40000);
serverSocket.bind(new InetSocketAddress(port));
return port;
});
try { try {
action.run(port); action.run(blockedPort);
} }
finally { finally {
serverSocket.close(); serverSocket.close();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment