[
https://issues.apache.org/jira/browse/CAMEL-26746?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen updated CAMEL-26746:
--------------------------------
Fix Version/s: 4.24.0
> Timer consumer drops its first fire (delay=0) when its route is started at
> runtime via RouteController.startRoute
> -----------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-26746
> URL: https://issues.apache.org/jira/browse/CAMEL-26746
> Project: Camel
> Issue Type: Bug
> Components: camel-core
> Affects Versions: 4.23.0
> Reporter: David J. M. Karlsen
> Priority: Minor
> Fix For: 4.24.0
>
>
> Start a timer route on a CamelContext that is already started, either with
> `stopRoute`/`startRoute` or by adding the route later.
> `TimerConsumer.doStart` then calls `configureTask` immediately, while
> `BaseService.start()` still has status STARTING.
> With `delay=0`, `java.util.Timer` can run the TimerTask before `start()` sets
> STARTED. The task then calls `isTaskRunAllowed()`, which is `isStarted()` and
> returns false. The run is skipped silently, with a debug log only, and the
> next fire comes only after `period`.
> This cannot happen at context start: `InternalRouteStartupManager` starts the
> consumers first and then calls `onCamelContextStarted`, which configures the
> task.
> *Expected:* after `startRoute` returns, a `delay=0` timer fires once
> immediately, as it does at context start.
> *Actual:* in about 2.6% of restarts under CPU load (77 of 3,000 in the
> reproducer), the immediate fire is lost, and the next fire comes one period
> later.
> h3. Reproducer (JUnit 5, camel-core 4.23.0)
> {code:java}
> try (var camel = new DefaultCamelContext()) {
> var fires = new AtomicInteger();
> camel.addRoutes(new RouteBuilder() {
> public void configure() {
> from("timer:t?delay=0&period=3600000").routeId("t").process(e ->
> fires.incrementAndGet());
> }
> });
> camel.start();
> await().until(() -> fires.get() == 1);
> int dropped = 0;
> for (int i = 0; i < 3000; i++) {
> int before = fires.get();
> camel.getRouteController().stopRoute("t");
> camel.getRouteController().startRoute("t");
> try {
> await().atMost(Duration.ofMillis(500)).until(() -> fires.get() >
> before);
> } catch (ConditionTimeoutException e) {
> dropped++;
> }
> }
> assertEquals(0, dropped); // fails, e.g. dropped=77 with background
> CPU-burner threads
> }
> {code}
> h3. Deterministic variant
> Bind a custom `java.util.Timer` (`timer=#eager`) whose `schedule(task, delay,
> period)` runs `task.run()` synchronously before delegating. After
> `stopRoute`/`startRoute`, the fire count does not change.
> h3. Suggested fix
> Either make `isTaskRunAllowed()` use `isRunAllowed()`, which accepts STARTING
> as `ScheduledPollConsumer.isPollAllowed` does, or configure the task only
> once the consumer has reached STARTED.
> h3. Environment
> Java 25.0.5, Linux (GitHub Actions runner) and macOS.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)