shashank created CAMEL-25299:
--------------------------------

             Summary: Error handler - with asyncDelayedRedelivery, 
allowRedeliveryWhileStopping=false is ignored: a route stop waits for the 
redelivery delay and then redelivers
                 Key: CAMEL-25299
                 URL: https://issues.apache.org/jira/browse/CAMEL-25299
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank


The documentation of the dead letter channel says that with 
{{allowRedeliveryWhileStopping=false}} any new redelivery attempt is not 
allowed once the stop of the route (or Camel) has been triggered: a 
{{RejectedExecutionException}} is set and the exchange stops being routed (the 
dead letter channel still receives it).

That holds for the synchronous redelivery: {{RedeliveryTask.sleep()}} waits in 
chunks of 1 second and checks {{preparingShutdown}} after each chunk, then 
marks the exchange rejected and exhausted, so it goes to the dead letter 
channel within a second 
({{NotAllowRedeliveryWhileStoppingDeadLetterChannelTest}}).

With {{asyncDelayedRedelivery}} the delay is a task on the error handler's 
scheduled thread pool:

{code:java}
executorService.schedule(() -> reactiveExecutor.schedule(this::redeliver), 
redeliveryDelay, TimeUnit.MILLISECONDS);
{code}

and {{redeliver()}} calls the output again without any check of the shutdown 
state (only {{run()}} checks {{isRunAllowed()}}, and the redelivery decision in 
{{doRun()}} was taken before the stop). So when the route is stopped while a 
redelivery waits for its delay:

* the graceful stop waits for the inflight exchange for the whole remaining 
delay, or until the shutdown timeout (45 s by default) if the delay is longer, 
then stops the consumer forcibly;
* when the delay ends, the message is redelivered anyway (also after the forced 
stop, into the stopped route), which is what 
{{allowRedeliveryWhileStopping=false}} is meant to prevent.

CAMEL-3364 (2.11.0), which added {{allowRedeliveryWhileStopping}}, already 
named this case in its discussion: the error handler keeps the delayed 
exchanges as tasks in its thread pool, and on shutdown those have to be dealt 
with too. The implementation only made {{sleep()}} check the shutdown state; 
nothing in the ticket or the code says the scheduled task was meant to be 
exempt.

h3. Reproduction

Test {{NotAllowRedeliveryWhileStoppingAsyncDelayedTest}} (camel-core): 
{{deadLetterChannel("mock:dead").maximumRedeliveries(5).redeliveryDelay(1 hour, 
capped to the 60 s 
maximumRedeliveryDelay).asyncDelayedRedelivery().allowRedeliveryWhileStopping(false)}},
 a route {{seda:start -> mock:foo -> throwException}}, and a scheduled thread 
pool for the error handler that counts down a latch when the redelivery is 
scheduled. After the first attempt failed and the redelivery is scheduled, the 
test calls {{stopRoute("foo")}} (shutdown timeout 5 s). Expected: the dead 
letter channel gets the exchange with {{RejectedExecutionException("Redelivery 
not allowed while stopping")}}, no redelivery, nothing inflight. A second test 
does the same with {{context.stop()}}. On main both stops run into the 5 s 
timeout and the dead letter channel has nothing:

{noformat}
NotAllowRedeliveryWhileStoppingAsyncDelayedTest.testStopRouteRejectsScheduledRedelivery
 mock://dead Received message count. Expected: <1> but was: <0>
NotAllowRedeliveryWhileStoppingAsyncDelayedTest.testStopCamelContextRejectsScheduledRedelivery
 mock://dead Received message count. Expected: <1> but was: <0>
{noformat}

The defect was found with a TLA+ model of the redelivery task (delivery, 
{{run()}}/{{doRun()}}, synchronous {{sleep()}}, the scheduled task, 
{{redeliver()}}) and the graceful shutdown ({{prepareShutdown}}, waiting for 
inflight exchanges, timeout). The invariant "no redelivery starts after its 
delay ended while the error handler prepares to shut down 
(allowRedeliveryWhileStopping=false)" is violated with 
{{asyncDelayedRedelivery}} in 6 steps (deliver, fail, schedule, 
prepareShutdown, delay ends, redeliver); it holds for the synchronous 
redelivery, with and without a dead letter channel, and for the fixed task.

h3. Proposed fix

When the redelivery is not allowed while stopping, the scheduled task wakes up 
every second, like {{sleep()}}, and when the error handler is preparing to shut 
down it rejects the redelivery the same way 
({{RejectedExecutionException("Redelivery not allowed while stopping")}}, 
redelivery exhausted, back to {{run()}}, which moves the exchange to the dead 
letter channel or fails it). When the delay is over it redelivers as before. 
With {{allowRedeliveryWhileStopping=true}} (the default) nothing changes: one 
task for the whole delay. If the thread pool rejects the next wake-up, the 
redelivery is rejected the same way instead of losing the callback.

With the fix the new tests pass (each in about 1.5 s), and {{*Redelivery*}}, 
{{NotAllowRedelivery*}} and {{DeadLetterChannel*}} tests in camel-core pass (95 
tests).

Cost: one short task per second per waiting redelivery, only with 
{{allowRedeliveryWhileStopping=false}} (the same rate as the synchronous 
{{sleep()}} loop, but without a blocked thread). One difference to {{sleep()}}: 
{{sleep()}} does not check at all for a delay below 1 second, while the 
scheduled task checks once when such a delay ends, so a short redelivery whose 
delay ends while stopping is rejected too (which is what the option describes). 
The upgrade guide gets a note, as a stop now rejects these messages within a 
second instead of redelivering them.

Not addressed (low): with {{allowRedeliveryWhileStopping=true}} (the default), 
when Camel itself is stopped and the shutdown times out, the error handler 
thread pool is shut down with the delayed task still in it, so the exchange is 
never completed; Camel is stopping and the await manager releases waiting 
callers. (With {{false}} the fix rejects the task within a second, before the 
timeout.)

Affected: 4.14.x, 4.18.x and main (same code).

Duplicate check (2026-10-03): JIRA text "allowRedeliveryWhileStopping" 
(CAMEL-6033, CAMEL-20744, CAMEL-3364, CAMEL-19896, CAMEL-12603), 
"asyncDelayedRedelivery" with stop/shutdown (none), "redelivery" with 
"stopping" since 2022 (11 issues, none about the asynchronous delay). GitHub 
pull requests "asyncDelayedRedelivery" (#1771, a doc note, 2017), 
"allowRedeliveryWhileStopping" (#23179, CAMEL-23494).

_Filed with Claude Code on behalf of allthingssecurity._




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

Reply via email to