oscerd opened a new pull request, #25217:
URL: https://github.com/apache/camel/pull/25217

   ## The bug
   
   `BackgroundTask.runTaskWrapper` rethrows a supplier exception on the 
scheduler thread without counting down the completion latch or unregistering 
the task:
   
   ```java
   try {
       if (doRun(supplier)) {
           ...
           latch.countDown();
       }
   } catch (Exception e) {          // no countDown, no removeTask
       status = Status.Failed;
       cause = e;
       throw e;
   }
   ```
   
   `doRun` only catches `TaskRunFailureException`, so any *other* exception 
reaches this path. `scheduleWithFixedDelay` then suppresses all further 
executions, and for a task built with `withUnlimitedDuration()` 
`waitForTaskCompletion` calls `latch.await()` with **no timeout** — so the 
calling thread **blocks forever** and the task **leaks in the 
`TaskManagerRegistry`**.
   
   The interrupt path has a smaller sibling of the same gap: in 
`waitForTaskCompletion`, `removeTask` sits inside the `try`, so an 
`InterruptedException` from `latch.await()` also leaves the task registered.
   
   ## Reachability on `main`
   
   CAMEL-24272 now routes two *unlimited-duration* reconnection loops through 
`BackgroundTask.run()`:
   
   - **camel-ftp SFTP** — `SftpOperations.java` uses `withUnlimitedDuration()`; 
`tryConnect` catches only `JSchException`. A non-JSch `RuntimeException` on the 
connect path escapes into the throw path.
   - **camel-mongodb-gridfs** — `GridFsConsumer` uses `withUnlimitedDuration()` 
+ `run()`. In `processCollection`, the inner `catch (Exception) { // ignore }` 
wraps only `getProcessor().process(...)`; the cursor acquisition and the 
pre-process `findOneAndUpdate` are **outside** any catch, so a transient 
`MongoException` escapes.
   
   Bounded tasks (`withMaxDuration`, e.g. Infinispan) don't hang — 
`latch.await(maxDuration)` times out — but they still fail slow and lose the 
cause.
   
   ## The fix
   
   In the `catch`, mark the task not-completed, unregister it, and count down 
the latch **before** rethrowing, so the blocking `run()` caller unblocks and 
observes the failure. The rethrow is kept so the `schedule()` path (no latch 
waiter) still surfaces the error to the executor.
   
   Also move `removeTask` + `task.cancel` into the `finally` of 
`waitForTaskCompletion`, so an interrupted `await()` no longer leaves the task 
registered.
   
   ## Test
   
   `BackgroundTaskRegistryTest.testRunReturnsAndUnregistersWhenSupplierThrows` 
builds an unlimited-duration task whose supplier throws, runs it on a 
**daemon** thread (so a regression can't wedge the JVM), and asserts `run()` 
returns, reports not-completed, and the registry ends empty.
   
   Verified both directions locally:
   - **without the fix** — the new test fails (`run()` hangs on the 10s join); 
the three existing tests pass.
   - **with the fix** — all four pass.
   
   ## Notes
   
   Found while reviewing the task-manager PR cluster (CAMEL-24271/24272/24278). 
It is the residual foreground→background porting gap, not attributable to a 
single PR. The rethrow is deliberately preserved in case surfacing the error to 
the executor on the `schedule()` path is intended — only the 
latch-release/unregister omission is fixed.
   
   ---
   
   _Claude Code on behalf of @oscerd_
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to