Add support for gracefully shutting down the web server
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
parent
067accb3a8
commit
308e1d3675
@ -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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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