shashank created CAMEL-25037:
--------------------------------

             Summary: camel-support - an interrupted 
EventDrivenPollingConsumer.receive() never returns: it loops at 100 % CPU and 
logs a WARN on every iteration (regression of CAMEL-20297, 4.4)
                 Key: CAMEL-25037
                 URL: https://issues.apache.org/jira/browse/CAMEL-25037
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank


{{EventDrivenPollingConsumer.receive()}} 
({{core/camel-support/.../EventDrivenPollingConsumer.java:128-153}}) loops 
{{while (isRunAllowed())}} around {{queue.take()}}. On {{InterruptedException}} 
it calls {{handleInterruptedException}} ({{:236-239}}). Since CAMEL-20297 
(commit ff48bbc4d2, 4.4.0) that method restores the interrupt flag 
({{Thread.currentThread().interrupt()}}) and then logs through the 
{{LoggingExceptionHandler}} at WARN. The loop then calls {{take()}} again, 
which throws at once because the flag is set, and so on. The thread never 
leaves {{receive()}}, even when a message is queued, because {{take()}} checks 
the flag first. It uses a full core and writes one WARN line per iteration.

This is the same pattern as CAMEL-22390 (SedaConsumer infinite loop on 
interrupt, also caused by CAMEL-20297). That issue was closed as "Information 
Provided" with the advice that end users should not interrupt Camel's own 
threads. The case here is different. {{ConsumerTemplate.receive()}} and 
{{PollingConsumer.receive()}} run on the caller's own thread, which the 
application may legitimately interrupt or cancel. Camel itself also interrupts 
route threads during a forced shutdown. A blocking {{receive()}} that is 
interrupted should return, not spin.

A thread in {{receive()}} gets interrupted when, for example:
* a task doing {{consumerTemplate.receive(uri)}} is cancelled with 
{{Future.cancel(true)}}, or its executor is shut down with {{shutdownNow()}};
* the thread pool of a route that waits in {{pollEnrich}} (default timeout -1) 
is shut down with {{shutdownNow()}}, which Camel does on a forced shutdown (for 
example the Threads EIP pool in {{ThreadsProcessor.doShutdown}}, or 
{{BaseExecutorServiceManager.doShutdown}} after its await timeout).

Affected: 4.4.0 and later (4.4.x, 4.8.x, 4.14.x, 4.18.x, main).

*Reproduction* (4.23.0-SNAPSHOT):
{noformat}
thread A: consumerTemplate.receive("direct:idle")   -> waiting in 
EventDrivenPollingConsumer.receive:141
A.interrupt()
2 s later: A alive=true state=RUNNABLE, CPU time used by A in those 2 s = 1979 
ms
200 stack samples of A: EventDrivenPollingConsumer.receive:141 / 
handleInterruptedException:237 / LoggingExceptionHandler
with log4j at WARN: 603,114 lines "WARN EventDrivenPollingConsumer - Caused by: 
[java.lang.InterruptedException - null]"
                    in about 3 s (the log file reached 544 MB)
{noformat}
With the fix below {{receive()}} returns null 591 ms after the interrupt and 
the thread ends.

TLA+: the same model with interrupts violates {{ReceiveEndsAfterInterrupt}} 
({{pc_shared_interrupt_live}}). The lasso is Interrupt -> RIntr -> RLoop -> 
RAcq -> RIntr ..., and it holds even with a message in the queue. The fix 
variant ({{pc_fix_interrupt}}) satisfies it.

*Proposed fix:* on {{InterruptedException}} in {{receive()}} / 
{{receive(timeout)}}, restore the interrupt flag and return null (leave the 
loop). Do not call {{take()}} again. Optionally log once at DEBUG, like 
{{process()}} does for an interrupted put.

_Filed with Claude Code on behalf of allthingssecurity._




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

Reply via email to