Skip to content

Commit aed243f

Browse files
author
Phillip Webb
committed
Only add Tomcat connectors in start() method
Update TomcatEmbeddedServletContainer so that Connectors are removed during initialization and re-added when the start() method is called. This prevent protocol handlers from being bound then immediately unbound. It also ensure that any additional connectors that may have been added don't accidentally start to accept traffic (which could cause potential deadlock issues). Fixes gh-1212
1 parent 7455e4e commit aed243f

File tree

2 files changed

+58
-12
lines changed

2 files changed

+58
-12
lines changed

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

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

1717
package org.springframework.boot.context.embedded.tomcat;
1818

19+
import java.util.HashMap;
20+
import java.util.Map;
1921
import java.util.concurrent.atomic.AtomicInteger;
2022

2123
import org.apache.catalina.Container;
2224
import org.apache.catalina.Engine;
2325
import org.apache.catalina.LifecycleException;
2426
import org.apache.catalina.LifecycleState;
27+
import org.apache.catalina.Server;
28+
import org.apache.catalina.Service;
2529
import org.apache.catalina.connector.Connector;
2630
import org.apache.catalina.startup.Tomcat;
2731
import org.apache.commons.logging.Log;
@@ -47,6 +51,8 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer
4751

4852
private final Tomcat tomcat;
4953

54+
private final Map<Service, Connector[]> serviceConnectors = new HashMap<Service, Connector[]>();
55+
5056
private final boolean autoStart;
5157

5258
/**
@@ -71,12 +77,25 @@ public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
7177

7278
private synchronized void initialize() throws EmbeddedServletContainerException {
7379
try {
80+
Server server = this.tomcat.getServer();
7481
int instanceId = containerCounter.incrementAndGet();
7582
if (instanceId > 0) {
7683
Engine engine = this.tomcat.getEngine();
7784
engine.setName(engine.getName() + "-" + instanceId);
7885
}
86+
87+
// Remove service connectors to that protocol binding doesn't happen yet
88+
for (Service service : server.findServices()) {
89+
Connector[] connectors = service.findConnectors().clone();
90+
this.serviceConnectors.put(service, connectors);
91+
for (Connector connector : connectors) {
92+
service.removeConnector(connector);
93+
}
94+
}
95+
96+
// Start the server to trigger initialization listeners
7997
this.tomcat.start();
98+
8099
Container[] children = this.tomcat.getHost().findChildren();
81100
for (Container container : children) {
82101
if (container instanceof TomcatEmbeddedContext) {
@@ -87,16 +106,7 @@ private synchronized void initialize() throws EmbeddedServletContainerException
87106
}
88107
}
89108
}
90-
try {
91-
// Allow the server to start so the ServletContext is available, but stop
92-
// the connector to prevent requests from being handled before the Spring
93-
// context is ready:
94-
Connector connector = this.tomcat.getConnector();
95-
connector.getProtocolHandler().stop();
96-
}
97-
catch (Exception ex) {
98-
this.logger.error("Cannot pause connector: ", ex);
99-
}
109+
100110
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
101111
// blocking non-daemon to stop immediate shutdown
102112
Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
@@ -120,6 +130,20 @@ public void run() {
120130

121131
@Override
122132
public void start() throws EmbeddedServletContainerException {
133+
// Add the previously removed connectors (also starting them)
134+
Service[] services = this.tomcat.getServer().findServices();
135+
for (Service service : services) {
136+
Connector[] connectors = this.serviceConnectors.get(service);
137+
if (connectors != null) {
138+
for (Connector connector : connectors) {
139+
service.addConnector(connector);
140+
if (!this.autoStart) {
141+
unbind(connector);
142+
}
143+
}
144+
this.serviceConnectors.remove(service);
145+
}
146+
}
123147
Connector connector = this.tomcat.getConnector();
124148
if (connector != null && this.autoStart) {
125149
try {
@@ -139,6 +163,19 @@ public void start() throws EmbeddedServletContainerException {
139163
}
140164
}
141165

166+
private void unbind(Connector connector) {
167+
try {
168+
connector.getProtocolHandler().stop();
169+
}
170+
catch (Exception ex) {
171+
this.logger.error("Cannot pause connector: ", ex);
172+
}
173+
}
174+
175+
Map<Service, Connector[]> getServiceConnectors() {
176+
return this.serviceConnectors;
177+
}
178+
142179
private void logPorts() {
143180
StringBuilder ports = new StringBuilder();
144181
for (Connector additionalConnector : this.tomcat.getService().findConnectors()) {

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717
package org.springframework.boot.context.embedded.tomcat;
1818

1919
import java.util.Arrays;
20+
import java.util.Map;
2021
import java.util.concurrent.TimeUnit;
2122

2223
import org.apache.catalina.Context;
2324
import org.apache.catalina.LifecycleEvent;
2425
import org.apache.catalina.LifecycleListener;
26+
import org.apache.catalina.LifecycleState;
27+
import org.apache.catalina.Service;
2528
import org.apache.catalina.Valve;
2629
import org.apache.catalina.connector.Connector;
2730
import org.apache.catalina.startup.Tomcat;
@@ -34,6 +37,7 @@
3437
import static org.junit.Assert.assertEquals;
3538
import static org.junit.Assert.assertFalse;
3639
import static org.junit.Assert.assertThat;
40+
import static org.mockito.BDDMockito.given;
3741
import static org.mockito.Matchers.any;
3842
import static org.mockito.Matchers.anyObject;
3943
import static org.mockito.Mockito.inOrder;
@@ -127,11 +131,16 @@ public void tomcatAdditionalConnectors() throws Exception {
127131
TomcatEmbeddedServletContainerFactory factory = getFactory();
128132
Connector[] listeners = new Connector[4];
129133
for (int i = 0; i < listeners.length; i++) {
130-
listeners[i] = mock(Connector.class);
134+
Connector connector = mock(Connector.class);
135+
given(connector.getState()).willReturn(LifecycleState.STOPPED);
136+
listeners[i] = connector;
131137
}
132138
factory.addAdditionalTomcatConnectors(listeners);
133139
this.container = factory.getEmbeddedServletContainer();
134-
assertEquals(listeners.length, factory.getAdditionalTomcatConnectors().size());
140+
Map<Service, Connector[]> connectors = ((TomcatEmbeddedServletContainer) this.container)
141+
.getServiceConnectors();
142+
assertThat(connectors.values().iterator().next().length,
143+
equalTo(listeners.length + 1));
135144
}
136145

137146
@Test

0 commit comments

Comments
 (0)