Guillaume Nodet created CAMEL-24227:
---------------------------------------
Summary: Add volatile to JMX-writable fields read on routing
threads
Key: CAMEL-24227
URL: https://issues.apache.org/jira/browse/CAMEL-24227
Project: Camel
Issue Type: Improvement
Components: camel-management
Reporter: Guillaume Nodet
An audit of the Camel management layer revealed a systemic JMM-unsafe pattern
across 12 engine classes (~50 fields). Fields writable via JMX
@ManagedAttribute setters are stored as plain (non-volatile) fields in engine
classes, but read on routing threads without any memory visibility guarantee.
This was discovered while investigating the BacklogTracerActivityTest flaky
test (PR [#24713|https://github.com/apache/camel/pull/24713]), where
activityEnabled, enabled, and standby fields in BacklogTracer were not volatile.
h3. Affected classes
||Engine class||Non-volatile JMX-writable fields||Hot path||
|BacklogTracer|tracePattern+patterns, traceFilter+predicate, bodyMaxChars,
bodyIncludeStreams, bodyIncludeFiles, includeExchangeProperties,
includeExchangeVariables, backlogSize, activitySize,
removeOnDump|shouldTrace(), traceNode()|
|DefaultTracer|enabled, tracePattern+patterns|shouldTrace() via
TracingAdvice.before()|
|BaseProcessorSupport|disabled|CamelInternalProcessor.process() — every
exchange|
|DefaultBacklogDebugger|fallbackTimeout (long — non-atomic on 32-bit JVMs),
bodyMaxChars, bodyIncludeStreams, bodyIncludeFiles, includeExchangeProperties,
includeExchangeVariables,
singleStepIncludeStartEnd|NodeBreakpoint.beforeProcess()|
|TotalRequestsThrottler|timePeriodMillis (long)|process() — every exchange|
|AbstractThrottler|maxRequestsExpression|process() — every exchange|
|DefaultStreamCachingStrategy|spoolThreshold (long),
spoolUsedHeapMemoryThreshold, anySpoolRules, bufferSize|shouldSpoolCache()|
|ThrottlingInflightRoutePolicy|maxInflightExchanges+resumeInflightExchanges
(compound non-atomic), resumePercentOfMax, scope|throttle() via
onExchangeDone()|
|ThrottlingExceptionRoutePolicy|halfOpenAfter (long), failureWindow (long),
failureThreshold|calculateState() via onExchangeDone()|
|ManagedPerformanceCounter|statisticsEnabled|DefaultInstrumentationProcessor.before()
— every exchange|
|ScheduledPollConsumer|greedy, sendEmptyMessageWhenIdle, delay,
runLoggingLevel|doRun() on every poll cycle|
|Delayer|delay (Expression)|calculateDelay() — every exchange|
h3. Recommended fix approach
* For simple boolean/int fields: add volatile. Cost is negligible — on x86,
volatile reads compile to the same instruction as plain loads.
* For compound writes (tracePattern+patterns,
maxInflightExchanges+resumeInflightExchanges): volatile alone is insufficient.
These need either a lock or an immutable holder object swapped atomically via
volatile reference.
* Do NOT use synchronized on MBean getters/setters — it would add lock
contention on hot paths for no benefit.
h3. Practical impact
None of these are known to cause flaky tests today (only BacklogTracer's guard
conditions manifested, fixed in PR #24713). On x86 the bug is mostly
theoretical because volatile reads compile to plain loads. The fix is justified
as correctness-by-contract per the Java Memory Model, and would matter on
ARM/POWER architectures with weaker memory ordering.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)