shashank created CAMEL-24952:
--------------------------------

             Summary: camel-seda - virtualThreadPerTask consumer: graceful stop 
does not wait for polled exchanges whose task has not started yet
                 Key: CAMEL-24952
                 URL: https://issues.apache.org/jira/browse/CAMEL-24952
             Project: Camel
          Issue Type: Bug
          Components: camel-seda
            Reporter: shashank


{{ThreadPerTaskSedaConsumer.processPolledExchange}} hands each polled exchange 
to the task executor. {{activeTasks}} is only incremented when the task starts 
running (ThreadPerTaskSedaConsumer.java:133). An exchange that was polled but 
whose task has not started yet is:
* not in the queue;
* not inflight, because it has not entered the route;
* not counted in {{activeTasks}}.

So both the {{DefaultShutdownStrategy}} wait loop and {{prepareShutdown}} 
(which skips waiting when {{activeTasks.sum() == 0}}, :168-169) consider the 
consumer idle. The route is stopped. When the task finally runs, the stopped 
error handler rejects the exchange ({{RejectedExecutionException}} from 
{{RedeliveryErrorHandler.SimpleTask.runNotAllowed}}). The exception is only 
logged, so the message is lost. The classic {{SedaConsumer}} does not have this 
problem, because it processes the exchange on the polling thread that 
{{prepareShutdown}} waits for.

The window is the delay between {{taskExecutor.execute}} and the task starting. 
It only matters when that delay is longer than the coordinator's next empty 
poll ({{pollTimeout}}), e.g. with starved/pinned virtual-thread carriers. 
Reproduced against 4.23.0-SNAPSHOT with a {{ThreadPoolFactory}} that delays the 
start of cached-pool tasks by 2 s; Camel code unchanged:
{noformat}
t=120 ms stopRoute(v) returned, route status=Stopped
WARN Error processing exchange. Exchange[]. Caused by: 
[java.util.concurrent.RejectedExecutionException - null]   (logged twice)
{noformat}

Proposed fix: increment {{activeTasks}} before {{taskExecutor.execute(...)}}. 
Decrement it (and release the concurrency permit) in the task's {{finally}} 
block, and also when {{execute}} throws (e.g. {{RejectedExecutionException}}; 
previously the permit acquired in {{beforePoll}} leaked in that case).

A PR with the fix follows, with regression test 
{{ThreadPerTaskSedaConsumerStopTest}} (a {{ThreadPoolFactory}} whose task 
executor holds dispatched tasks until the stop reaches {{awaitTermination}}). 
The duplicate exception-handler report below is left for a separate change.

Minor, same class: a failed exchange is reported to the exception handler 
twice, once by {{onProcessingDone(exchange, prepared)}} and again in the 
callback's {{finally}} block (:143-146), because {{prepared == exchange}}.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to