[ 
https://issues.apache.org/jira/browse/CAMEL-25060?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18119922#comment-18119922
 ] 

shashank commented on CAMEL-25060:
----------------------------------

PR: https://github.com/apache/camel/pull/26943

I cannot assign issues to myself; could a committer assign this to me 
(smjainblr)? Thanks.

_Claude Code on behalf of allthingssecurity_

> Property placeholders: an optional circular reference such as 
> timeout={{?timeout:5000}} makes the parser loop for ever (CamelContext start 
> hangs at 100 % CPU)
> --------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25060
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25060
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-core
>            Reporter: shashank
>            Priority: Minor
>
> {{DefaultPropertiesParser.ParsingContext.doParseNested}} 
> ({{core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java}})
>  detects a circular reference by the keys that are already being resolved on 
> the current branch:
> {code:java}
> while ((property = readProperty(prevKey, answer)) != null) {
>     if (replacedPropertyKeys.contains(property.getKey())) {
>         // Check for circular references (skip optional)
>         boolean optional = property.getKey().startsWith(OPTIONAL_TOKEN);
>         if (optional) {
>             break;
>         } else {
>             throw new IllegalArgumentException("Circular reference detected 
> with key [" + ...);
>         }
>     }
>     ...
>     String parsed = doParseNested(property.getKey(), property.getValue(), 
> newReplaced);
>     answer = before + parsed + after;
> }
> {code}
> For an optional key the {{break}} returns the text with the placeholder still 
> in it. The caller puts that text back into its own {{answer}}, and its loop 
> reads the same placeholder again. Its set of keys is one key smaller, so it 
> does not see a cycle, resolves the placeholder again, gets the same text 
> back, and so on for ever. A required cycle is reported with the exception; an 
> optional one hangs the thread at 100 % CPU without any log.
> A plausible way to get there is a property that should take an override from 
> somewhere else and otherwise a default, written with its own name: 
> timeout=\{\{?timeout:5000\}\}. Two optional properties that refer to each 
> other (a=\{\{?b\}\} and b=\{\{?a\}\}) hang as well.
> h3. Reproduction
> A standalone program against main 65f315628; each case runs in its own thread 
> with a 3 second limit:
> {noformat}
> self={{?self}};            resolvePropertyPlaceholders("{{self}}")    -> 
> still running after 3005 ms
> a={{?b}}, b={{?a}};        resolvePropertyPlaceholders("x{{a}}y")     -> 
> still running after 3004 ms
> timeout={{?timeout:5000}}; resolvePropertyPlaceholders("{{timeout}}") -> 
> still running after 3002 ms
> same, route from("timer:t?period={{timeout}}&repeatCount=1"), 
> CamelContext.start() -> still running after 3004 ms
>   stack: DefaultPropertiesParser doGetPropertyValue:468 getPropertyValue:391 
> readProperty:243 doParseNested:174
>          doParseNested:202 doParseNested:202 parse:121 parseUri:73
> controls:
>   loop1={{loop2}}, loop2={{loop1}}  -> IllegalArgumentException: Circular 
> reference detected with key [loop1]
>   x{{?nope}}y                        -> xy
>   a={{b}}-{{?c}}, b=B: {{a}}         -> B-
> {noformat}
> A thread dump of the first case after 160 s shows the main thread RUNNABLE 
> with 159 s of CPU time in the same frames. A jqwik property over cycles of 1 
> to 3 keys fails on the first cycle whose closing reference is optional, and a 
> Lean model of {{readProperty}} and {{doParseNested}} proves that resolving 
> the self reference runs out of fuel for every amount of fuel: one iteration 
> of the loop at the second level returns it to exactly the same state.
> h3. Why the break can never return a result
> The set holds the keys of the placeholders being resolved above the current 
> one on the same branch (it is copied for each branch), so the check only 
> fires for a real cycle. When a level breaks, it returns its text to the level 
> above, whose next {{readProperty}} finds the same placeholder first. If that 
> level's own set does not hold the key, it is the level that resolves that 
> key, and it recurses into exactly the same state as before. If its set holds 
> the key too, it breaks as well, and the same happens one level up. The top 
> level starts with an empty set, so some level always loops. Unless a 
> properties function gives a different value on each call, no configuration 
> can get a result through this {{break}}, so any change that makes it end 
> cannot break a configuration that works today.
> Optional placeholders came with CAMEL-16302 (3.9.0), which added this 
> {{break}}; the comment says "skip optional", so the intent was to skip the 
> cycle, not to hang. The {{break}} is in camel-3.9.0 and every later release 
> checked (3.14.0, 4.0.0, 4.14.0, 4.18.0) and on main. The documentation of 
> optional placeholders ("Using optional property placeholders" in 
> {{using-propertyplaceholder.adoc}}) says what happens to a missing optional 
> key and says nothing about cycles.
> h3. Proposed fix
> Report the circular reference for optional keys too, with the same 
> {{IllegalArgumentException}} ("Circular reference detected with key 
> \[?timeout:5000\] ...") as for required keys, by removing the {{optional}} 
> special case. The configuration is wrong either way, and the user gets a 
> clear message at startup instead of a hung thread.
> The alternative, treating the cyclic placeholder like a missing optional key, 
> would have to use the default after the first {{:}} when there is one, and 
> otherwise return what a missing optional key gives (dropped from the text, or 
> the option removed from an endpoint URI). That is more code, needs its own 
> tests for both modes, and hides a configuration error. To take an override 
> and otherwise a default, another key works today: 
> timeout=\{\{?timeout.override:5000\}\}.
> Tests: a new test with {{assertTimeoutPreemptively}}: the self reference, the 
> mutual reference and the self reference with a default each end with the 
> circular reference error, also as a timer endpoint option on 
> {{CamelContext.start()}}; a required cycle still gives the error; non-cyclic 
> optional placeholders, defaults and a missing optional key in an endpoint URI 
> keep working.
> Duplicate check (2026-09-27): JIRA {{text ~ "DefaultPropertiesParser"}} 
> (CAMEL-24776, CAMEL-23652, CAMEL-21484 and older), {{text ~ "Circular 
> reference detected"}} (CAMEL-11273, CAMEL-11668, CAMEL-11497), {{summary ~ 
> placeholder AND (hang OR loop OR circular OR infinite)}}, and optional 
> placeholder issues since 2024: nothing related. GitHub PR searches 
> {{DefaultPropertiesParser}}, {{circular reference placeholder}} and 
> {{optional placeholder}}: nothing related. Not reported.
> _Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to