shashank created CAMEL-26334:
--------------------------------

             Summary: camel-once - the consumer keeps a non-daemon timer thread 
after its route or the CamelContext stopped, and a restarted route fails to 
start (Task already scheduled or cancelled)
                 Key: CAMEL-26334
                 URL: https://issues.apache.org/jira/browse/CAMEL-26334
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank
            Assignee: shashank


{{OnceConsumer}} creates a {{java.util.Timer}} in its constructor ({{new 
Timer(endpoint.getName())}}, a non-daemon thread named after the endpoint) and 
never cancels it: {{doStop}} only cancels the {{TimerTask}} (main 
8e0ce2d21371). So:

* the thread stays alive after the route is stopped and after the CamelContext 
is stopped; after the route is removed it ends only when the consumer is 
garbage collected (the cleaner of {{java.util.Timer}}). Every once route that 
was created, or reloaded in dev mode, keeps a thread until then, and a 
non-daemon one;
* a once route that is stopped and started again does not start: {{doStart}} 
schedules the cancelled {{TimerTask}} again and fails with 
{{IllegalStateException: Task already scheduled or cancelled}}. A CamelContext 
with a once route that is stopped and started again fails to start the same 
way, and so does the resume of a suspended once route.

{code:java}
from("once:tick?delay=0").routeId("r").process(e -> fired.release());

context.start();                          // fires
context.stop();                           // thread "tick" (non-daemon) still 
alive 10 s later
// 20 routes from("once:xN?delay=0") added, fired, stopped and removed: 20 
threads alive 10 s later
context.getRouteController().stopRoute("r");
context.getRouteController().startRoute("r");   // IllegalStateException: Task 
already scheduled or cancelled
{code}

*Fix* (proposed): the consumer fires on a single thread scheduled executor that 
{{doStart}} creates through the {{ExecutorServiceManager}} (thread {{Camel 
(<context>) thread #N - <name>}}). {{doStop}} cancels the pending fire with 
{{cancel(false)}} (a fire that is routing is not interrupted, as with 
{{TimerTask.cancel()}}) and shuts the executor down. Each start schedules a new 
fire, so a restarted route fires again, as {{doStop}} resetting {{scheduled}} 
intended. {{scheduleTask(TimerTask, Timer)}} becomes {{scheduleTask()}}, which 
does nothing before the consumer started. This also takes the {{TimerTask}} out 
of camel-once, which CAMEL-22957 listed for virtual thread support. Upgrade 
guide entry (thread name, restart, protected method).

*Tests*: {{OnceConsumerLifecycleTest}} (5 tests, latches and {{Thread.join}}, 
no sleeps): the thread ends when the CamelContext stops; when the route is 
removed before it fired ({{delay=3600000}}); no thread left after 20 routes 
were added, fired and removed; a route started again fires again; a route fires 
again after a CamelContext restart. All 5 fail on main (3 with the leaked 
non-daemon threads listed, 2 with the IllegalStateException) and pass with the 
fix; camel-once 16/0.

*How it was found*: a scan of the consumers for {{java.util.Timer}} and own 
executors that outlive a route stop, while adding leak invariants (no task of a 
pool after the route stop, pools released) to the deterministic simulation 
worlds; a {{java.util.Timer}} is outside the harness, so the scan and a plain 
test found it.

*Affected*: 4.17.0 and later (4.17.x, 4.18.x, 4.22.x, 4.23.0, main; same code 
since the component was added in CAMEL-22431).

_Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to