[
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)