shashank created CAMEL-25252:
--------------------------------
Summary: camel-timer - stopping a timer consumer with a negative
delay releases the shared java.util.Timer it never took, which cancels the
timer of the other routes with the same timer name (they stay Started but never
fire again)
Key: CAMEL-25252
URL: https://issues.apache.org/jira/browse/CAMEL-25252
Project: Camel
Issue Type: Bug
Components: camel-core
Reporter: shashank
The timer consumers of the same timer name share one {{java.util.Timer}}, which
{{TimerComponent}} reference counts: {{getTimer}} creates it or increments the
count (line numbers of main, :63-79), {{removeTimer}} decrements it and cancels
the {{Timer}} at 0 (:81-92).
{{TimerConsumer}} takes the reference only for a delay of 0 or more
({{doStart}} :181-186, or {{onCamelContextStarted}} :229-234 when the consumer
was started before the context). A consumer with a negative delay
({{delay=-1}}, fire as fast as possible, CAMEL-9249) uses its own single thread
executor (:190) and never calls {{getTimer}}. But {{doStop}} always calls
{{endpoint.removeTimer(this)}} (:217). So stopping such a consumer decrements a
count it never incremented. When it was the last reference of another consumer,
the shared {{Timer}} is cancelled while that consumer is still running: its
route stays Started but never fires again, and nothing is logged. Because the
holder is removed from the map, a route started later on the same name gets a
new {{Timer}}, but the running one is not rescheduled.
Example: {{from("timer:tick?period=1000")}} in one route and
{{from("timer:tick?delay=-1&repeatCount=1")}} in another (a one-shot start-up
route). Stopping the second route (JMX, the route controller, a route policy,
or a restart of it) silently stops the first.
h3. Reproduction
Route A {{timer:shared?period=20}}, route B
{{timer:shared?delay=-1&repeatCount=1}}; wait until both fired, stop route B,
then wait up to 3 s for 5 more exchanges from A: none arrive, A reports
Started. 3 of 3 runs, and 10 of 10 in a loop; the same after B was started and
stopped twice. Control: B {{timer:shared?delay=0&repeatCount=1}} (takes the
timer): A keeps firing (3 of 3).
A TLA+ model of the reference count with consumers that do and do not take the
timer, started and stopped in any order, violates "every started consumer with
a delay of 0 or more is scheduled on a Timer that is not cancelled" and "the
count equals the consumers scheduled on the Timer" as soon as a negative-delay
consumer is stopped; with the fix both hold (2 and 3 consumers, up to 9
start/stop operations); with only non-negative delays they hold on the current
code.
h3. Proposed fix
{{TimerConsumer}} remembers whether it took the timer (a flag set where
{{getTimer}} is called in {{doStart}} and {{onCamelContextStarted}}) and
{{doStop}} calls {{removeTimer}} only then, clearing the flag. It would also
cover a consumer with a delay of 0 or more that is stopped before the
CamelContext completed its start, which has not taken the timer yet either (not
tested). With the fix route A keeps firing after B is stopped once or twice (3
of 3), the control is unchanged, and the timer tests of camel-core pass (19
classes, 34 tests, including {{TimerMultipleConsumerStopRouteTest}},
{{TimerNegativeDelayTest}}, {{TimerRestartTest}} and a new stop/restart test of
a negative-delay route next to a period route on the same timer name, which
fails without the fix).
Affected: all versions with delay=-1 (the same code at camel-3.20.0, 4.0.0,
4.10.0, 4.14.0, 4.18.0, 4.22.0 and main).
Duplicate check (2026-09-30): JIRA text "TimerComponent" (12, the newest
CAMEL-18232 thread name pattern), "timer" with "delay=-1" (CAMEL-9249),
component camel-timer (none recent); GitHub pull requests "camel-timer", "timer
removeTimer", "timer delay=-1": none about this.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)