shashank created CAMEL-25701:
--------------------------------

             Summary: camel-azure-servicebus - the Camel lock renewer waits for 
its next run when a message lock expires within 10 seconds, but runs only every 
10 to 11 seconds, so locks of long-running exchanges are lost and the messages 
are delivered again
                 Key: CAMEL-25701
                 URL: https://issues.apache.org/jira/browse/CAMEL-25701
             Project: Camel
          Issue Type: Bug
          Components: camel-azure-servicebus
            Reporter: shashank
            Assignee: shashank


Since CAMEL-23937 the Service Bus consumer renews the message locks itself in 
{{PEEK_LOCK}} mode (the default, with the default {{maxAutoLockRenewDuration}} 
of 5 minutes, sessions disabled): the SDK's own renewal stops when Camel's 
callback returns, which is immediately for an asynchronous route. 
{{ServiceBusConsumer}} schedules a {{LockRenewer}} with 
{{PeriodTaskScheduler.schedulePeriodTask(lockRenewer, 10000)}}, and the renewer 
renews a lock when {{now + 10 seconds}} is after the lock's expiry 
({{LOCK_RENEW_INTERVAL_SECONDS}}, line 347 on main d46d51c24550).

The period task scheduler is driven by a 1-second timer 
({{TimerListenerManager}}), and {{PeriodicTaskWrapper}} runs the task when more 
than 10000 ms have passed since the last run, so two runs are 10 or 11 seconds 
apart. The window in which the renewer acts is 10 seconds long. When the lock 
expires between 10 and 11 seconds after a run, that run leaves it alone ("not 
yet within 10 seconds") and the next run comes after the lock has expired, or 
so close to the expiry that the renewal request arrives too late. The renewal 
then fails with a lost lock, the entry is dropped (WARN "Failed to renew 
lock"), Service Bus delivers the message again to this or another consumer 
while the first exchange is still being processed, and the final {{complete()}} 
of the first exchange fails.

A small exhaustive model of the timing (every sequence of 6 run gaps of 10 or 
11 seconds, lock expiry at every 100 ms phase) loses the lock in 6.0% of the 
cases with the current 10-second margin (11% when the runs come every 11 
seconds, 1% when they come exactly every 10 seconds: an expiry that falls on a 
run), and in none with a margin of two intervals. Which gap the scheduler 
produces depends on sub-millisecond timer jitter. An exchange that is processed 
for 5 minutes with the default 60-second lock duration crosses about 5 lock 
periods, so with 6% per period roughly one such exchange in four is delivered 
twice.

Trigger: default options ({{PEEK_LOCK}}, {{maxAutoLockRenewDuration}} 5 
minutes), no sessions, an asynchronous route (the case CAMEL-23937 is about), 
and an exchange that takes longer than the lock duration of the queue or 
subscription minus 10 seconds.

h3. Reproduction

New test in {{ServiceBusConsumerTest}} (Mockito, no Service Bus): the renewer 
is captured from the scheduler together with its period; a message whose lock 
expires 2 seconds after the earliest possible next run ({{period + 2 s}}) is 
tracked, and the renewer runs once. Waiting for the next run would leave at 
most 2 seconds, or none. On main the renewer does not renew:
{noformat}
Wanted but not invoked:
renewalClient.renewMessageLock(message);
{noformat}
A second new test (a lock that expires in 60 seconds is not renewed) passes on 
main and with the fix; it guards against renewing too early.

h3. Proposed fix

Renew when the lock expires within two intervals (20 seconds) instead of one: 
every lock is then renewed by a run that comes at least about 9 seconds before 
it expires. A lock is still renewed about once per lock duration (somewhat 
earlier than before); with a lock duration of 20 seconds or less it is renewed 
at every run. No option changes.

camel-azure-servicebus unit tests with the fix: 79 run, 0 failures, 8 skipped 
(integration tests). Not run: a real namespace.

How it was found: comparing the lock renewal timing with the visibility 
extender of camel-aws2-sqs (which renews when the deadline falls before the 
next expected run) during a sweep of acknowledge and lock timing; the test and 
the timing model confirmed it.

Affected: main (tested). camel-4.23.x and camel-4.22.x (line 336) and 
camel-4.18.x (line 310) have the same renewer (read, not run).

Duplicate check (2026-10-16): JIRA text "renewMessageLock" / "lock renewal" 
with component camel-azure-servicebus (CAMEL-23937, CAMEL-23938 session lock 
renewal, resolved Not A Problem, its PR #25001 closed unmerged; none about the 
margin); GitHub pull requests "servicebus lock renew" (#24511 merged, #25001 
closed unmerged, sessions).

_Filed with Claude Code on behalf of allthingssecurity._




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

Reply via email to