You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2020/07/28 10:41:42 UTC

[camel] branch master updated: CAMEL-15343: Fixed camel-main to do graceful shutdown on JVM shutdown hook (eg SIGTERM or cltr+c).

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 <cl...@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 {