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)