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 @@ |
|||||||
|
/* |
||||||
|
* 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