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

gnodet 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 5e245ae048ac CAMEL-24272: Migrate reconnection loops from 
ForegroundTask to BackgroundTask
5e245ae048ac is described below

commit 5e245ae048ac2e9a78bdd9f806ff78a071a633da
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue Jul 28 09:35:21 2026 +0200

    CAMEL-24272: Migrate reconnection loops from ForegroundTask to 
BackgroundTask
    
    Migrate 9 components from ForegroundTask to BackgroundTask so their
    reconnection/retry tasks register with TaskManagerRegistry and become
    visible in the TUI, CLI, and dev console internal task views.
    
    Components migrated: FTP, SFTP, MLLP, Infinispan, Google Pub/Sub,
    ZooKeeper, Hazelcast SEDA, Salesforce, and MongoDB GridFS. Each now
    uses a ScheduledExecutorService managed through Camel's
    ExecutorServiceManager for proper lifecycle handling.
    
    Closes #25164
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../camel/component/file/remote/FtpOperations.java | 55 ++++++++++-------
 .../component/file/remote/SftpOperations.java      | 36 +++++++----
 .../google/pubsub/GooglePubsubConsumer.java        | 16 ++++-
 .../hazelcast/seda/HazelcastSedaConsumer.java      | 16 ++++-
 .../infinispan/remote/InfinispanRemoteManager.java | 70 ++++++++++++----------
 .../mllp/internal/TcpServerBindThread.java         | 35 +++++++----
 .../component/mongodb/gridfs/GridFsConsumer.java   | 18 ++++--
 .../internal/streaming/SubscriptionHelper.java     | 26 ++++++--
 .../component/zookeeper/ZooKeeperConsumer.java     | 16 ++++-
 .../jbang/core/commands/tui/TabRegistryTest.java   |  4 +-
 10 files changed, 192 insertions(+), 100 deletions(-)

diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
index b25f13eed9c9..1dc597eb7944 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
@@ -25,6 +25,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.time.Duration;
 import java.util.Iterator;
+import java.util.concurrent.ScheduledExecutorService;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.InvalidPayloadException;
@@ -131,33 +132,43 @@ public class FtpOperations implements 
RemoteFileOperations<FTPFile> {
                     client.getConnectTimeout());
         }
 
-        BlockingTask task = Tasks.foregroundTask()
-                .withBudget(Budgets.iterationBudget()
-                        
.withMaxIterations(Budgets.atLeastOnce(endpoint.getMaximumReconnectAttempts()))
-                        
.withInterval(Duration.ofMillis(endpoint.getReconnectDelay()))
-                        .build())
-                .build();
-
-        TaskPayload payload = new TaskPayload(configuration);
-
-        if (!task.run(endpoint.getCamelContext(), this::tryConnect, payload)) {
-            if (exchange != null) {
-                exchange.getIn().setHeader(FtpConstants.FTP_REPLY_CODE, 
client.getReplyCode());
-                exchange.getIn().setHeader(FtpConstants.FTP_REPLY_STRING, 
client.getReplyString());
-            }
+        ScheduledExecutorService ses = 
endpoint.getCamelContext().getExecutorServiceManager()
+                .newSingleThreadScheduledExecutor(this, "FtpReconnect");
+        try {
+            BlockingTask task = Tasks.backgroundTask()
+                    .withBudget(Budgets.iterationTimeBudget()
+                            
.withMaxIterations(Budgets.atLeastOnce(endpoint.getMaximumReconnectAttempts()))
+                            .withUnlimitedDuration()
+                            
.withInterval(Duration.ofMillis(endpoint.getReconnectDelay()))
+                            .build())
+                    .withScheduledExecutor(ses)
+                    .withName("FtpReconnect")
+                    .build();
+
+            TaskPayload payload = new TaskPayload(configuration);
+
+            if (!task.run(endpoint.getCamelContext(), this::tryConnect, 
payload)) {
+                if (exchange != null) {
+                    exchange.getIn().setHeader(FtpConstants.FTP_REPLY_CODE, 
client.getReplyCode());
+                    exchange.getIn().setHeader(FtpConstants.FTP_REPLY_STRING, 
client.getReplyString());
+                }
 
-            if (payload.exception != null) {
-                if (payload.exception instanceof 
GenericFileOperationFailedException genericFileOperationFailedException) {
-                    throw genericFileOperationFailedException;
+                if (payload.exception != null) {
+                    if (payload.exception instanceof 
GenericFileOperationFailedException genericFileOperationFailedException) {
+                        throw genericFileOperationFailedException;
+                    } else {
+                        throw new GenericFileOperationFailedException(
+                                client.getReplyCode(), 
client.getReplyString(), payload.exception.getMessage(),
+                                payload.exception);
+                    }
                 } else {
                     throw new GenericFileOperationFailedException(
-                            client.getReplyCode(), client.getReplyString(), 
payload.exception.getMessage(), payload.exception);
+                            client.getReplyCode(), client.getReplyString(),
+                            "Server refused connection");
                 }
-            } else {
-                throw new GenericFileOperationFailedException(
-                        client.getReplyCode(), client.getReplyString(),
-                        "Server refused connection");
             }
+        } finally {
+            
endpoint.getCamelContext().getExecutorServiceManager().shutdown(ses);
         }
 
         // we are now connected
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
index a7a09cc921ea..e921423868fa 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
@@ -35,6 +35,7 @@ import java.time.Duration;
 import java.util.Base64;
 import java.util.List;
 import java.util.Vector;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.regex.Pattern;
@@ -132,19 +133,28 @@ public class SftpOperations implements 
RemoteFileOperations<SftpRemoteFile> {
                 return true;
             }
 
-            BlockingTask task = Tasks.foregroundTask()
-                    .withBudget(Budgets.iterationBudget()
-                            
.withMaxIterations(Budgets.atLeastOnce(endpoint.getMaximumReconnectAttempts()))
-                            
.withInterval(Duration.ofMillis(endpoint.getReconnectDelay()))
-                            .build())
-                    .build();
-
-            TaskPayload payload = new TaskPayload(configuration);
-
-            if (!task.run(endpoint.getCamelContext(), this::tryConnect, 
payload)) {
-                throw new GenericFileOperationFailedException(
-                        "Cannot connect to " + 
configuration.remoteServerInformation(),
-                        payload.exception);
+            ScheduledExecutorService ses = 
endpoint.getCamelContext().getExecutorServiceManager()
+                    .newSingleThreadScheduledExecutor(this, "SftpReconnect");
+            try {
+                BlockingTask task = Tasks.backgroundTask()
+                        .withBudget(Budgets.iterationTimeBudget()
+                                
.withMaxIterations(Budgets.atLeastOnce(endpoint.getMaximumReconnectAttempts()))
+                                .withUnlimitedDuration()
+                                
.withInterval(Duration.ofMillis(endpoint.getReconnectDelay()))
+                                .build())
+                        .withScheduledExecutor(ses)
+                        .withName("SftpReconnect")
+                        .build();
+
+                TaskPayload payload = new TaskPayload(configuration);
+
+                if (!task.run(endpoint.getCamelContext(), this::tryConnect, 
payload)) {
+                    throw new GenericFileOperationFailedException(
+                            "Cannot connect to " + 
configuration.remoteServerInformation(),
+                            payload.exception);
+                }
+            } finally {
+                
endpoint.getCamelContext().getExecutorServiceManager().shutdown(ses);
             }
 
             configureBulkRequests();
diff --git 
a/components/camel-google/camel-google-pubsub/src/main/java/org/apache/camel/component/google/pubsub/GooglePubsubConsumer.java
 
b/components/camel-google/camel-google-pubsub/src/main/java/org/apache/camel/component/google/pubsub/GooglePubsubConsumer.java
index 49cc2ebccf2c..40a4f74c8998 100644
--- 
a/components/camel-google/camel-google-pubsub/src/main/java/org/apache/camel/component/google/pubsub/GooglePubsubConsumer.java
+++ 
b/components/camel-google/camel-google-pubsub/src/main/java/org/apache/camel/component/google/pubsub/GooglePubsubConsumer.java
@@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import com.google.api.core.AbstractApiService;
@@ -67,6 +68,7 @@ public class GooglePubsubConsumer extends DefaultConsumer 
implements ShutdownAwa
     private final Processor processor;
     private final AtomicInteger pendingExchanges = new AtomicInteger();
     private ExecutorService executor;
+    private ScheduledExecutorService taskExecutor;
     private final List<Subscriber> subscribers;
     private final Set<ApiFuture<PullResponse>> pendingSynchronousPullResponses;
     private final HeaderFilterStrategy headerFilterStrategy;
@@ -101,6 +103,8 @@ public class GooglePubsubConsumer extends DefaultConsumer 
implements ShutdownAwa
         }
 
         executor = endpoint.createExecutor(this);
+        taskExecutor = endpoint.getCamelContext().getExecutorServiceManager()
+                .newSingleThreadScheduledExecutor(this, "PubSubReconnectTask");
         for (int i = 0; i < endpoint.getConcurrentConsumers(); i++) {
             executor.submit(new SubscriberWrapper());
         }
@@ -122,6 +126,10 @@ public class GooglePubsubConsumer extends DefaultConsumer 
implements ShutdownAwa
 
     @Override
     protected void doShutdown() throws Exception {
+        if (taskExecutor != null) {
+            
getEndpoint().getCamelContext().getExecutorServiceManager().shutdown(taskExecutor);
+            taskExecutor = null;
+        }
         if (executor != null) {
             if (getEndpoint() != null && getEndpoint().getCamelContext() != 
null) {
                 
getEndpoint().getCamelContext().getExecutorServiceManager().shutdownGraceful(executor);
@@ -285,12 +293,14 @@ public class GooglePubsubConsumer extends DefaultConsumer 
implements ShutdownAwa
 
                     // Add backoff delay for recoverable errors to prevent 
tight loop
                     // We use initialDelay for the actual delay, and 
maxIterations(1) to run once
-                    Tasks.foregroundTask()
-                            .withBudget(Budgets.iterationBudget()
+                    Tasks.backgroundTask()
+                            .withBudget(Budgets.iterationTimeBudget()
                                     .withMaxIterations(1)
                                     .withInitialDelay(Duration.ofSeconds(5))
-                                    .withInterval(Duration.ZERO)
+                                    .withInterval(Duration.ofMillis(1))
+                                    .withUnlimitedDuration()
                                     .build())
+                            .withScheduledExecutor(taskExecutor)
                             .withName("PubSubRetryDelay")
                             .build()
                             .run(getEndpoint().getCamelContext(), () -> true);
diff --git 
a/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/seda/HazelcastSedaConsumer.java
 
b/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/seda/HazelcastSedaConsumer.java
index 81110a3ff295..bae894aebd1e 100644
--- 
a/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/seda/HazelcastSedaConsumer.java
+++ 
b/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/seda/HazelcastSedaConsumer.java
@@ -18,6 +18,7 @@ package org.apache.camel.component.hazelcast.seda;
 
 import java.time.Duration;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 import com.hazelcast.collection.BaseQueue;
@@ -46,6 +47,7 @@ public class HazelcastSedaConsumer extends DefaultConsumer 
implements Runnable {
     private final HazelcastSedaEndpoint endpoint;
     private final AsyncProcessor processor;
     private ExecutorService executor;
+    private ScheduledExecutorService taskExecutor;
 
     public HazelcastSedaConsumer(final Endpoint endpoint, final Processor 
processor) {
         super(endpoint, processor);
@@ -55,6 +57,8 @@ public class HazelcastSedaConsumer extends DefaultConsumer 
implements Runnable {
 
     @Override
     protected void doStart() throws Exception {
+        taskExecutor = endpoint.getCamelContext().getExecutorServiceManager()
+                .newSingleThreadScheduledExecutor(this, 
"HazelcastSedaRecoveryTask");
         int concurrentConsumers = 
endpoint.getConfiguration().getConcurrentConsumers();
         executor = 
endpoint.getCamelContext().getExecutorServiceManager().newFixedThreadPool(this, 
endpoint.getEndpointUri(),
                 concurrentConsumers);
@@ -67,6 +71,10 @@ public class HazelcastSedaConsumer extends DefaultConsumer 
implements Runnable {
 
     @Override
     protected void doStop() throws Exception {
+        if (taskExecutor != null) {
+            
endpoint.getCamelContext().getExecutorServiceManager().shutdown(taskExecutor);
+            taskExecutor = null;
+        }
         if (executor != null) {
             
endpoint.getCamelContext().getExecutorServiceManager().shutdown(executor);
             executor = null;
@@ -153,12 +161,14 @@ public class HazelcastSedaConsumer extends 
DefaultConsumer implements Runnable {
                 getExceptionHandler().handleException("Error processing 
exchange", exchange, e);
                 // Use Camel's task API for error recovery delay instead of 
Thread.sleep()
                 // We use initialDelay for the actual delay, and 
maxIterations(1) to run once
-                Tasks.foregroundTask()
-                        .withBudget(Budgets.iterationBudget()
+                Tasks.backgroundTask()
+                        .withBudget(Budgets.iterationTimeBudget()
                                 .withMaxIterations(1)
                                 
.withInitialDelay(Duration.ofMillis(endpoint.getConfiguration().getOnErrorDelay()))
-                                .withInterval(Duration.ZERO)
+                                .withInterval(Duration.ofMillis(1))
+                                .withUnlimitedDuration()
                                 .build())
+                        .withScheduledExecutor(taskExecutor)
                         .withName("HazelcastSedaErrorRecoveryDelay")
                         .build()
                         .run(getEndpoint().getCamelContext(), () -> true);
diff --git 
a/components/camel-infinispan/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteManager.java
 
b/components/camel-infinispan/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteManager.java
index 68f7acdd19e9..092ce4e7f6f3 100644
--- 
a/components/camel-infinispan/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteManager.java
+++ 
b/components/camel-infinispan/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteManager.java
@@ -19,13 +19,14 @@ package org.apache.camel.component.infinispan.remote;
 import java.time.Duration;
 import java.util.Properties;
 import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.component.infinispan.InfinispanManager;
 import org.apache.camel.component.infinispan.InfinispanUtil;
 import 
org.apache.camel.component.infinispan.remote.embeddingstore.EmbeddingStoreUtil;
 import org.apache.camel.support.service.ServiceSupport;
-import org.apache.camel.support.task.ForegroundTask;
+import org.apache.camel.support.task.BackgroundTask;
 import org.apache.camel.support.task.Tasks;
 import org.apache.camel.support.task.budget.Budgets;
 import org.apache.camel.util.ObjectHelper;
@@ -148,38 +149,45 @@ public class InfinispanRemoteManager extends 
ServiceSupport implements Infinispa
      */
     private void registerSchemaWithRetry() throws Exception {
         Duration timeout = 
configuration.getEmbeddingStoreSchemaRegistrationTimeout();
-        ForegroundTask task = Tasks.foregroundTask()
-                .withName("infinispan-schema-registration")
-                .withBudget(Budgets.iterationTimeBudget()
-                        .withInterval(Duration.ofSeconds(1))
-                        .withMaxDuration(timeout)
-                        .build())
-                .build();
-
-        final boolean[] firstAttempt = { true };
-        boolean registered = task.run(camelContext, () -> {
-            try {
-                EmbeddingStoreUtil.registerSchema(configuration, 
cacheContainer);
-                return true;
-            } catch (HotRodClientException e) {
-                if (!isIllegalLifecycleStateException(e)) {
-                    throw e;
-                }
-                if (firstAttempt[0]) {
-                    firstAttempt[0] = false;
-                    LOG.info("Infinispan server not ready for schema 
registration, will retry for up to {}: {}",
-                            timeout, e.getMessage());
-                } else {
-                    LOG.debug("Schema registration failed (server not ready), 
retrying: {}", e.getMessage());
+        ScheduledExecutorService ses = camelContext.getExecutorServiceManager()
+                .newSingleThreadScheduledExecutor(this, 
"infinispan-schema-registration-task");
+        try {
+            BackgroundTask task = Tasks.backgroundTask()
+                    .withBudget(Budgets.iterationTimeBudget()
+                            .withInterval(Duration.ofSeconds(1))
+                            .withMaxDuration(timeout)
+                            .build())
+                    .withScheduledExecutor(ses)
+                    .withName("infinispan-schema-registration")
+                    .build();
+
+            final boolean[] firstAttempt = { true };
+            boolean registered = task.run(camelContext, () -> {
+                try {
+                    EmbeddingStoreUtil.registerSchema(configuration, 
cacheContainer);
+                    return true;
+                } catch (HotRodClientException e) {
+                    if (!isIllegalLifecycleStateException(e)) {
+                        throw e;
+                    }
+                    if (firstAttempt[0]) {
+                        firstAttempt[0] = false;
+                        LOG.info("Infinispan server not ready for schema 
registration, will retry for up to {}: {}",
+                                timeout, e.getMessage());
+                    } else {
+                        LOG.debug("Schema registration failed (server not 
ready), retrying: {}", e.getMessage());
+                    }
+                    return false;
                 }
-                return false;
-            }
-        });
+            });
 
-        if (!registered) {
-            throw new IllegalStateException(
-                    "Failed to register Infinispan schema after " + timeout
-                                            + " of retries. The server may not 
be fully started.");
+            if (!registered) {
+                throw new IllegalStateException(
+                        "Failed to register Infinispan schema after " + timeout
+                                                + " of retries. The server may 
not be fully started.");
+            }
+        } finally {
+            camelContext.getExecutorServiceManager().shutdown(ses);
         }
     }
 
diff --git 
a/components/camel-mllp/src/main/java/org/apache/camel/component/mllp/internal/TcpServerBindThread.java
 
b/components/camel-mllp/src/main/java/org/apache/camel/component/mllp/internal/TcpServerBindThread.java
index 0dc572f9f081..70a758a0e79a 100644
--- 
a/components/camel-mllp/src/main/java/org/apache/camel/component/mllp/internal/TcpServerBindThread.java
+++ 
b/components/camel-mllp/src/main/java/org/apache/camel/component/mllp/internal/TcpServerBindThread.java
@@ -22,6 +22,7 @@ import java.net.ServerSocket;
 import java.net.SocketException;
 import java.security.GeneralSecurityException;
 import java.time.Duration;
+import java.util.concurrent.ScheduledExecutorService;
 
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLServerSocketFactory;
@@ -100,19 +101,27 @@ public class TcpServerBindThread extends Thread {
     }
 
     private void doAccept(ServerSocket serverSocket, InetSocketAddress 
socketAddress) {
-        BlockingTask task = Tasks.foregroundTask()
-                .withBudget(Budgets.iterationTimeBudget()
-                        
.withMaxDuration(Duration.ofMillis(consumer.getConfiguration().getBindTimeout()))
-                        
.withInterval(Duration.ofMillis(consumer.getConfiguration().getBindRetryInterval()))
-                        .build())
-                .withName("mllp-tcp-server-accept")
-                .build();
-
-        if (task.run(consumer.getEndpoint().getCamelContext(), () -> 
doBind(serverSocket, socketAddress))) {
-            consumer.startAcceptThread(serverSocket);
-        } else {
-            log.error("Failed to bind to address {} within timeout {}", 
socketAddress,
-                    consumer.getConfiguration().getBindTimeout());
+        ScheduledExecutorService ses = consumer.getEndpoint().getCamelContext()
+                .getExecutorServiceManager()
+                .newSingleThreadScheduledExecutor(consumer, 
"mllp-tcp-server-bind-task");
+        try {
+            BlockingTask task = Tasks.backgroundTask()
+                    .withBudget(Budgets.iterationTimeBudget()
+                            
.withMaxDuration(Duration.ofMillis(consumer.getConfiguration().getBindTimeout()))
+                            
.withInterval(Duration.ofMillis(consumer.getConfiguration().getBindRetryInterval()))
+                            .build())
+                    .withScheduledExecutor(ses)
+                    .withName("mllp-tcp-server-accept")
+                    .build();
+
+            if (task.run(consumer.getEndpoint().getCamelContext(), () -> 
doBind(serverSocket, socketAddress))) {
+                consumer.startAcceptThread(serverSocket);
+            } else {
+                log.error("Failed to bind to address {} within timeout {}", 
socketAddress,
+                        consumer.getConfiguration().getBindTimeout());
+            }
+        } finally {
+            
consumer.getEndpoint().getCamelContext().getExecutorServiceManager().shutdown(ses);
         }
     }
 
diff --git 
a/components/camel-mongodb-gridfs/src/main/java/org/apache/camel/component/mongodb/gridfs/GridFsConsumer.java
 
b/components/camel-mongodb-gridfs/src/main/java/org/apache/camel/component/mongodb/gridfs/GridFsConsumer.java
index 3ca6c9fe2b52..06223f1c63ff 100644
--- 
a/components/camel-mongodb-gridfs/src/main/java/org/apache/camel/component/mongodb/gridfs/GridFsConsumer.java
+++ 
b/components/camel-mongodb-gridfs/src/main/java/org/apache/camel/component/mongodb/gridfs/GridFsConsumer.java
@@ -20,6 +20,7 @@ import java.io.InputStream;
 import java.time.Duration;
 import java.util.Date;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
 
 import com.mongodb.BasicDBObject;
 import com.mongodb.client.MongoCollection;
@@ -36,7 +37,6 @@ import org.apache.camel.support.DefaultConsumer;
 import org.apache.camel.support.task.BlockingTask;
 import org.apache.camel.support.task.Tasks;
 import org.apache.camel.support.task.budget.Budgets;
-import org.apache.camel.support.task.budget.IterationBoundedBudget;
 import org.bson.Document;
 import org.bson.conversions.Bson;
 
@@ -49,6 +49,7 @@ import static 
org.apache.camel.component.mongodb.gridfs.GridFsConstants.PERSISTE
 
 public class GridFsConsumer extends DefaultConsumer implements Runnable {
     private final GridFsEndpoint endpoint;
+    private volatile ScheduledExecutorService taskExecutor;
     private volatile ExecutorService executor;
 
     public GridFsConsumer(GridFsEndpoint endpoint, Processor processor) {
@@ -59,6 +60,10 @@ public class GridFsConsumer extends DefaultConsumer 
implements Runnable {
     @Override
     protected void doStop() throws Exception {
         super.doStop();
+        if (taskExecutor != null) {
+            
endpoint.getCamelContext().getExecutorServiceManager().shutdown(taskExecutor);
+            taskExecutor = null;
+        }
         if (executor != null) {
             
endpoint.getCamelContext().getExecutorServiceManager().shutdown(executor);
             executor = null;
@@ -68,6 +73,8 @@ public class GridFsConsumer extends DefaultConsumer 
implements Runnable {
     @Override
     protected void doStart() throws Exception {
         super.doStart();
+        taskExecutor = endpoint.getCamelContext().getExecutorServiceManager()
+                .newSingleThreadScheduledExecutor(this, "GridFsPollingTask");
         executor = 
endpoint.getCamelContext().getExecutorServiceManager().newFixedThreadPool(this, 
endpoint.getEndpointUri(),
                 1);
         executor.execute(this);
@@ -107,12 +114,15 @@ public class GridFsConsumer extends DefaultConsumer 
implements Runnable {
             fromDate = new Date();
         }
 
-        BlockingTask task = Tasks.foregroundTask()
-                .withBudget(Budgets.iterationBudget()
-                        
.withMaxIterations(IterationBoundedBudget.UNLIMITED_ITERATIONS)
+        BlockingTask task = Tasks.backgroundTask()
+                .withBudget(Budgets.iterationTimeBudget()
+                        // maxIterations defaults to Integer.MAX_VALUE 
(effectively unlimited)
                         .withInterval(Duration.ofMillis(endpoint.getDelay()))
                         
.withInitialDelay(Duration.ofMillis(endpoint.getInitialDelay()))
+                        .withUnlimitedDuration()
                         .build())
+                .withScheduledExecutor(taskExecutor)
+                .withName("GridFsPolling")
                 .build();
 
         MongoCollection<Document> finalPtsCollection = ptsCollection;
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/SubscriptionHelper.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/SubscriptionHelper.java
index 9a6d4b13e209..0960afa4b8d1 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/SubscriptionHelper.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/SubscriptionHelper.java
@@ -30,6 +30,7 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.Lock;
@@ -93,6 +94,7 @@ public class SubscriptionHelper extends ServiceSupport {
 
     BayeuxClient client;
 
+    private ScheduledExecutorService taskExecutor;
     private final SalesforceComponent component;
     private SalesforceSession session;
 
@@ -151,12 +153,14 @@ public class SubscriptionHelper extends ServiceSupport {
                 } else {
                     LOG.debug("Pausing for {} msecs before handshake retry", 
backoff);
                     if (backoff > 0) {
-                        Tasks.foregroundTask()
-                                .withBudget(Budgets.iterationBudget()
+                        Tasks.backgroundTask()
+                                .withBudget(Budgets.iterationTimeBudget()
                                         .withMaxIterations(1)
                                         
.withInitialDelay(Duration.ofMillis(backoff))
-                                        .withInterval(Duration.ZERO)
+                                        .withInterval(Duration.ofMillis(1))
+                                        .withUnlimitedDuration()
                                         .build())
+                                .withScheduledExecutor(taskExecutor)
                                 .withName("SalesforceHandshakeRetryDelay")
                                 .build()
                                 .run(component.getCamelContext(), () -> true);
@@ -286,12 +290,14 @@ public class SubscriptionHelper extends ServiceSupport {
                 LOG.debug("Pausing for {} msecs before subscribe attempt", 
backoff);
                 // Use Camel's task API for backoff delay instead of 
Thread.sleep()
                 // We use initialDelay for the actual delay, and 
maxIterations(1) to run once
-                Tasks.foregroundTask()
-                        .withBudget(Budgets.iterationBudget()
+                Tasks.backgroundTask()
+                        .withBudget(Budgets.iterationTimeBudget()
                                 .withMaxIterations(1)
                                 .withInitialDelay(Duration.ofMillis(backoff))
-                                .withInterval(Duration.ZERO)
+                                .withInterval(Duration.ofMillis(1))
+                                .withUnlimitedDuration()
                                 .build())
+                        .withScheduledExecutor(taskExecutor)
                         .withName("SalesforceSubscribeRetryDelay")
                         .build()
                         .run(component.getCamelContext(), () -> true);
@@ -330,6 +336,9 @@ public class SubscriptionHelper extends ServiceSupport {
             throw new CamelException("Lazy login is not supported by 
salesforce consumers.");
         }
 
+        taskExecutor = component.getCamelContext().getExecutorServiceManager()
+                .newSingleThreadScheduledExecutor(this, 
"SalesforceReconnectTask");
+
         // create CometD client
         client = createClient(component, session);
 
@@ -397,6 +406,11 @@ public class SubscriptionHelper extends ServiceSupport {
 
     @Override
     protected void doStop() throws Exception {
+        if (taskExecutor != null) {
+            
component.getCamelContext().getExecutorServiceManager().shutdown(taskExecutor);
+            taskExecutor = null;
+        }
+
         closeChannel(META_CONNECT);
         closeChannel(META_SUBSCRIBE);
         closeChannel(META_HANDSHAKE);
diff --git 
a/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/ZooKeeperConsumer.java
 
b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/ZooKeeperConsumer.java
index 16f81de19fff..9e73fc6cffe5 100644
--- 
a/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/ZooKeeperConsumer.java
+++ 
b/components/camel-zookeeper/src/main/java/org/apache/camel/component/zookeeper/ZooKeeperConsumer.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.zookeeper;
 import java.time.Duration;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.Processor;
@@ -53,6 +54,7 @@ public class ZooKeeperConsumer extends DefaultConsumer {
     private ZooKeeperConfiguration configuration;
     private LinkedBlockingQueue<ZooKeeperOperation> operations = new 
LinkedBlockingQueue<>();
     private ExecutorService executor;
+    private ScheduledExecutorService taskExecutor;
     private volatile boolean shuttingDown;
 
     public ZooKeeperConsumer(ZooKeeperEndpoint endpoint, Processor processor) {
@@ -72,6 +74,8 @@ public class ZooKeeperConsumer extends DefaultConsumer {
         initializeConsumer();
         executor = 
getEndpoint().getCamelContext().getExecutorServiceManager().newFixedThreadPool(this,
                 "Camel-Zookeeper OperationsExecutor", 1);
+        taskExecutor = 
getEndpoint().getCamelContext().getExecutorServiceManager()
+                .newSingleThreadScheduledExecutor(this, 
"ZooKeeperReconnectTask");
 
         OperationsExecutor opsService = new OperationsExecutor();
         executor.submit(opsService);
@@ -84,6 +88,10 @@ public class ZooKeeperConsumer extends DefaultConsumer {
         if (LOG.isTraceEnabled()) {
             LOG.trace(String.format("Shutting down zookeeper consumer of 
'%s'", configuration.getPath()));
         }
+        if (taskExecutor != null) {
+            
getEndpoint().getCamelContext().getExecutorServiceManager().shutdown(taskExecutor);
+            taskExecutor = null;
+        }
         
getEndpoint().getCamelContext().getExecutorServiceManager().shutdown(executor);
         zkm.shutdown();
     }
@@ -179,12 +187,14 @@ public class ZooKeeperConsumer extends DefaultConsumer {
                 if (isRunAllowed()) {
                     // Use Camel's task API for reconnection backoff delay 
instead of Thread.sleep()
                     // We use initialDelay for the actual delay, and 
maxIterations(1) to run once
-                    Tasks.foregroundTask()
-                            .withBudget(Budgets.iterationBudget()
+                    Tasks.backgroundTask()
+                            .withBudget(Budgets.iterationTimeBudget()
                                     .withMaxIterations(1)
                                     
.withInitialDelay(Duration.ofMillis(configuration.getBackoff()))
-                                    .withInterval(Duration.ZERO)
+                                    .withInterval(Duration.ofMillis(1))
+                                    .withUnlimitedDuration()
                                     .build())
+                            .withScheduledExecutor(taskExecutor)
                             .withName("ZooKeeperReconnectBackoff")
                             .build()
                             .run(getEndpoint().getCamelContext(), () -> true);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistryTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistryTest.java
index 91ce742833c6..ff21cdec6050 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistryTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistryTest.java
@@ -82,7 +82,7 @@ class TabRegistryTest {
 
     @Test
     void moreTabsHasTwentySevenEntries() {
-        assertEquals(29, registry.moreTabs().size());
+        assertEquals(31, registry.moreTabs().size());
     }
 
     @Test
@@ -109,7 +109,7 @@ class TabRegistryTest {
         List<Character> shortcuts = 
registry.moreTabs().stream().map(TabRegistry.MoreTab::shortcut).toList();
         assertEquals(
                 List.of('B', 'C', 'H', 'I', 'P', 'R', 'C', 'H', 'M', 'N', 'E', 
'T', 'O', 'J', 'K', 'Q', 'Q', 'C', 'M', 'M',
-                        'M', 'P', 'S', 'T', 'B', 'C', 'C', 'V', 'M'),
+                        'M', 'P', 'S', 'T', 'B', 'C', 'C', 'V', 'M', 'Y', 'F'),
                 shortcuts, "More tab shortcut letters must match the 
historical sequence");
     }
 

Reply via email to