shashank created CAMEL-25018:
--------------------------------

             Summary: ThrottlingInflightRoutePolicy and 
ThrottlingExceptionRoutePolicy resume a consumer that the route controller 
suspended: suspendRoute is not honoured and a graceful stop keeps consuming 
until the timeout
                 Key: CAMEL-25018
                 URL: https://issues.apache.org/jira/browse/CAMEL-25018
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank


Both throttling route policies resume the route's consumer with 
{{RoutePolicySupport.resumeOrStartConsumer}} -> 
{{ServiceHelper.resumeService}}, which resumes any suspended {{Suspendable}} 
consumer, whoever suspended it:
* {{ThrottlingInflightRoutePolicy.throttle}} resumes on every 
{{onExchangeDone}} where the route's inflight count is at or below the resume 
threshold (70 % of {{maxInflightExchanges}}), i.e. on almost every completion 
when the route is not busy;
* {{ThrottlingExceptionRoutePolicy.halfOpenCircuit}} / {{closeCircuit}} resume 
from the half-open timer or from {{onExchangeDone}}.

The route controller uses the same consumer state: for 
{{stopRoute}}/{{suspendRoute}} {{DefaultShutdownStrategy}} suspends a 
{{Suspendable}} consumer first, then waits for the inflight exchanges, and only 
then stops or suspends it for good. {{RoutePolicyAdvice}} skips 
{{onExchangeDone}} only while the whole CamelContext is stopping, not while one 
route is being stopped or suspended.

Consequences:
# {{ThrottlingInflightRoutePolicy}} + {{stopRoute}}/{{suspendRoute}} of a route 
with inflight exchanges: the first exchange that completes resumes the 
consumer, which takes new messages while the shutdown strategy waits for the 
inflight count to reach 0. Under steady traffic the stop/suspend always runs 
into the timeout and is then forced, failing the exchanges it just took in 
(JMS/Kafka: redelivery or loss depending on the acknowledge mode).
# {{ThrottlingExceptionRoutePolicy}} + {{suspendRoute}} while the circuit is 
open (an operator suspends the route during an outage): the half-open timer 
resumes the consumer and the route consumes although its status is Suspended.

Batch consumers (file, FTP) are not affected by (1) because they refuse to 
route while shutting down; timer, seda, JMS, Kafka and other {{Suspendable}} 
consumers are.

*Reproduction*:
{noformat}
inflight-stop-timer: from("timer:t?period=100") with 
ThrottlingInflightRoutePolicy (max 10); each exchange takes 900 ms,
the first one is held until stopRoute("r", 4 s) has suspended the consumer:
  stopRoute(r, 4 s) in progress: consumer=Suspended, releasing the inflight 
exchange
  300 ms after the inflight exchange completed: consumer=Started
  stopRoute returned after 4007 ms; exchanges started by the consumer after it 
was suspended for the stop=5 (expected 0)
  log: "Timeout occurred during graceful shutdown. Forcing the routes to be 
shutdown now", then an exchange taken
       during the stop fails with RejectedExecutionException
control (same route, a RoutePolicySupport that does nothing): consumer stays 
Suspended, stopRoute returns after 1007 ms, 0 started

usersuspend: file consumer, ThrottlingExceptionRoutePolicy(1, 60000, 500, 
null); a failing file opens the circuit, then suspendRoute("r"):
  suspendRoute(r) returned: route=Suspended consumer=Suspended circuit=opened
  2 s later: route=Suspended consumer=Started circuit=half opened, files 
processed while the route is suspended=3/3
{noformat}

TLA+ models of both policies (the inflight policy's throttle split into its 
steps, the controller's suspend/stop, the half-open timer) violate "the policy 
never resumes a consumer the controller suspended"; with the fix below it 
holds, and the policy still never leaves a consumer it suspended itself 
suspended forever.

*Proposed fix:*
* the policies only resume a consumer they suspended themselves, and only 
record that for a consumer that was started ({{ServiceHelper.suspendService}} 
also returns true for an already stopped consumer, which would otherwise be 
claimed and later restarted);
* they do not resume or start the consumer while the route is Suspending, 
Suspended, Stopping or Stopped, or the CamelContext is stopping (a shared 
{{RoutePolicySupport}} helper);
* the exception policy only resumes from OPEN (a HALF_OPEN close leaves the 
consumer to the route controller).

Known remaining gap: the route status only changes after the graceful wait, so 
a consumer the policy suspended itself before the stop began can still be 
resumed during the wait. Closing that would need the shutdown strategy to 
notify the route policies before it waits.

_Filed with Claude Code on behalf of allthingssecurity._




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

Reply via email to