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)