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

commit 49c7d5b7a1f6f53b61495aa5e506bdab3815e967
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jul 27 20:59:35 2026 +0200

    CAMEL-24278: BackgroundTask keeps task registered in TaskManagerRegistry 
during blocking run
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../task/task/BackgroundTaskRegistryTest.java      | 172 +++++++++++++++++++++
 .../apache/camel/support/task/BackgroundTask.java  |  13 +-
 2 files changed, 182 insertions(+), 3 deletions(-)

diff --git 
a/core/camel-core/src/test/java/org/apache/camel/support/task/task/BackgroundTaskRegistryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/support/task/task/BackgroundTaskRegistryTest.java
new file mode 100644
index 000000000000..3f0154d49802
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/support/task/task/BackgroundTaskRegistryTest.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support.task.task;
+
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.task.BackgroundTask;
+import org.apache.camel.support.task.TaskManagerRegistry;
+import org.apache.camel.support.task.Tasks;
+import org.apache.camel.support.task.budget.Budgets;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+class BackgroundTaskRegistryTest {
+
+    private CamelContext camelContext;
+    private TaskManagerRegistry registry;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        camelContext = new DefaultCamelContext();
+        camelContext.start();
+        registry = 
PluginHelper.getTaskManagerRegistry(camelContext.getCamelContextExtension());
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        if (camelContext != null) {
+            camelContext.stop();
+        }
+    }
+
+    @DisplayName("Test that run() registers task immediately before first 
scheduled tick")
+    @Test
+    @Timeout(10)
+    void testTaskRegisteredImmediatelyOnRun() throws Exception {
+        AtomicBoolean supplierCalled = new AtomicBoolean(false);
+
+        BackgroundTask task = Tasks.backgroundTask()
+                
.withScheduledExecutor(Executors.newSingleThreadScheduledExecutor())
+                .withBudget(Budgets.timeBudget()
+                        .withInterval(Duration.ofMillis(100))
+                        .withInitialDelay(Duration.ofSeconds(2))
+                        .withMaxDuration(Duration.ofSeconds(5))
+                        .build())
+                .build();
+
+        Thread runner = new Thread(() -> task.run(camelContext, () -> {
+            supplierCalled.set(true);
+            return true;
+        }));
+        runner.start();
+
+        await().atMost(1, TimeUnit.SECONDS)
+                .untilAsserted(() -> 
assertThat(registry.getTasks()).hasSize(1));
+
+        assertThat(supplierCalled.get())
+                .as("Supplier should not have been called yet (initial delay 
is 2s)")
+                .isFalse();
+
+        assertThat(registry.getTasks().iterator().next()).isNotNull();
+
+        runner.join(10000);
+        assertThat(registry.getTasks()).isEmpty();
+    }
+
+    @DisplayName("Test that task stays registered during retries and is 
removed after run() returns")
+    @Test
+    @Timeout(10)
+    void testTaskStaysRegisteredDuringRetries() throws Exception {
+        AtomicBoolean shouldSucceed = new AtomicBoolean(false);
+        CountDownLatch firstAttempt = new CountDownLatch(1);
+
+        BackgroundTask task = Tasks.backgroundTask()
+                
.withScheduledExecutor(Executors.newSingleThreadScheduledExecutor())
+                .withBudget(Budgets.timeBudget()
+                        .withInterval(Duration.ofMillis(200))
+                        .withInitialDelay(Duration.ZERO)
+                        .withMaxDuration(Duration.ofSeconds(5))
+                        .build())
+                .build();
+
+        Thread runner = new Thread(() -> task.run(camelContext, () -> {
+            firstAttempt.countDown();
+            return shouldSucceed.get();
+        }));
+        runner.start();
+
+        assertThat(firstAttempt.await(3, TimeUnit.SECONDS)).isTrue();
+
+        for (int i = 0; i < 3; i++) {
+            Thread.sleep(250);
+            assertThat(registry.getTasks())
+                    .as("Task should stay in registry during retry %d", i)
+                    .hasSize(1);
+        }
+
+        shouldSucceed.set(true);
+        runner.join(5000);
+
+        assertThat(registry.getTasks()).isEmpty();
+    }
+
+    @DisplayName("Test that task is removed from registry after supplier 
succeeds")
+    @Test
+    @Timeout(10)
+    void testTaskRemovedAfterSuccess() throws Exception {
+        CountDownLatch supplierReady = new CountDownLatch(1);
+        CountDownLatch supplierRelease = new CountDownLatch(1);
+        AtomicBoolean runCompleted = new AtomicBoolean(false);
+
+        BackgroundTask task = Tasks.backgroundTask()
+                
.withScheduledExecutor(Executors.newSingleThreadScheduledExecutor())
+                .withBudget(Budgets.timeBudget()
+                        .withInterval(Duration.ofMillis(100))
+                        .withInitialDelay(Duration.ZERO)
+                        .withMaxDuration(Duration.ofSeconds(5))
+                        .build())
+                .build();
+
+        Thread runner = new Thread(() -> {
+            task.run(camelContext, () -> {
+                supplierReady.countDown();
+                try {
+                    supplierRelease.await(5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    return false;
+                }
+                return true;
+            });
+            runCompleted.set(true);
+        });
+        runner.start();
+
+        assertThat(supplierReady.await(3, TimeUnit.SECONDS)).isTrue();
+        assertThat(registry.getTasks()).hasSize(1);
+
+        supplierRelease.countDown();
+        runner.join(5000);
+
+        assertThat(runCompleted.get()).isTrue();
+        assertThat(registry.getTasks()).isEmpty();
+    }
+}
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 c49b1584a6f3..f0f9a51bef81 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
@@ -81,6 +81,7 @@ public class BackgroundTask extends AbstractTask implements 
BlockingTask {
     private Duration elapsed = Duration.ZERO;
     private final AtomicBoolean running = new AtomicBoolean();
     private final AtomicBoolean completed = new AtomicBoolean();
+    private volatile boolean registeredByRun;
 
     BackgroundTask(TimeBudget budget, ScheduledExecutorService service, String 
name) {
         super(name);
@@ -97,13 +98,15 @@ public class BackgroundTask extends AbstractTask implements 
BlockingTask {
         TaskManagerRegistry registry = null;
         if (camelContext != null) {
             registry = 
PluginHelper.getTaskManagerRegistry(camelContext.getCamelContextExtension());
-            registry.addTask(this);
+            if (!registeredByRun) {
+                registry.addTask(this);
+            }
         }
         if (!budget.next()) {
             LOG.warn("The task {} does not have more budget to continue 
running", getName());
             status = Status.Exhausted;
             completed.set(false);
-            if (registry != null) {
+            if (!registeredByRun && registry != null) {
                 registry.removeTask(this);
             }
             latch.countDown();
@@ -118,7 +121,7 @@ public class BackgroundTask extends AbstractTask implements 
BlockingTask {
             if (doRun(supplier)) {
                 status = Status.Completed;
                 completed.set(true);
-                if (registry != null) {
+                if (!registeredByRun && registry != null) {
                     registry.removeTask(this);
                 }
                 latch.countDown();
@@ -149,6 +152,10 @@ public class BackgroundTask extends AbstractTask 
implements BlockingTask {
     @Override
     public boolean run(CamelContext camelContext, BooleanSupplier supplier) {
         running.set(true);
+        registeredByRun = true;
+        if (camelContext != null) {
+            
PluginHelper.getTaskManagerRegistry(camelContext.getCamelContextExtension()).addTask(this);
+        }
         Future<?> task = service.scheduleWithFixedDelay(() -> 
runTaskWrapper(camelContext, supplier), budget.initialDelay(),
                 budget.interval(), TimeUnit.MILLISECONDS);
         waitForTaskCompletion(camelContext, task);

Reply via email to