Browse Source
This commit adds support for gracefully shutting down the embedded web server. When a grace period is configured (server.shutdown.grace-period), upon shutdown, the web server will no longer permit new requests and will wait for up to the grace period for active requests to complete. Closes gh-4657pull/20434/head
40 changed files with 1541 additions and 56 deletions
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
/* |
||||
* Copyright 2012-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.web.embedded.jetty; |
||||
|
||||
import java.time.Duration; |
||||
import java.util.function.Supplier; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.eclipse.jetty.server.Connector; |
||||
import org.eclipse.jetty.server.Server; |
||||
import org.eclipse.jetty.server.ServerConnector; |
||||
|
||||
import org.springframework.boot.web.server.GracefulShutdown; |
||||
|
||||
/** |
||||
* {@link GracefulShutdown} for Jetty. |
||||
* |
||||
* @author Andy Wilkinson |
||||
*/ |
||||
class JettyGracefulShutdown implements GracefulShutdown { |
||||
|
||||
private static final Log logger = LogFactory.getLog(JettyGracefulShutdown.class); |
||||
|
||||
private final Server server; |
||||
|
||||
private final Supplier<Integer> activeRequests; |
||||
|
||||
private final Duration period; |
||||
|
||||
private volatile boolean shuttingDown = false; |
||||
|
||||
JettyGracefulShutdown(Server server, Supplier<Integer> activeRequests, Duration period) { |
||||
this.server = server; |
||||
this.activeRequests = activeRequests; |
||||
this.period = period; |
||||
} |
||||
|
||||
@Override |
||||
public boolean shutDownGracefully() { |
||||
logger.info("Commencing graceful shutdown, allowing up to " + this.period.getSeconds() |
||||
+ "s for active requests to complete"); |
||||
for (Connector connector : this.server.getConnectors()) { |
||||
((ServerConnector) connector).setAccepting(false); |
||||
} |
||||
this.shuttingDown = true; |
||||
long end = System.currentTimeMillis() + this.period.toMillis(); |
||||
while (System.currentTimeMillis() < end && (this.activeRequests.get() > 0)) { |
||||
try { |
||||
Thread.sleep(100); |
||||
} |
||||
catch (InterruptedException ex) { |
||||
Thread.currentThread().interrupt(); |
||||
} |
||||
} |
||||
this.shuttingDown = false; |
||||
long activeRequests = this.activeRequests.get(); |
||||
if (activeRequests == 0) { |
||||
logger.info("Graceful shutdown complete"); |
||||
return true; |
||||
} |
||||
if (logger.isInfoEnabled()) { |
||||
logger.info("Grace period elaped with " + activeRequests + " request(s) still active"); |
||||
} |
||||
return activeRequests == 0; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isShuttingDown() { |
||||
return this.shuttingDown; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,116 @@
@@ -0,0 +1,116 @@
|
||||
/* |
||||
* Copyright 2012-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.web.embedded.netty; |
||||
|
||||
import java.time.Duration; |
||||
import java.util.concurrent.atomic.AtomicLong; |
||||
import java.util.function.BiFunction; |
||||
import java.util.function.Supplier; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.reactivestreams.Publisher; |
||||
import reactor.netty.DisposableServer; |
||||
import reactor.netty.http.server.HttpServerRequest; |
||||
import reactor.netty.http.server.HttpServerResponse; |
||||
|
||||
import org.springframework.boot.web.server.GracefulShutdown; |
||||
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; |
||||
|
||||
/** |
||||
* {@link GracefulShutdown} for a Reactor Netty {@link DisposableServer}. |
||||
* |
||||
* @author Andy Wilkinson |
||||
*/ |
||||
final class NettyGracefulShutdown implements GracefulShutdown { |
||||
|
||||
private static final Log logger = LogFactory.getLog(NettyGracefulShutdown.class); |
||||
|
||||
private final Supplier<DisposableServer> disposableServer; |
||||
|
||||
private final Duration lifecycleTimeout; |
||||
|
||||
private final Duration period; |
||||
|
||||
private final AtomicLong activeRequests = new AtomicLong(); |
||||
|
||||
private volatile boolean shuttingDown; |
||||
|
||||
NettyGracefulShutdown(Supplier<DisposableServer> disposableServer, Duration lifecycleTimeout, Duration period) { |
||||
this.disposableServer = disposableServer; |
||||
this.lifecycleTimeout = lifecycleTimeout; |
||||
this.period = period; |
||||
} |
||||
|
||||
@Override |
||||
public boolean shutDownGracefully() { |
||||
logger.info("Commencing graceful shutdown, allowing up to " + this.period.getSeconds() |
||||
+ "s for active requests to complete"); |
||||
DisposableServer server = this.disposableServer.get(); |
||||
if (server == null) { |
||||
return false; |
||||
} |
||||
if (this.lifecycleTimeout != null) { |
||||
server.disposeNow(this.lifecycleTimeout); |
||||
} |
||||
else { |
||||
server.disposeNow(); |
||||
} |
||||
this.shuttingDown = true; |
||||
long end = System.currentTimeMillis() + this.period.toMillis(); |
||||
try { |
||||
while (this.activeRequests.get() > 0 && System.currentTimeMillis() < end) { |
||||
try { |
||||
Thread.sleep(50); |
||||
} |
||||
catch (InterruptedException ex) { |
||||
Thread.currentThread().interrupt(); |
||||
break; |
||||
} |
||||
} |
||||
long activeRequests = this.activeRequests.get(); |
||||
if (activeRequests == 0) { |
||||
logger.info("Graceful shutdown complete"); |
||||
return true; |
||||
} |
||||
if (logger.isInfoEnabled()) { |
||||
logger.info("Grace period elaped with " + activeRequests + " request(s) still active"); |
||||
} |
||||
return false; |
||||
} |
||||
finally { |
||||
this.shuttingDown = false; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean isShuttingDown() { |
||||
return this.shuttingDown; |
||||
} |
||||
|
||||
BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> wrapHandler( |
||||
ReactorHttpHandlerAdapter handlerAdapter) { |
||||
if (this.period == null) { |
||||
return handlerAdapter; |
||||
} |
||||
return (request, response) -> { |
||||
this.activeRequests.incrementAndGet(); |
||||
return handlerAdapter.apply(request, response).doOnTerminate(() -> this.activeRequests.decrementAndGet()); |
||||
}; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,126 @@
@@ -0,0 +1,126 @@
|
||||
/* |
||||
* Copyright 2012-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.web.embedded.tomcat; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.time.Duration; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.concurrent.atomic.AtomicLong; |
||||
|
||||
import org.apache.catalina.Container; |
||||
import org.apache.catalina.Service; |
||||
import org.apache.catalina.connector.Connector; |
||||
import org.apache.catalina.core.StandardWrapper; |
||||
import org.apache.catalina.startup.Tomcat; |
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
|
||||
import org.springframework.boot.web.server.GracefulShutdown; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
/** |
||||
* {@link GracefulShutdown} for {@link Tomcat}. |
||||
* |
||||
* @author Andy Wilkinson |
||||
*/ |
||||
class TomcatGracefulShutdown implements GracefulShutdown { |
||||
|
||||
private static final Log logger = LogFactory.getLog(TomcatGracefulShutdown.class); |
||||
|
||||
private final Tomcat tomcat; |
||||
|
||||
private final Duration period; |
||||
|
||||
private volatile boolean shuttingDown = false; |
||||
|
||||
TomcatGracefulShutdown(Tomcat tomcat, Duration period) { |
||||
this.tomcat = tomcat; |
||||
this.period = period; |
||||
} |
||||
|
||||
@Override |
||||
public boolean shutDownGracefully() { |
||||
logger.info("Commencing graceful shutdown, allowing up to " + this.period.getSeconds() |
||||
+ "s for active requests to complete"); |
||||
List<Connector> connectors = getConnectors(); |
||||
for (Connector connector : connectors) { |
||||
connector.pause(); |
||||
connector.getProtocolHandler().closeServerSocketGraceful(); |
||||
} |
||||
this.shuttingDown = true; |
||||
try { |
||||
long end = System.currentTimeMillis() + this.period.toMillis(); |
||||
for (Container host : this.tomcat.getEngine().findChildren()) { |
||||
for (Container context : host.findChildren()) { |
||||
while (active(context)) { |
||||
if (System.currentTimeMillis() > end) { |
||||
logger.info("Grace period elaped with one or more requests still active"); |
||||
return false; |
||||
} |
||||
Thread.sleep(50); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
catch (InterruptedException ex) { |
||||
Thread.currentThread().interrupt(); |
||||
} |
||||
finally { |
||||
this.shuttingDown = false; |
||||
} |
||||
logger.info("Graceful shutdown complete"); |
||||
return true; |
||||
} |
||||
|
||||
private boolean active(Container context) { |
||||
try { |
||||
Field field = ReflectionUtils.findField(context.getClass(), "inProgressAsyncCount"); |
||||
field.setAccessible(true); |
||||
AtomicLong inProgressAsyncCount = (AtomicLong) field.get(context); |
||||
if (inProgressAsyncCount.get() > 0) { |
||||
return true; |
||||
} |
||||
for (Container wrapper : context.findChildren()) { |
||||
if (((StandardWrapper) wrapper).getCountAllocated() > 0) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
catch (Exception ex) { |
||||
throw new RuntimeException(ex); |
||||
} |
||||
} |
||||
|
||||
private List<Connector> getConnectors() { |
||||
List<Connector> connectors = new ArrayList<>(); |
||||
for (Service service : this.tomcat.getServer().findServices()) { |
||||
for (Connector connector : service.findConnectors()) { |
||||
connectors.add(connector); |
||||
} |
||||
} |
||||
return connectors; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isShuttingDown() { |
||||
return this.shuttingDown; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* Copyright 2012-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.web.embedded.undertow; |
||||
|
||||
import java.time.Duration; |
||||
|
||||
import io.undertow.server.handlers.GracefulShutdownHandler; |
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
|
||||
import org.springframework.boot.web.server.GracefulShutdown; |
||||
|
||||
/** |
||||
* {@link GracefulShutdown} for Undertow. |
||||
* |
||||
* @author Andy Wilkinson |
||||
*/ |
||||
class UndertowGracefulShutdown implements GracefulShutdown { |
||||
|
||||
private static final Log logger = LogFactory.getLog(UndertowGracefulShutdown.class); |
||||
|
||||
private final GracefulShutdownHandler gracefulShutdownHandler; |
||||
|
||||
private final Duration period; |
||||
|
||||
private volatile boolean shuttingDown; |
||||
|
||||
UndertowGracefulShutdown(GracefulShutdownHandler gracefulShutdownHandler, Duration period) { |
||||
this.gracefulShutdownHandler = gracefulShutdownHandler; |
||||
this.period = period; |
||||
} |
||||
|
||||
@Override |
||||
public boolean shutDownGracefully() { |
||||
logger.info("Commencing graceful shutdown, allowing up to " + this.period.getSeconds() |
||||
+ "s for active requests to complete"); |
||||
this.gracefulShutdownHandler.shutdown(); |
||||
this.shuttingDown = true; |
||||
boolean graceful = false; |
||||
try { |
||||
graceful = this.gracefulShutdownHandler.awaitShutdown(this.period.toMillis()); |
||||
} |
||||
catch (InterruptedException ex) { |
||||
Thread.currentThread().interrupt(); |
||||
} |
||||
finally { |
||||
this.shuttingDown = false; |
||||
} |
||||
if (graceful) { |
||||
logger.info("Graceful shutdown complete"); |
||||
return true; |
||||
} |
||||
logger.info("Grace period elaped with one or more requests still active"); |
||||
return graceful; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isShuttingDown() { |
||||
return this.shuttingDown; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/* |
||||
* Copyright 2012-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.web.server; |
||||
|
||||
/** |
||||
* Handles graceful shutdown of a {@link WebServer}. |
||||
* |
||||
* @author Andy Wilkinson |
||||
* @since 2.3.0 |
||||
*/ |
||||
public interface GracefulShutdown { |
||||
|
||||
/** |
||||
* Shuts down the {@link WebServer}, returning {@code true} if activity ceased during |
||||
* the grace period, otherwise {@code false}. |
||||
* @return {@code true} if activity ceased during the grace period, otherwise |
||||
* {@code false} |
||||
*/ |
||||
boolean shutDownGracefully(); |
||||
|
||||
/** |
||||
* Returns whether the handler is in the process of gracefully shutting down the web |
||||
* server. |
||||
* @return {@code true} is graceful shutdown is in progress, otherwise {@code false}. |
||||
*/ |
||||
boolean isShuttingDown(); |
||||
|
||||
} |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
/* |
||||
* Copyright 2012-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.web.server; |
||||
|
||||
/** |
||||
* A {@link GracefulShutdown} that returns immediately with no grace period. |
||||
* |
||||
* @author Andy Wilkinson |
||||
* @since 2.3.0 |
||||
*/ |
||||
public class ImmediateGracefulShutdown implements GracefulShutdown { |
||||
|
||||
@Override |
||||
public boolean shutDownGracefully() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isShuttingDown() { |
||||
return false; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* Copyright 2012-2020 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.web.server; |
||||
|
||||
import java.time.Duration; |
||||
|
||||
/** |
||||
* Configuration for shutting down a {@link WebServer}. |
||||
* |
||||
* @author Andy Wilkinson |
||||
* @since 2.3.0 |
||||
*/ |
||||
public class Shutdown { |
||||
|
||||
/** |
||||
* Time to wait for web activity to cease before shutting down the application. By |
||||
* default, shutdown will proceed immediately. |
||||
*/ |
||||
private Duration gracePeriod; |
||||
|
||||
public Duration getGracePeriod() { |
||||
return this.gracePeriod; |
||||
} |
||||
|
||||
public void setGracePeriod(Duration period) { |
||||
this.gracePeriod = period; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue