[ 
https://issues.apache.org/jira/browse/CAMEL-26816?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Work on CAMEL-26816 started by shashank.
----------------------------------------
> 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
>            Priority: Major
>
> {{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