This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 0d861d06b5b CAMEL-22206: Do not use BackOff but BackgroundTask for 
reconnector (#18494)
0d861d06b5b is described below

commit 0d861d06b5b89525bad0287a116e6a941bd34981
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Jun 28 11:29:54 2025 +0200

    CAMEL-22206: Do not use BackOff but BackgroundTask for reconnector (#18494)
    
    * CAMEL-22206: camel-pgevent should not use BackOff but BackgroundTask for 
reconnector
    * CAMEL-22206: camel-master should not use BackOff but BackgroundTask for 
reconnector
    * CAMEL-22206: camel-sjms should not use BackOff but BackgroundTask for 
reconnector
---
 .../apache/camel/catalog/components/master.json    |  2 +-
 .../master/MasterComponentConfigurer.java          |  4 +-
 .../org/apache/camel/component/master/master.json  |  2 +-
 .../camel/component/master/MasterComponent.java    | 31 +--------
 .../camel/component/master/MasterConsumer.java     | 52 +++++++++------
 .../camel/component/master/MasterEndpoint.java     | 11 +---
 .../component/master/MasterComponentTest.java      |  8 ++-
 .../camel/component/pgevent/PgEventConsumer.java   | 51 +++++++++------
 .../consumer/SimpleMessageListenerContainer.java   | 76 ++++++++++++----------
 .../apache/camel/support/task/BackgroundTask.java  | 21 ++++++
 .../apache/camel/support/task/BlockingTask.java    |  7 ++
 .../apache/camel/support/task/ForegroundTask.java  | 12 +++-
 .../dsl/MasterComponentBuilderFactory.java         |  6 +-
 13 files changed, 164 insertions(+), 119 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/master.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/master.json
index 840a8e99738..0d27e7fa776 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/master.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/master.json
@@ -27,7 +27,7 @@
     "bridgeErrorHandler": { "index": 0, "kind": "property", "displayName": 
"Bridge Error Handler", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Allows for bridging the consumer to the Camel routing Error Handler, which 
mean any exceptions (if possible) occurred while the Camel consumer is trying 
to pickup incoming messages, or the like [...]
     "autowiredEnabled": { "index": 1, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
     "backOffDelay": { "index": 2, "kind": "property", "displayName": "Back Off 
Delay", "group": "advanced", "label": "advanced", "required": false, "type": 
"integer", "javaType": "long", "deprecated": false, "autowired": false, 
"secret": false, "description": "When the master becomes leader then backoff is 
in use to repeat starting the consumer until the consumer is successfully 
started or max attempts reached. This option is the delay in millis between 
start attempts." },
-    "backOffMaxAttempts": { "index": 3, "kind": "property", "displayName": 
"Back Off Max Attempts", "group": "advanced", "label": "advanced", "required": 
false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": 
false, "secret": false, "description": "When the master becomes leader then 
backoff is in use to repeat starting the consumer until the consumer is 
successfully started or max attempts reached. This option is the maximum number 
of attempts to try." },
+    "backOffMaxAttempts": { "index": 3, "kind": "property", "displayName": 
"Back Off Max Attempts", "group": "advanced", "label": "advanced", "required": 
false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": 
false, "secret": false, "description": "When the master becomes leader then 
backoff is in use to repeat starting the consumer until the consumer is 
successfully started or max attempts reached. This option is the maximum number 
of attempts to try." },
     "service": { "index": 4, "kind": "property", "displayName": "Service", 
"group": "advanced", "label": "advanced", "required": false, "type": "object", 
"javaType": "org.apache.camel.cluster.CamelClusterService", "deprecated": 
false, "autowired": false, "secret": false, "description": "Inject the service 
to use." },
     "serviceSelector": { "index": 5, "kind": "property", "displayName": 
"Service Selector", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": 
"org.apache.camel.cluster.CamelClusterService.Selector", "deprecated": false, 
"autowired": false, "secret": false, "description": "Inject the service 
selector used to lookup the CamelClusterService to use." }
   },
diff --git 
a/components/camel-master/src/generated/java/org/apache/camel/component/master/MasterComponentConfigurer.java
 
b/components/camel-master/src/generated/java/org/apache/camel/component/master/MasterComponentConfigurer.java
index 81a7ecfe5e7..2d0413d4bcd 100644
--- 
a/components/camel-master/src/generated/java/org/apache/camel/component/master/MasterComponentConfigurer.java
+++ 
b/components/camel-master/src/generated/java/org/apache/camel/component/master/MasterComponentConfigurer.java
@@ -28,7 +28,7 @@ public class MasterComponentConfigurer extends 
PropertyConfigurerSupport impleme
         case "backoffdelay":
         case "backOffDelay": target.setBackOffDelay(property(camelContext, 
long.class, value)); return true;
         case "backoffmaxattempts":
-        case "backOffMaxAttempts": 
target.setBackOffMaxAttempts(property(camelContext, long.class, value)); return 
true;
+        case "backOffMaxAttempts": 
target.setBackOffMaxAttempts(property(camelContext, int.class, value)); return 
true;
         case "bridgeerrorhandler":
         case "bridgeErrorHandler": 
target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); 
return true;
         case "service": target.setService(property(camelContext, 
org.apache.camel.cluster.CamelClusterService.class, value)); return true;
@@ -46,7 +46,7 @@ public class MasterComponentConfigurer extends 
PropertyConfigurerSupport impleme
         case "backoffdelay":
         case "backOffDelay": return long.class;
         case "backoffmaxattempts":
-        case "backOffMaxAttempts": return long.class;
+        case "backOffMaxAttempts": return int.class;
         case "bridgeerrorhandler":
         case "bridgeErrorHandler": return boolean.class;
         case "service": return 
org.apache.camel.cluster.CamelClusterService.class;
diff --git 
a/components/camel-master/src/generated/resources/META-INF/org/apache/camel/component/master/master.json
 
b/components/camel-master/src/generated/resources/META-INF/org/apache/camel/component/master/master.json
index 840a8e99738..0d27e7fa776 100644
--- 
a/components/camel-master/src/generated/resources/META-INF/org/apache/camel/component/master/master.json
+++ 
b/components/camel-master/src/generated/resources/META-INF/org/apache/camel/component/master/master.json
@@ -27,7 +27,7 @@
     "bridgeErrorHandler": { "index": 0, "kind": "property", "displayName": 
"Bridge Error Handler", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Allows for bridging the consumer to the Camel routing Error Handler, which 
mean any exceptions (if possible) occurred while the Camel consumer is trying 
to pickup incoming messages, or the like [...]
     "autowiredEnabled": { "index": 1, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
     "backOffDelay": { "index": 2, "kind": "property", "displayName": "Back Off 
Delay", "group": "advanced", "label": "advanced", "required": false, "type": 
"integer", "javaType": "long", "deprecated": false, "autowired": false, 
"secret": false, "description": "When the master becomes leader then backoff is 
in use to repeat starting the consumer until the consumer is successfully 
started or max attempts reached. This option is the delay in millis between 
start attempts." },
-    "backOffMaxAttempts": { "index": 3, "kind": "property", "displayName": 
"Back Off Max Attempts", "group": "advanced", "label": "advanced", "required": 
false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": 
false, "secret": false, "description": "When the master becomes leader then 
backoff is in use to repeat starting the consumer until the consumer is 
successfully started or max attempts reached. This option is the maximum number 
of attempts to try." },
+    "backOffMaxAttempts": { "index": 3, "kind": "property", "displayName": 
"Back Off Max Attempts", "group": "advanced", "label": "advanced", "required": 
false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": 
false, "secret": false, "description": "When the master becomes leader then 
backoff is in use to repeat starting the consumer until the consumer is 
successfully started or max attempts reached. This option is the maximum number 
of attempts to try." },
     "service": { "index": 4, "kind": "property", "displayName": "Service", 
"group": "advanced", "label": "advanced", "required": false, "type": "object", 
"javaType": "org.apache.camel.cluster.CamelClusterService", "deprecated": 
false, "autowired": false, "secret": false, "description": "Inject the service 
to use." },
     "serviceSelector": { "index": 5, "kind": "property", "displayName": 
"Service Selector", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": 
"org.apache.camel.cluster.CamelClusterService.Selector", "deprecated": false, 
"autowired": false, "secret": false, "description": "Inject the service 
selector used to lookup the CamelClusterService to use." }
   },
diff --git 
a/components/camel-master/src/main/java/org/apache/camel/component/master/MasterComponent.java
 
b/components/camel-master/src/main/java/org/apache/camel/component/master/MasterComponent.java
index b4d8bc4e59f..95ddbd92434 100644
--- 
a/components/camel-master/src/main/java/org/apache/camel/component/master/MasterComponent.java
+++ 
b/components/camel-master/src/main/java/org/apache/camel/component/master/MasterComponent.java
@@ -17,7 +17,6 @@
 package org.apache.camel.component.master;
 
 import java.util.Map;
-import java.util.concurrent.ScheduledExecutorService;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Endpoint;
@@ -46,9 +45,7 @@ public class MasterComponent extends DefaultComponent {
     @Metadata(label = "advanced")
     private long backOffDelay = 5000;
     @Metadata(label = "advanced")
-    private long backOffMaxAttempts = 10;
-
-    private ScheduledExecutorService backOffThreadPool;
+    private int backOffMaxAttempts = 10;
 
     public MasterComponent() {
         this(null);
@@ -111,10 +108,6 @@ public class MasterComponent extends DefaultComponent {
         this.serviceSelector = serviceSelector;
     }
 
-    public ScheduledExecutorService getBackOffThreadPool() {
-        return backOffThreadPool;
-    }
-
     public long getBackOffDelay() {
         return backOffDelay;
     }
@@ -129,7 +122,7 @@ public class MasterComponent extends DefaultComponent {
         this.backOffDelay = backOffDelay;
     }
 
-    public long getBackOffMaxAttempts() {
+    public int getBackOffMaxAttempts() {
         return backOffMaxAttempts;
     }
 
@@ -139,7 +132,7 @@ public class MasterComponent extends DefaultComponent {
      *
      * This option is the maximum number of attempts to try.
      */
-    public void setBackOffMaxAttempts(long backOffMaxAttempts) {
+    public void setBackOffMaxAttempts(int backOffMaxAttempts) {
         this.backOffMaxAttempts = backOffMaxAttempts;
     }
 
@@ -153,22 +146,4 @@ public class MasterComponent extends DefaultComponent {
                     () -> new IllegalStateException("No cluster service 
found"));
         }
     }
-
-    @Override
-    protected void doStart() throws Exception {
-        if (backOffThreadPool == null) {
-            backOffThreadPool
-                    = 
getCamelContext().getExecutorServiceManager().newDefaultScheduledThreadPool(this,
 "MasterLeaderTask");
-        }
-    }
-
-    @Override
-    protected void doStop() throws Exception {
-        super.doStop();
-
-        if (backOffThreadPool != null) {
-            
getCamelContext().getExecutorServiceManager().shutdown(backOffThreadPool);
-            backOffThreadPool = null;
-        }
-    }
 }
diff --git 
a/components/camel-master/src/main/java/org/apache/camel/component/master/MasterConsumer.java
 
b/components/camel-master/src/main/java/org/apache/camel/component/master/MasterConsumer.java
index 652c82e2865..603fa9d5073 100644
--- 
a/components/camel-master/src/main/java/org/apache/camel/component/master/MasterConsumer.java
+++ 
b/components/camel-master/src/main/java/org/apache/camel/component/master/MasterConsumer.java
@@ -16,6 +16,9 @@
  */
 package org.apache.camel.component.master;
 
+import java.time.Duration;
+import java.util.concurrent.ScheduledExecutorService;
+
 import org.apache.camel.Consumer;
 import org.apache.camel.Endpoint;
 import org.apache.camel.Processor;
@@ -31,11 +34,11 @@ import org.apache.camel.resume.ResumeAdapter;
 import org.apache.camel.resume.ResumeAware;
 import org.apache.camel.resume.ResumeStrategy;
 import org.apache.camel.support.DefaultConsumer;
-import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.resume.AdapterHelper;
 import org.apache.camel.support.service.ServiceHelper;
-import org.apache.camel.util.backoff.BackOff;
-import org.apache.camel.util.backoff.BackOffTimer;
+import org.apache.camel.support.task.BackgroundTask;
+import org.apache.camel.support.task.Tasks;
+import org.apache.camel.support.task.budget.Budgets;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -54,11 +57,10 @@ public class MasterConsumer extends DefaultConsumer 
implements ResumeAware<Resum
     private volatile Consumer delegatedConsumer;
     private volatile CamelClusterView view;
     private ResumeStrategy resumeStrategy;
-    private BackOffTimer timer;
+    private ScheduledExecutorService leaderPool;
 
     public MasterConsumer(MasterEndpoint masterEndpoint, Processor processor, 
CamelClusterService clusterService) {
         super(masterEndpoint, processor);
-
         this.clusterService = clusterService;
         this.masterEndpoint = masterEndpoint;
         this.delegatedEndpoint = masterEndpoint.getEndpoint();
@@ -79,16 +81,16 @@ public class MasterConsumer extends DefaultConsumer 
implements ResumeAware<Resum
     @Override
     protected void doInit() throws Exception {
         super.doInit();
-        this.timer = 
PluginHelper.getBackOffTimerFactory(masterEndpoint.getCamelContext().getCamelContextExtension())
-                .newBackOffTimer("MasterConsumer", 
masterEndpoint.getComponent().getBackOffThreadPool());
+
+        // used for re-connecting to the database
+        leaderPool = 
getEndpoint().getCamelContext().getExecutorServiceManager()
+                .newSingleThreadScheduledExecutor(this, "Leadership");
     }
 
     @Override
     protected void doStart() throws Exception {
         super.doStart();
 
-        ServiceHelper.startService(timer);
-
         LOG.debug("Using ClusterService instance {} (id={}, type={})", 
clusterService, clusterService.getId(),
                 clusterService.getClass().getName());
 
@@ -106,7 +108,8 @@ public class MasterConsumer extends DefaultConsumer 
implements ResumeAware<Resum
             view = null;
         }
 
-        ServiceHelper.stopAndShutdownServices(delegatedConsumer, 
delegatedEndpoint, timer);
+        
getEndpoint().getCamelContext().getExecutorServiceManager().shutdown(leaderPool);
+        ServiceHelper.stopAndShutdownServices(delegatedConsumer, 
delegatedEndpoint);
         delegatedConsumer = null;
     }
 
@@ -135,6 +138,18 @@ public class MasterConsumer extends DefaultConsumer 
implements ResumeAware<Resum
     // Helpers
     // **************************************
 
+    private BackgroundTask createTask() {
+        return Tasks.backgroundTask()
+                .withScheduledExecutor(leaderPool)
+                .withBudget(Budgets.iterationTimeBudget()
+                        
.withInterval(Duration.ofMillis(masterEndpoint.getComponent().getBackOffDelay()))
+                        .withInitialDelay(Duration.ofSeconds(1))
+                        
.withMaxIterations(masterEndpoint.getComponent().getBackOffMaxAttempts())
+                        .build())
+                .withName("Leadership")
+                .build();
+    }
+
     private void onLeadershipTaken() throws Exception {
         lock.lock();
         try {
@@ -146,13 +161,12 @@ public class MasterConsumer extends DefaultConsumer 
implements ResumeAware<Resum
                 return;
             }
 
-            // start consumer using background task up till X attempts
-            long delay = masterEndpoint.getComponent().getBackOffDelay();
-            long max = masterEndpoint.getComponent().getBackOffMaxAttempts();
-
-            
timer.schedule(BackOff.builder().delay(delay).maxAttempts(max).build(), task -> 
{
-                LOG.info("Leadership taken. Attempt #{} to start consumer: 
{}", task.getCurrentAttempts(),
-                        delegatedEndpoint);
+            final BackgroundTask leaderTask = createTask();
+            leaderTask.run(() -> {
+                if (!isRunAllowed()) {
+                    return false;
+                }
+                LOG.info("Leadership taken. Attempt #{} to start consumer: 
{}", leaderTask.iteration(), delegatedEndpoint);
 
                 Exception cause = null;
                 try {
@@ -179,13 +193,13 @@ public class MasterConsumer extends DefaultConsumer 
implements ResumeAware<Resum
                 }
 
                 if (cause != null) {
-                    String message = "Leadership taken. Attempt #" + 
task.getCurrentAttempts()
+                    String message = "Leadership taken. Attempt #" + 
leaderTask.iteration()
                                      + " failed to start consumer due to: " + 
cause.getMessage();
                     getExceptionHandler().handleException(message, cause);
                     return true; // retry
                 }
 
-                LOG.info("Leadership taken. Attempt #" + 
task.getCurrentAttempts() + " success. Consumer started: {}",
+                LOG.info("Leadership taken. Attempt #{} success. Consumer 
started: {}", leaderTask.iteration(),
                         delegatedEndpoint);
                 return false; // no more attempts
             });
diff --git 
a/components/camel-master/src/main/java/org/apache/camel/component/master/MasterEndpoint.java
 
b/components/camel-master/src/main/java/org/apache/camel/component/master/MasterEndpoint.java
index ff52293fe4b..64e22c5ae75 100644
--- 
a/components/camel-master/src/main/java/org/apache/camel/component/master/MasterEndpoint.java
+++ 
b/components/camel-master/src/main/java/org/apache/camel/component/master/MasterEndpoint.java
@@ -34,14 +34,10 @@ import org.apache.camel.support.DefaultEndpoint;
  * Have only a single consumer in a cluster consuming from a given endpoint; 
with automatic failover if the JVM dies.
  */
 @ManagedResource(description = "Managed Master Endpoint")
-@UriEndpoint(firstVersion = "2.20.0",
-             scheme = "master",
-             syntax = "master:namespace:delegateUri",
-             consumerOnly = true,
-             title = "Master",
-             lenientProperties = true,
-             category = { Category.CLUSTERING })
+@UriEndpoint(firstVersion = "2.20.0", scheme = "master", syntax = 
"master:namespace:delegateUri", title = "Master",
+             consumerOnly = true, lenientProperties = true, category = { 
Category.CLUSTERING })
 public class MasterEndpoint extends DefaultEndpoint implements 
DelegateEndpoint {
+
     private final Endpoint delegateEndpoint;
     private final CamelClusterService clusterService;
 
@@ -56,7 +52,6 @@ public class MasterEndpoint extends DefaultEndpoint 
implements DelegateEndpoint
     public MasterEndpoint(String uri, MasterComponent component, 
CamelClusterService clusterService, String namespace,
                           String delegateUri) {
         super(uri, component);
-
         this.clusterService = clusterService;
         this.namespace = namespace;
         this.delegateUri = delegateUri;
diff --git 
a/components/camel-master/src/test/java/org/apache/camel/component/master/MasterComponentTest.java
 
b/components/camel-master/src/test/java/org/apache/camel/component/master/MasterComponentTest.java
index 5fa7240cf08..6e069c1e644 100644
--- 
a/components/camel-master/src/test/java/org/apache/camel/component/master/MasterComponentTest.java
+++ 
b/components/camel-master/src/test/java/org/apache/camel/component/master/MasterComponentTest.java
@@ -90,11 +90,15 @@ public class MasterComponentTest {
             // Start the context after some random time so the startup order
             // changes for each test.
             
Awaitility.await().pollDelay(ThreadLocalRandom.current().nextInt(500), 
TimeUnit.MILLISECONDS)
-                    .untilAsserted(() -> 
Assertions.assertDoesNotThrow(context::start));
+                    .untilAsserted(() -> Assertions.assertDoesNotThrow(() -> {
+                        LOGGER.info("Starting node {}", id);
+                        context.start();
+                    }));
 
+            LOGGER.info("Waiting for {} events on node {}", events, id);
             contextLatch.await();
 
-            LOGGER.debug("Shutting down node {}", id);
+            LOGGER.info("Shutting down node {}", id);
             RESULTS.add(id);
 
             context.stop();
diff --git 
a/components/camel-pgevent/src/main/java/org/apache/camel/component/pgevent/PgEventConsumer.java
 
b/components/camel-pgevent/src/main/java/org/apache/camel/component/pgevent/PgEventConsumer.java
index e5d52ef9db1..666b61f4a1c 100644
--- 
a/components/camel-pgevent/src/main/java/org/apache/camel/component/pgevent/PgEventConsumer.java
+++ 
b/components/camel-pgevent/src/main/java/org/apache/camel/component/pgevent/PgEventConsumer.java
@@ -17,6 +17,7 @@
 package org.apache.camel.component.pgevent;
 
 import java.sql.PreparedStatement;
+import java.time.Duration;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ScheduledExecutorService;
 
@@ -26,10 +27,9 @@ import org.apache.camel.Exchange;
 import org.apache.camel.Message;
 import org.apache.camel.Processor;
 import org.apache.camel.support.DefaultConsumer;
-import org.apache.camel.support.PluginHelper;
-import org.apache.camel.support.service.ServiceHelper;
-import org.apache.camel.util.backoff.BackOff;
-import org.apache.camel.util.backoff.BackOffTimer;
+import org.apache.camel.support.task.BackgroundTask;
+import org.apache.camel.support.task.Tasks;
+import org.apache.camel.support.task.budget.Budgets;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -44,9 +44,9 @@ public class PgEventConsumer extends DefaultConsumer {
     private final PgEventEndpoint endpoint;
     private PGConnection dbConnection;
     private ScheduledExecutorService reconnectPool;
+    private BackgroundTask reconnectTask;
     private ExecutorService workerPool;
     private boolean shutdownWorkerPool;
-    private BackOffTimer timer;
 
     public PgEventConsumer(PgEventEndpoint endpoint, Processor processor) {
         super(endpoint, processor);
@@ -67,14 +67,20 @@ public class PgEventConsumer extends DefaultConsumer {
         }
         // used for re-connecting to the database
         reconnectPool = 
getEndpoint().getCamelContext().getExecutorServiceManager()
-                .newSingleThreadScheduledExecutor(this, "Reconnector");
-        timer = 
PluginHelper.getBackOffTimerFactory(endpoint.getCamelContext().getCamelContextExtension())
-                .newBackOffTimer("PgEventConsumer", reconnectPool);
+                .newSingleThreadScheduledExecutor(this, "PgEventReconnect");
+        reconnectTask = Tasks.backgroundTask()
+                .withScheduledExecutor(reconnectPool)
+                .withBudget(Budgets.iterationTimeBudget()
+                        
.withInterval(Duration.ofMillis(endpoint.getReconnectDelay()))
+                        .withInitialDelay(Duration.ofSeconds(1))
+                        .withUnlimitedDuration()
+                        .build())
+                .withName("PgEventReconnect")
+                .build();
     }
 
     @Override
     protected void doStart() throws Exception {
-        ServiceHelper.startService(timer);
         listener.initConnection();
         super.doStart();
     }
@@ -89,23 +95,26 @@ public class PgEventConsumer extends DefaultConsumer {
             
endpoint.getCamelContext().getExecutorServiceManager().shutdownGraceful(workerPool,
 10000);
             workerPool = null;
         }
-        ServiceHelper.stopService(timer);
     }
 
     public class PgEventListener implements PGNotificationListener {
 
         public void reconnect() {
-            BackOff bo = 
BackOff.builder().delay(endpoint.getReconnectDelay()).build();
-            timer.schedule(bo, t -> {
-                LOG.debug("Connecting attempt #{}", t.getCurrentAttempts());
-                try {
-                    initConnection();
-                } catch (Exception e) {
-                    return true;
-                }
-                LOG.debug("Connecting successful");
-                return false;
-            });
+            // only submit the task if not already running
+            if (!reconnectTask.isRunning()) {
+                reconnectTask.run(() -> {
+                    if (isRunAllowed()) {
+                        LOG.debug("Connecting attempt #{}", 
reconnectTask.iteration());
+                        try {
+                            initConnection();
+                        } catch (Exception e) {
+                            return true;
+                        }
+                        LOG.debug("Connecting successful");
+                    }
+                    return false;
+                });
+            }
         }
 
         public void initConnection() throws Exception {
diff --git 
a/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/consumer/SimpleMessageListenerContainer.java
 
b/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/consumer/SimpleMessageListenerContainer.java
index bcf9c56fc91..23723daf9a4 100644
--- 
a/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/consumer/SimpleMessageListenerContainer.java
+++ 
b/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/consumer/SimpleMessageListenerContainer.java
@@ -16,8 +16,10 @@
  */
 package org.apache.camel.component.sjms.consumer;
 
+import java.time.Duration;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
@@ -35,11 +37,10 @@ import org.apache.camel.CamelContext;
 import org.apache.camel.component.sjms.SessionMessageListener;
 import org.apache.camel.component.sjms.SjmsEndpoint;
 import org.apache.camel.component.sjms.jms.DestinationCreationStrategy;
-import org.apache.camel.support.PluginHelper;
-import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.support.service.ServiceSupport;
-import org.apache.camel.util.backoff.BackOff;
-import org.apache.camel.util.backoff.BackOffTimer;
+import org.apache.camel.support.task.BackgroundTask;
+import org.apache.camel.support.task.Tasks;
+import org.apache.camel.support.task.budget.Budgets;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -64,9 +65,9 @@ public class SimpleMessageListenerContainer extends 
ServiceSupport
     private final Lock consumerLock = new ReentrantLock();
     private Set<MessageConsumer> consumers;
     private Set<Session> sessions;
-    private BackOffTimer.Task recoverTask;
-    private BackOffTimer timer;
-    private ScheduledExecutorService scheduler;
+    private ScheduledExecutorService recoverPool;
+    private BackgroundTask recoverTask;
+    private Future<?> recoverFuture;
 
     public SimpleMessageListenerContainer(SjmsEndpoint endpoint) {
         this.endpoint = endpoint;
@@ -196,17 +197,17 @@ public class SimpleMessageListenerContainer extends 
ServiceSupport
         scheduleConnectionRecovery();
     }
 
-    protected boolean recoverConnection(BackOffTimer.Task task) throws 
Exception {
-        LOG.debug("Recovering from JMS Connection exception (attempt: {})", 
task.getCurrentAttempts());
+    protected boolean recoverConnection(BackgroundTask task) {
+        LOG.debug("Recovering from JMS Connection exception (attempt: {})", 
task.iteration());
         try {
             refreshConnection();
             initConsumers();
-            LOG.debug("Successfully recovered JMS Connection (attempt: {})", 
task.getCurrentAttempts());
+            LOG.debug("Successfully recovered JMS Connection (attempt: {})", 
task.iteration());
             // success so do not try again
             return false;
         } catch (Exception e) {
             if (LOG.isDebugEnabled()) {
-                LOG.debug("Failed to recover JMS Connection. Will try again in 
{} millis", task.getCurrentDelay(), e);
+                LOG.debug("Failed to recover JMS Connection. Will try again in 
{} millis", endpoint.getRecoveryInterval(), e);
             }
             // try again
             return true;
@@ -214,24 +215,33 @@ public class SimpleMessageListenerContainer extends 
ServiceSupport
     }
 
     protected void scheduleConnectionRecovery() {
-        if (scheduler == null) {
-            this.scheduler = 
endpoint.getCamelContext().getExecutorServiceManager().newSingleThreadScheduledExecutor(this,
-                    "SimpleMessageListenerContainer");
-        }
-
-        // we need to recover using a background task
-        if (recoverTask == null || recoverTask.getStatus() != 
BackOffTimer.Task.Status.Active) {
-            BackOff backOff = 
BackOff.builder().delay(endpoint.getRecoveryInterval()).build();
-            if (timer == null) {
-                timer = 
PluginHelper.getBackOffTimerFactory(endpoint.getCamelContext().getCamelContextExtension())
-                        .newBackOffTimer("SjmsConnectionRecovery",
-                                scheduler);
-                ServiceHelper.startService(timer);
+        connectionLock.lock();
+        try {
+            if (recoverPool == null) {
+                recoverPool = 
endpoint.getCamelContext().getExecutorServiceManager().newSingleThreadScheduledExecutor(this,
+                        "SjmsConnectionRecovery");
+            }
+            if (recoverTask == null) {
+                recoverTask = createTask();
+                recoverFuture = recoverTask.schedule(() -> 
recoverConnection(recoverTask));
             }
-            recoverTask = timer.schedule(backOff, this::recoverConnection);
+        } finally {
+            connectionLock.unlock();
         }
     }
 
+    private BackgroundTask createTask() {
+        return Tasks.backgroundTask()
+                .withScheduledExecutor(recoverPool)
+                .withBudget(Budgets.iterationTimeBudget()
+                        
.withInterval(Duration.ofMillis(endpoint.getRecoveryInterval()))
+                        .withInitialDelay(Duration.ofSeconds(1))
+                        .withUnlimitedDuration()
+                        .build())
+                .withName("SjmsConnectionRecovery")
+                .build();
+    }
+
     @Override
     protected void doStart() throws Exception {
         createConnection();
@@ -242,17 +252,17 @@ public class SimpleMessageListenerContainer extends 
ServiceSupport
 
     @Override
     protected void doStop() throws Exception {
-        if (recoverTask != null) {
-            recoverTask.cancel();
-        }
         stopConnection();
         stopConsumers();
-        if (scheduler != null) {
-            
endpoint.getCamelContext().getExecutorServiceManager().shutdown(scheduler);
-            scheduler = null;
+        if (recoverPool != null) {
+            
endpoint.getCamelContext().getExecutorServiceManager().shutdown(recoverPool);
+            recoverPool = null;
+        }
+        if (recoverFuture != null && recoverTask != null && 
recoverTask.isRunning()) {
+            recoverFuture.cancel(true);
+            recoverTask = null;
+            recoverFuture = null;
         }
-        ServiceHelper.stopService(timer);
-        timer = null;
     }
 
     @Override
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/task/BackgroundTask.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/task/BackgroundTask.java
index 3df28497529..ff829d31ec1 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/task/BackgroundTask.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/task/BackgroundTask.java
@@ -77,6 +77,7 @@ public class BackgroundTask implements BlockingTask {
     private final String name;
     private final CountDownLatch latch = new CountDownLatch(1);
     private Duration elapsed = Duration.ZERO;
+    private final AtomicBoolean running = new AtomicBoolean();
     private final AtomicBoolean completed = new AtomicBoolean();
 
     BackgroundTask(TimeBudget budget, ScheduledExecutorService service, String 
name) {
@@ -105,8 +106,22 @@ public class BackgroundTask implements BlockingTask {
         }
     }
 
+    /**
+     * Schedules the task to be run
+     *
+     * @param  supplier the task as a boolean supplier. The result is used to 
check if the task has completed or not.
+     *                  The supplier must return true if the execution has 
completed or false otherwise.
+     * @return          a future for the task
+     */
+    public Future<?> schedule(BooleanSupplier supplier) {
+        running.set(true);
+        return service.scheduleAtFixedRate(() -> runTaskWrapper(supplier), 
budget.initialDelay(),
+                budget.interval(), TimeUnit.MILLISECONDS);
+    }
+
     @Override
     public boolean run(BooleanSupplier supplier) {
+        running.set(true);
         Future<?> task = service.scheduleAtFixedRate(() -> 
runTaskWrapper(supplier), budget.initialDelay(),
                 budget.interval(), TimeUnit.MILLISECONDS);
         waitForTaskCompletion(task);
@@ -133,9 +148,15 @@ public class BackgroundTask implements BlockingTask {
             Thread.currentThread().interrupt();
         } finally {
             elapsed = budget.elapsed();
+            running.set(false);
         }
     }
 
+    @Override
+    public boolean isRunning() {
+        return running.get();
+    }
+
     @Override
     public Duration elapsed() {
         return elapsed;
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/task/BlockingTask.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/task/BlockingTask.java
index 54b61116c93..b07f05d5eb8 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/task/BlockingTask.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/task/BlockingTask.java
@@ -49,4 +49,11 @@ public interface BlockingTask extends Task {
      *                  task was interrupted.
      */
     boolean run(BooleanSupplier supplier);
+
+    /**
+     * Whether the task has been submitted for running (the state of the task 
can be waiting for next run etc).
+     *
+     * @return true if the run method has been called.
+     */
+    boolean isRunning();
 }
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/task/ForegroundTask.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/task/ForegroundTask.java
index 3fa8f8abecc..24a0d08aa29 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/task/ForegroundTask.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/task/ForegroundTask.java
@@ -18,6 +18,7 @@ package org.apache.camel.support.task;
 
 import java.time.Duration;
 import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BooleanSupplier;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -73,6 +74,7 @@ public class ForegroundTask implements BlockingTask {
     private final String name;
     private final IterationBudget budget;
     private Duration elapsed = Duration.ZERO;
+    private final AtomicBoolean running = new AtomicBoolean();
 
     ForegroundTask(IterationBudget budget, String name) {
         this.budget = budget;
@@ -81,6 +83,7 @@ public class ForegroundTask implements BlockingTask {
 
     @Override
     public boolean run(BooleanSupplier supplier) {
+        running.set(true);
         boolean completed = false;
 
         try {
@@ -93,7 +96,6 @@ public class ForegroundTask implements BlockingTask {
                     LOG.debug("Task {} is complete after {} iterations and it 
is ready to continue",
                             name, budget.iteration());
                     completed = true;
-
                     break;
                 }
 
@@ -106,6 +108,7 @@ public class ForegroundTask implements BlockingTask {
             Thread.currentThread().interrupt();
         } finally {
             elapsed = budget.elapsed();
+            running.set(false);
         }
 
         return completed;
@@ -120,6 +123,7 @@ public class ForegroundTask implements BlockingTask {
      * @return           An optional with the result
      */
     public <T> Optional<T> run(Supplier<T> supplier, Predicate<T> predicate) {
+        running.set(true);
         try {
             if (budget.initialDelay() > 0) {
                 Thread.sleep(budget.initialDelay());
@@ -142,11 +146,17 @@ public class ForegroundTask implements BlockingTask {
             Thread.currentThread().interrupt();
         } finally {
             elapsed = budget.elapsed();
+            running.set(false);
         }
 
         return Optional.empty();
     }
 
+    @Override
+    public boolean isRunning() {
+        return running.get();
+    }
+
     @Override
     public Duration elapsed() {
         return elapsed;
diff --git 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MasterComponentBuilderFactory.java
 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MasterComponentBuilderFactory.java
index 7d202c99539..4dc16924c10 100644
--- 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MasterComponentBuilderFactory.java
+++ 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/MasterComponentBuilderFactory.java
@@ -126,14 +126,14 @@ public interface MasterComponentBuilderFactory {
          * max attempts reached. This option is the maximum number of attempts
          * to try.
          * 
-         * The option is a: &lt;code&gt;long&lt;/code&gt; type.
+         * The option is a: &lt;code&gt;int&lt;/code&gt; type.
          * 
          * Group: advanced
          * 
          * @param backOffMaxAttempts the value to set
          * @return the dsl builder
          */
-        default MasterComponentBuilder backOffMaxAttempts(long 
backOffMaxAttempts) {
+        default MasterComponentBuilder backOffMaxAttempts(int 
backOffMaxAttempts) {
             doSetProperty("backOffMaxAttempts", backOffMaxAttempts);
             return this;
         }
@@ -189,7 +189,7 @@ public interface MasterComponentBuilderFactory {
             case "bridgeErrorHandler": ((MasterComponent) 
component).setBridgeErrorHandler((boolean) value); return true;
             case "autowiredEnabled": ((MasterComponent) 
component).setAutowiredEnabled((boolean) value); return true;
             case "backOffDelay": ((MasterComponent) 
component).setBackOffDelay((long) value); return true;
-            case "backOffMaxAttempts": ((MasterComponent) 
component).setBackOffMaxAttempts((long) value); return true;
+            case "backOffMaxAttempts": ((MasterComponent) 
component).setBackOffMaxAttempts((int) value); return true;
             case "service": ((MasterComponent) 
component).setService((org.apache.camel.cluster.CamelClusterService) value); 
return true;
             case "serviceSelector": ((MasterComponent) 
component).setServiceSelector((org.apache.camel.cluster.CamelClusterService.Selector)
 value); return true;
             default: return false;


Reply via email to