shashank created CAMEL-26736:
--------------------------------

             Summary: camel-jpa - JpaKeyValueRepository putIfAbsent: the loser 
of an insert race fails, two callers can win an expired entry, and reading an 
expired entry can delete a live one
                 Key: CAMEL-26736
                 URL: https://issues.apache.org/jira/browse/CAMEL-26736
             Project: Camel
          Issue Type: Bug
          Components: camel-jpa
            Reporter: shashank
            Assignee: shashank


CAMEL-26708 fixes two patterns in {{JdbcKeyValueRepository}} (camel-sql). 
{{JpaKeyValueRepository}} has the same two patterns; it is a different module, 
so it was left out of that PR (apache/camel#29405).

*putIfAbsent*: when {{persist}} + {{flush}} fails with a constraint violation 
(another node inserted the key), the key is read again with {{findByKey}} in 
the same transaction. The failed flush has marked that transaction for 
rollback, so the commit fails and the losing caller gets an exception 
({{DataIntegrityViolationException}} with the Spring based 
{{DefaultTransactionStrategy}}) instead of the value of the winner. When the 
entry is expired, {{putIfAbsent}} updates it in place with {{merge}}; 
{{KeyValueEntry}} has no {{@Version}}, so two nodes that both read the expired 
entry both update it and both return {{null}}: both win, and 
{{KeyValueIdempotentRepository.add}} lets the same message through on both.

*Lazy expiry*: {{get}} (and {{contains}}, {{delete}}) finds an expired entry 
and deletes it with {{entityManager.remove}}, which deletes by primary key. If 
another node stored the key between the read and the delete, its live entry is 
deleted.

*Expected*: one winner per key across nodes, and an expired entry is deleted 
only while it is still expired.

*Suggested fix*, as in CAMEL-26708: let the constraint violation roll back the 
transaction and read the key again in a new transaction (bounded attempts), let 
the primary key decide for an expired entry as well (delete it while it is 
still expired, then insert), and delete an expired entry with a JPQL {{DELETE 
... WHERE itemKey = ?1 AND expiresAt > 0 AND expiresAt <= ?2}} instead of 
{{remove}}. Tests can use two repositories on one embedded database, as 
{{JdbcKeyValueRepositoryConcurrencyTest}} in apache/camel#29405 does.

*Affected*: main (reproduced with H2 and OpenJPA, two repositories on one 
database) and 4.23.0 (read; the class is new in 4.23).

_Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to