[ 
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)

Reply via email to