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

Claus Ibsen updated CAMEL-24151:
--------------------------------
    Fix Version/s: 4.22.0

> camel-saga/camel-lra: medium and low findings from Saga EIP code review (July 
> 2026)
> -----------------------------------------------------------------------------------
>
>                 Key: CAMEL-24151
>                 URL: https://issues.apache.org/jira/browse/CAMEL-24151
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-core, camel-lra
>            Reporter: Federico Mariani
>            Assignee: Claus Ibsen
>            Priority: Major
>             Fix For: 4.22.0
>
>
> Umbrella for the medium/low findings of a July 2026 code review of the Saga 
> EIP (model, reifier, processors, in-memory service, saga component, 
> camel-lra). The high-severity findings are filed as separate issues.
> h3. Medium
> * *LRAClient accepts only HTTP 200 and throws context-free errors*: 
> {{join}}/{{complete}}/{{compensate}} test {{statusCode() != HTTP_OK}} 
> (LRAClient.java:141,158,175). Other 2xx codes are legitimate per coordinator 
> implementations; 404/410 for an already-finished LRA (e.g. compensated by 
> timeout) is a common, meaningful case. Users get a bare 
> {{RuntimeCamelException("Cannot complete LRA")}} with no status code, 
> response body or LRA id. Fix: accept the 2xx range, include status/body/LRA 
> URL in messages.
> * *In-memory step timeout timers never cancelled*: every {{beginStep}} with a 
> timeout schedules a compensation task (InMemorySagaCoordinator.java:109-116) 
> that is not cancelled on completion. Functionally harmless (CAS fails) but 
> retains the coordinator and the enlisting Exchange until the timer fires 
> (hours for long timeouts); under exchange pooling the captured exchange may 
> have been recycled when the timer runs. Fix: keep and cancel the 
> {{ScheduledFuture}}; extract only what is needed from the exchange.
> * *In-memory status machine loose ends*: terminal status is set in 
> {{thenApply}} (skipped if the chain completes exceptionally - coordinator 
> hangs in COMPENSATING/COMPLETING); {{doCompensate}} marks COMPENSATED even 
> when steps exhausted all retries (WARN only). The EIP doc says "a manual 
> intervention process should be triggered" - there is no hook, event or 
> distinct status (e.g. COMPENSATION_FAILED) for it.
> * *MANUAL completion without a timeout silently leaks the saga*: docs say 
> MANUAL should always be combined with a timeout and mention a possible 
> default timeout; nothing enforces or warns, and no default-timeout mechanism 
> exists in either implementation. Fix: fail fast (or WARN) at reifier time; 
> optionally add a configurable default timeout.
> * *ifNotException can swallow synchronous exceptions and hang the exchange*: 
> the propagation processors run continuation code inside {{whenComplete}} 
> lambdas (SagaProcessor.java:155-172). If {{code.run()}} throws synchronously 
> the exception disappears into the already-completed future and the 
> {{AsyncCallback}} is never invoked. {{LRASagaCoordinator}} defensively wraps 
> exactly one such case (LRASagaCoordinator.java:40-47); the general risk 
> remains. Fix: try/catch around {{code.run()}} -> {{exchange.setException(t); 
> callback.done(false)}}.
> * *LRASagaService.setCamelContext adds participant REST routes as a side 
> effect of the setter* (LRASagaService.java:121-131): routes are added even if 
> the service is never started, and failures (e.g. no rest component) surface 
> from an unexpected place. Route setup belongs in {{doStart}}.
> * *camel-lra: single shared URI allowlist, never pruned*: {{verifyRequest}} 
> validates against one combined set of all compensation and completion URIs 
> (LRASagaRoutes.java:135-139), so a completion callback may invoke a 
> compensation URI and vice versa, and URIs from removed routes stay allowed 
> for the context lifetime.
> h3. Low / cleanups
> * {{SagaProcessor.toString()}} returns the literal string "id" 
> (SagaProcessor.java:146-148).
> * {{InMemorySagaCoordinator.beginStep}} does {{putIfAbsent(step, new 
> ConcurrentHashMap<>())}} immediately followed by {{computeIfAbsent(step, k -> 
> new HashMap<>())}} - the second mapping function is dead code.
> * Failed futures built as {{CompletableFuture.supplyAsync(() -> { throw ex; 
> })}} (InMemorySagaCoordinator.java:101-103, LRASagaCoordinator.java:44-46) 
> burn a pool task (the first on the common ForkJoinPool) - use 
> {{CompletableFuture.failedFuture(ex)}}.
> * {{LRAClient.join}} appends the time limit as {{lra + "?" + "TimeLimit=" + 
> ms}} (LRAClient.java:127-129) - double-{{?}} URL if the LRA id URI ever 
> carries a query string.
> * Missing {{coordinatorUrl}} surfaces as {{IllegalStateException("Host not 
> set")}} from {{LRAUrlBuilder}} during doStart; missing 
> {{localParticipantUrl}} only fails at the first join. Validate both at 
> doStart with messages naming the configuration options.
> * {{LRASagaService.registerStep}} uses {{Optional.map(set::add)}} for a side 
> effect with ignored return - use {{ifPresent}}.
> * {{SagaProcessorBuilder}}/{{SagaProcessor}} accept a {{CamelContext}} that 
> is never stored or used.
> * The EIP doc claims "the Saga EIP implementation may have a default timeout 
> set on all Sagas" - matches no shipped implementation.
> * {{SagaProducer}}'s "not bound to a saga context: cannot complete" message 
> is hardcoded - {{saga:compensate}} failures also say "cannot complete" 
> (SagaProducer.java:49-50).
> Found during a broader Saga EIP / camel-lra code review (July 2026).
> _Claude Code on behalf of Croway_



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to