shashank created CAMEL-26816:
--------------------------------

             Summary: camel-sql - JdbcOrphanLockAwareIdempotentRepository: 
confirm() is a no-op, so a processed key is taken over and processed again 
after its node stops
                 Key: CAMEL-26816
                 URL: https://issues.apache.org/jira/browse/CAMEL-26816
             Project: Camel
          Issue Type: Bug
          Components: camel-sql
            Reporter: shashank
            Assignee: shashank


{{JdbcOrphanLockAwareIdempotentRepository}} uses the inherited {{confirm()}}, 
which only returns true ({{AbstractJdbcMessageIdRepository.java:207-209}}). Its 
row has no "processed" state (columns {{processorName, messageId, createdAt}}, 
{{JdbcMessageIdRepository.java:41-42}}), so a processed and confirmed key is 
treated like an in-flight lease:
* While the node runs, the key stays in the keep-alive set 
({{JdbcOrphanLockAwareIdempotentRepository.java:51}}, added at :123 and :135, 
removed only in {{delete}} at :108). The keep-alive task refreshes its 
{{createdAt}} on every round (:184-198). The set and each keep-alive batch grow 
with every processed message.
* When the node stops or crashes, the row is no longer refreshed. Once it is 
older than {{lockMaxAgeMillis}}, {{queryForInt}} treats it as absent (:97-98) 
and {{insert}} takes it over (:129-136). {{add()}} returns true for a message 
that was already processed.

Example, nodes A and B share {{CAMEL_MESSAGEPROCESSED}} with the same 
{{processorName}}:
# A processes id {{K}}. {{add}} returns true, {{confirm}} does nothing, and A 
keeps refreshing the row.
# While A runs, a duplicate of {{K}} sent to B is rejected, as expected.
# A stops (rolling restart, scale-in, redeploy) or crashes.
# After {{lockMaxAgeMillis}} a duplicate of {{K}} reaches B. With {{noop=true}} 
this is the same file, which is still in the directory. The takeover UPDATE 
matches, {{add}} returns true, and {{K}} is processed a second time.

Found with Specula and confirmed with a test on the real classes, on H2 with 
two repository instances.

Why it matters: callers that confirm on success hit it with their defaults: the 
Idempotent Consumer EIP (eager), the file consumer with {{noop=true}} or 
{{idempotent=true}}, and {{readLock=idempotent}} with the default 
{{readLockRemoveOnCommit=false}}. The class javadoc and the Orphan Lock section 
of {{sql-component.adoc}} describe the takeover as freeing the lock of an 
instance that crashed. They do not say that processed keys are forgotten when 
their node stops. The parent {{JdbcMessageIdRepository}} keeps confirmed keys 
for good.

Expected: a confirmed key stays a duplicate after its node stops, and it is no 
longer refreshed by the keep-alive.

Fix direction, for the maintainers to choose:
* Should {{confirm()}} remove the key from the keep-alive set and mark the row 
as processed? Without a schema change this could be a {{createdAt}} value that 
the orphan check never treats as old; with one, a state column.
* Or is the repository meant only for in-flight locks, in which case the docs 
should say so and recommend {{readLockRemoveOnCommit=true}} or the plain 
repository for keys that must stay?

_Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to