This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new b0fd4c0 CAMEL-15343: Fixed camel-main to do graceful shutdown on JVM shutdown hook (eg SIGTERM or cltr+c). b0fd4c0 is described below commit b0fd4c067d7318cf040d7afd3c31ef4af7b7b7fc Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Tue Jul 28 12:35:54 2020 +0200 CAMEL-15343: Fixed camel-main to do graceful shutdown on JVM shutdown hook (eg SIGTERM or cltr+c). --- .../main/java/org/apache/camel/spring/Main.java | 17 +++++--- .../camel/main/DefaultMainShutdownStrategy.java | 50 +++++++++++++++++++++- .../apache/camel/main/MainShutdownStrategy.java | 18 ++++++++ .../java/org/apache/camel/main/MainSupport.java | 2 +- .../camel/main/SimpleMainShutdownStrategy.java | 20 +++++++++ .../camel/main/MainSupportCommandLineTest.java | 1 + .../test/java/org/apache/camel/main/MainTest.java | 23 ++++++++-- 7 files changed, 120 insertions(+), 11 deletions(-) diff --git a/components/camel-spring-main/src/main/java/org/apache/camel/spring/Main.java b/components/camel-spring-main/src/main/java/org/apache/camel/spring/Main.java index da7480b..706d5cb 100644 --- a/components/camel-spring-main/src/main/java/org/apache/camel/spring/Main.java +++ b/components/camel-spring-main/src/main/java/org/apache/camel/spring/Main.java @@ -192,14 +192,19 @@ public class Main extends MainCommandLineSupport { @Override protected void doStop() throws Exception { - super.doStop(); - if (additionalApplicationContext != null) { - LOG.debug("Stopping Additional ApplicationContext: {}", additionalApplicationContext.getId()); + try { + if (additionalApplicationContext != null) { + LOG.debug("Stopping Additional ApplicationContext: {}", additionalApplicationContext.getId()); + additionalApplicationContext.stop(); + } + if (applicationContext != null) { + LOG.debug("Stopping Spring ApplicationContext: {}", applicationContext.getId()); + applicationContext.stop(); + } IOHelper.close(additionalApplicationContext); - } - if (applicationContext != null) { - LOG.debug("Stopping Spring ApplicationContext: {}", applicationContext.getId()); IOHelper.close(applicationContext); + } finally { + super.doStop(); } } diff --git a/core/camel-main/src/main/java/org/apache/camel/main/DefaultMainShutdownStrategy.java b/core/camel-main/src/main/java/org/apache/camel/main/DefaultMainShutdownStrategy.java index 638dc4b..e08bcad 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/DefaultMainShutdownStrategy.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/DefaultMainShutdownStrategy.java @@ -16,9 +16,14 @@ */ package org.apache.camel.main; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.camel.CamelContext; +import org.apache.camel.spi.CamelContextTracker; +import org.apache.camel.util.StopWatch; +import org.apache.camel.util.TimeUtils; import org.apache.camel.util.concurrent.ThreadHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,10 +36,12 @@ public class DefaultMainShutdownStrategy extends SimpleMainShutdownStrategy { protected static final Logger LOG = LoggerFactory.getLogger(DefaultMainShutdownStrategy.class); private final AtomicBoolean hangupIntercepted; + private final BaseMainSupport main; private volatile boolean hangupInterceptorEnabled; - public DefaultMainShutdownStrategy() { + public DefaultMainShutdownStrategy(BaseMainSupport main) { + this.main = main; this.hangupIntercepted = new AtomicBoolean(); } @@ -67,6 +74,47 @@ public class DefaultMainShutdownStrategy extends SimpleMainShutdownStrategy { private void handleHangup() { LOG.info("Received hang up - stopping the main instance."); + // and shutdown listener to allow camel context to graceful shutdown if JVM shutdown hook is triggered + // as otherwise the JVM terminates before Camel is graceful shutdown + addShutdownListener(() -> { + LOG.trace("OnShutdown"); + // attempt to wait for main to complete its shutdown of camel context + if (main.getCamelContext() != null) { + final CountDownLatch latch = new CountDownLatch(1); + // use tracker to know when camel context is destroyed so we can complete this listener quickly + CamelContextTracker tracker = new CamelContextTracker() { + @Override + public void contextDestroyed(CamelContext camelContext) { + latch.countDown(); + } + }; + tracker.open(); + + // use timeout from camel shutdown strategy and add 5 second extra to allow camel to shutdown graceful + long max = 5000 + main.getCamelContext().getShutdownStrategy().getTimeUnit().toMillis(main.getCamelContext().getShutdownStrategy().getTimeout()); + int waits = 0; + boolean done = false; + StopWatch watch = new StopWatch(); + while (!main.getCamelContext().isStopped() && !done && watch.taken() < max) { + String msg = "Waiting for CamelContext to graceful shutdown, elapsed: " + TimeUtils.printDuration(watch.taken()); + if (waits % 5 == 0) { + // do some info logging every 5th time + LOG.info(msg); + } else { + LOG.trace(msg); + } + waits++; + try { + // wait 1 sec and loop and log activity so we can see we are waiting + done = latch.await(1000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // ignore + } + } + tracker.close(); + } + LOG.trace("OnShutdown complete"); + }); shutdown(); } diff --git a/core/camel-main/src/main/java/org/apache/camel/main/MainShutdownStrategy.java b/core/camel-main/src/main/java/org/apache/camel/main/MainShutdownStrategy.java index 0a28303..c5c9cc2 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/MainShutdownStrategy.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/MainShutdownStrategy.java @@ -18,7 +18,25 @@ package org.apache.camel.main; import java.util.concurrent.TimeUnit; +/** + * Graceful shutdown when using Camel Main. + */ public interface MainShutdownStrategy { + + /** + * Event listener when shutting down. + */ + interface ShutdownEventListener { + + /** + * Callback on shutdown + */ + void onShutdown(); + + } + + void addShutdownListener(ShutdownEventListener listener); + /** * @return true if the application is allowed to run. */ diff --git a/core/camel-main/src/main/java/org/apache/camel/main/MainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/MainSupport.java index aeb8982..231fc7a 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/MainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/MainSupport.java @@ -45,7 +45,7 @@ public abstract class MainSupport extends BaseMainSupport { } protected MainSupport() { - this.shutdownStrategy = new DefaultMainShutdownStrategy(); + this.shutdownStrategy = new DefaultMainShutdownStrategy(this); } /** diff --git a/core/camel-main/src/main/java/org/apache/camel/main/SimpleMainShutdownStrategy.java b/core/camel-main/src/main/java/org/apache/camel/main/SimpleMainShutdownStrategy.java index 4fcd180..8b8b7a0 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/SimpleMainShutdownStrategy.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/SimpleMainShutdownStrategy.java @@ -16,6 +16,8 @@ */ package org.apache.camel.main; +import java.util.LinkedHashSet; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -26,6 +28,7 @@ import org.slf4j.LoggerFactory; public class SimpleMainShutdownStrategy implements MainShutdownStrategy { protected static final Logger LOG = LoggerFactory.getLogger(SimpleMainShutdownStrategy.class); + private final Set<ShutdownEventListener> listeners = new LinkedHashSet<>(); private final AtomicBoolean completed; private final CountDownLatch latch; @@ -40,9 +43,24 @@ public class SimpleMainShutdownStrategy implements MainShutdownStrategy { } @Override + public void addShutdownListener(ShutdownEventListener listener) { + listeners.add(listener); + } + + @Override public boolean shutdown() { if (completed.compareAndSet(false, true)) { + LOG.debug("Setting shutdown completed state from false to true"); latch.countDown(); + for (ShutdownEventListener l : listeners) { + try { + LOG.trace("ShutdownEventListener: {}", l); + l.onShutdown(); + } catch (Throwable e) { + // ignore as we must continue + LOG.debug("Error during ShutdownEventListener: {}. This exception is ignored.", l, e); + } + } return true; } @@ -51,11 +69,13 @@ public class SimpleMainShutdownStrategy implements MainShutdownStrategy { @Override public void await() throws InterruptedException { + LOG.debug("Await shutdown to complete"); latch.await(); } @Override public void await(long timeout, TimeUnit unit) throws InterruptedException { + LOG.debug("Await shutdown to complete with timeout: {} {}", timeout, unit); latch.await(timeout, unit); } } diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainSupportCommandLineTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainSupportCommandLineTest.java index bd8072c..3f22c32 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/MainSupportCommandLineTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainSupportCommandLineTest.java @@ -20,6 +20,7 @@ import org.apache.camel.CamelContext; import org.apache.camel.ProducerTemplate; import org.apache.camel.impl.DefaultCamelContext; import org.junit.jupiter.api.Test; + public class MainSupportCommandLineTest { private class MyMainSupport extends MainCommandLineSupport { diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainTest.java index d3e1b51..fac91b2 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/MainTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainTest.java @@ -58,11 +58,12 @@ public class MainTest { @Test public void testDisableHangupSupport() throws Exception { - DefaultMainShutdownStrategy shutdownStrategy = new DefaultMainShutdownStrategy(); - shutdownStrategy.disableHangupSupport(); - // lets make a simple route Main main = new Main(); + + DefaultMainShutdownStrategy shutdownStrategy = new DefaultMainShutdownStrategy(main); + shutdownStrategy.disableHangupSupport(); + main.setShutdownStrategy(shutdownStrategy); main.configure().addRoutesBuilder(new MyRouteBuilder()); main.enableTrace(); @@ -151,6 +152,22 @@ public class MainTest { main.stop(); } + @Test + public void testDurationIdleSeconds() throws Exception { + Main main = new Main(); + main.configure().addRoutesBuilder(new MyRouteBuilder()); + main.configure().withDurationMaxIdleSeconds(2); + main.run(); + } + + @Test + public void testDurationMaxSeconds() throws Exception { + Main main = new Main(); + main.configure().addRoutesBuilder(new MyRouteBuilder()); + main.configure().withDurationMaxSeconds(2); + main.run(); + } + public static class MyRouteBuilder extends RouteBuilder { @Override public void configure() throws Exception {