shashank created CAMEL-25060:
--------------------------------

             Summary: 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


{{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