Skip to content

Commit 0af53b3

Browse files
committed
Ensure embedded start can be called multiple times
Update all `EmbeddedServletContainer` implementations to ensure that the `start()` method can be called multiple times, with the second call being ignored. Fixes gh-8036
1 parent ef69ae6 commit 0af53b3

File tree

4 files changed

+103
-58
lines changed

4 files changed

+103
-58
lines changed

spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainer.java

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public class JettyEmbeddedServletContainer implements EmbeddedServletContainer {
5959

6060
private Connector[] connectors;
6161

62+
private volatile boolean started;
63+
6264
/**
6365
* Create a new {@link JettyEmbeddedServletContainer} instance.
6466
* @param server the underlying Jetty server
@@ -111,37 +113,43 @@ private void stopSilently() {
111113

112114
@Override
113115
public void start() throws EmbeddedServletContainerException {
114-
this.server.setConnectors(this.connectors);
115-
if (!this.autoStart) {
116-
return;
117-
}
118-
try {
119-
this.server.start();
120-
for (Handler handler : this.server.getHandlers()) {
121-
handleDeferredInitialize(handler);
116+
synchronized (this.monitor) {
117+
if (this.started) {
118+
return;
119+
}
120+
this.server.setConnectors(this.connectors);
121+
if (!this.autoStart) {
122+
return;
122123
}
123-
Connector[] connectors = this.server.getConnectors();
124-
for (Connector connector : connectors) {
125-
try {
126-
connector.start();
124+
try {
125+
this.server.start();
126+
for (Handler handler : this.server.getHandlers()) {
127+
handleDeferredInitialize(handler);
127128
}
128-
catch (BindException ex) {
129-
if (connector instanceof NetworkConnector) {
130-
throw new PortInUseException(
131-
((NetworkConnector) connector).getPort());
129+
Connector[] connectors = this.server.getConnectors();
130+
for (Connector connector : connectors) {
131+
try {
132+
connector.start();
133+
}
134+
catch (BindException ex) {
135+
if (connector instanceof NetworkConnector) {
136+
throw new PortInUseException(
137+
((NetworkConnector) connector).getPort());
138+
}
139+
throw ex;
132140
}
133-
throw ex;
134141
}
142+
this.started = true;
143+
JettyEmbeddedServletContainer.logger
144+
.info("Jetty started on port(s) " + getActualPortsDescription());
145+
}
146+
catch (EmbeddedServletContainerException ex) {
147+
throw ex;
148+
}
149+
catch (Exception ex) {
150+
throw new EmbeddedServletContainerException(
151+
"Unable to start embedded Jetty servlet container", ex);
135152
}
136-
JettyEmbeddedServletContainer.logger
137-
.info("Jetty started on port(s) " + getActualPortsDescription());
138-
}
139-
catch (EmbeddedServletContainerException ex) {
140-
throw ex;
141-
}
142-
catch (Exception ex) {
143-
throw new EmbeddedServletContainerException(
144-
"Unable to start embedded Jetty servlet container", ex);
145153
}
146154
}
147155

@@ -197,6 +205,10 @@ else if (handler instanceof HandlerCollection) {
197205
@Override
198206
public void stop() {
199207
synchronized (this.monitor) {
208+
if (!this.started) {
209+
return;
210+
}
211+
this.started = false;
200212
try {
201213
this.server.stop();
202214
}

spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer
6262

6363
private final boolean autoStart;
6464

65+
private volatile boolean started;
66+
6567
/**
6668
* Create a new {@link TomcatEmbeddedServletContainer} instance.
6769
* @param tomcat the underlying Tomcat server
@@ -174,28 +176,34 @@ public void run() {
174176

175177
@Override
176178
public void start() throws EmbeddedServletContainerException {
177-
try {
178-
addPreviouslyRemovedConnectors();
179-
Connector connector = this.tomcat.getConnector();
180-
if (connector != null && this.autoStart) {
181-
startConnector(connector);
179+
synchronized (this.monitor) {
180+
if (this.started) {
181+
return;
182+
}
183+
try {
184+
addPreviouslyRemovedConnectors();
185+
Connector connector = this.tomcat.getConnector();
186+
if (connector != null && this.autoStart) {
187+
startConnector(connector);
188+
}
189+
checkThatConnectorsHaveStarted();
190+
this.started = true;
191+
TomcatEmbeddedServletContainer.logger
192+
.info("Tomcat started on port(s): " + getPortsDescription(true));
193+
}
194+
catch (ConnectorStartFailedException ex) {
195+
stopSilently();
196+
throw ex;
197+
}
198+
catch (Exception ex) {
199+
throw new EmbeddedServletContainerException(
200+
"Unable to start embedded Tomcat servlet container", ex);
201+
}
202+
finally {
203+
Context context = findContext();
204+
ContextBindings.unbindClassLoader(context, getNamingToken(context),
205+
getClass().getClassLoader());
182206
}
183-
checkThatConnectorsHaveStarted();
184-
TomcatEmbeddedServletContainer.logger
185-
.info("Tomcat started on port(s): " + getPortsDescription(true));
186-
}
187-
catch (ConnectorStartFailedException ex) {
188-
stopSilently();
189-
throw ex;
190-
}
191-
catch (Exception ex) {
192-
throw new EmbeddedServletContainerException(
193-
"Unable to start embedded Tomcat servlet container", ex);
194-
}
195-
finally {
196-
Context context = findContext();
197-
ContextBindings.unbindClassLoader(context, getNamingToken(context),
198-
getClass().getClassLoader());
199207
}
200208
}
201209

@@ -271,7 +279,11 @@ Map<Service, Connector[]> getServiceConnectors() {
271279
@Override
272280
public void stop() throws EmbeddedServletContainerException {
273281
synchronized (this.monitor) {
282+
if (!this.started) {
283+
return;
284+
}
274285
try {
286+
this.started = false;
275287
try {
276288
stopTomcat();
277289
this.tomcat.destroy();

spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
8888

8989
private Undertow undertow;
9090

91-
private boolean started = false;
91+
private volatile boolean started = false;
9292

9393
/**
9494
* Create a new {@link UndertowEmbeddedServletContainer} instance.
@@ -201,6 +201,9 @@ public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manag
201201
@Override
202202
public void start() throws EmbeddedServletContainerException {
203203
synchronized (this.monitor) {
204+
if (this.started) {
205+
return;
206+
}
204207
try {
205208
if (!this.autoStart) {
206209
return;
@@ -362,16 +365,17 @@ private Port getPortFromListener(Object listener) {
362365
@Override
363366
public void stop() throws EmbeddedServletContainerException {
364367
synchronized (this.monitor) {
365-
if (this.started) {
366-
try {
367-
this.started = false;
368-
this.manager.stop();
369-
this.undertow.stop();
370-
}
371-
catch (Exception ex) {
372-
throw new EmbeddedServletContainerException("Unable to stop undertow",
373-
ex);
374-
}
368+
if (!this.started) {
369+
return;
370+
}
371+
this.started = false;
372+
try {
373+
this.manager.stop();
374+
this.undertow.stop();
375+
}
376+
catch (Exception ex) {
377+
throw new EmbeddedServletContainerException("Unable to stop undertow",
378+
ex);
375379
}
376380
}
377381
}

spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
import org.springframework.boot.ApplicationHome;
8080
import org.springframework.boot.ApplicationTemp;
8181
import org.springframework.boot.context.embedded.Ssl.ClientAuth;
82+
import org.springframework.boot.testutil.InternalOutputCapture;
8283
import org.springframework.boot.web.servlet.ErrorPage;
8384
import org.springframework.boot.web.servlet.FilterRegistrationBean;
8485
import org.springframework.boot.web.servlet.ServletContextInitializer;
@@ -120,6 +121,9 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
120121
@Rule
121122
public TemporaryFolder temporaryFolder = new TemporaryFolder();
122123

124+
@Rule
125+
public InternalOutputCapture output = new InternalOutputCapture();
126+
123127
protected EmbeddedServletContainer container;
124128

125129
private final HttpClientContext httpClientContext = HttpClientContext.create();
@@ -153,6 +157,19 @@ public void startServlet() throws Exception {
153157
assertThat(getResponse(getLocalUrl("/hello"))).isEqualTo("Hello World");
154158
}
155159

160+
@Test
161+
public void startCalledTwice() throws Exception {
162+
AbstractEmbeddedServletContainerFactory factory = getFactory();
163+
this.container = factory
164+
.getEmbeddedServletContainer(exampleServletRegistration());
165+
this.container.start();
166+
int port = this.container.getPort();
167+
this.container.start();
168+
assertThat(this.container.getPort()).isEqualTo(port);
169+
assertThat(getResponse(getLocalUrl("/hello"))).isEqualTo("Hello World");
170+
assertThat(this.output.toString()).containsOnlyOnce("started on port");
171+
}
172+
156173
@Test
157174
public void emptyServerWhenPortIsMinusOne() throws Exception {
158175
AbstractEmbeddedServletContainerFactory factory = getFactory();

0 commit comments

Comments
 (0)