[
https://issues.apache.org/jira/browse/CAMEL-25060?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen resolved CAMEL-25060.
---------------------------------
Resolution: Fixed
Fixed by https://github.com/apache/camel/pull/26943 (merged to main for 4.23.0).
_Claude Code on behalf of davsclaus_
> 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
> Fix For: 4.23.0
>
>
> {{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)