Serdar Gökay created CAMEL-25695:
------------------------------------
Summary: camel-milo - awaited readValues service failures are
reported as success
Key: CAMEL-25695
URL: https://issues.apache.org/jira/browse/CAMEL-25695
Project: Camel
Issue Type: Bug
Components: camel-milo
Affects Versions: 4.22.1
Environment: Camel 4.22.1; Eclipse Milo 1.1.6; OpenJDK 25.0.4; Maven
3.9.16; macOS arm64. Attached reproduction uses a real loopback Milo OPC UA
server. Independent network confirmation used asyncua 1.1.8 on Python 3.12 and
a loopback TCP relay. No physical PLC or production deployment.
Reporter: Serdar Gökay
Attachments: camel-milo-readvalues-failure-reproducer.zip
h3. Problem
An awaited producer read selected by {{CamelMiloNodeIds}} can fail at the OPC
UA service/transport layer while the Camel exchange completes successfully with
a null body. The success continuation runs and configured redelivery,
{{onException}} and dead-letter handling are skipped.
Reproduced on released Camel 4.22.1 / Eclipse Milo 1.1.6. Both
{{CamelMiloAwait=true}} and {{defaultAwaitWrites=true}} reproduce the issue.
This concerns producer reads, not subscription-consumer delivery. Related
write/call finding: CAMEL-25694.
h3. Reproduction
The attached standalone Maven project starts its own real loopback TCP OPC UA
server. It does not mock the client, futures or transport, and does not patch
Camel classes.
{code:bash}
mvn -B -ntp -Duser.language=en -Duser.country=US
-Dtest=SingleNodeReadContractTest test
mvn -B -ntp -Duser.language=en -Duser.country=US -Dtest=AwaitedReadContractTest
test
{code}
# Establish a connection and successfully read the test node(s); verify values
and Good statuses.
# Make a subsequent awaited read. The real server returns a {{Bad_Timeout}}
ServiceFault for that read.
# Inspect the exchange and route counters:
{noformat}
READ_CONTRACT_RESULT camel=4.22.1 milo=1.1.6 mode=BAD_TIMEOUT
reads=1 redeliveries=0 downstream=1 failed=false exception=null body=null
{noformat}
Both desired-contract tests fail specifically because the exchange exception is
missing. The minimal reproduction needs only one node. A direct Milo SDK client
against the same server/fault throws {{UaException}} with the matching status.
The attachment also contains 14 characterization/control tests, run with {{mvn
test '-Dtest=*EvidenceTest'}}. These pass by asserting the observed behavior,
including the defect; they do not demonstrate a fix. They cover both await
settings, {{Bad_Timeout}} and {{Bad_SessionClosed}} service responses, native
SDK controls, successful list types/order/duplicates, namespace-URI addressing,
per-node bad status, retry/DLQ/onException controls and explicitly non-awaited
behavior.
Independent network testing used a normal asyncua OPC UA server behind a
loopback TCP relay. Dropping requests, dropping responses or closing the
established connection produced the same null-success result. Nine network
cases were run twice: four healthy controls passed and all fourteen failure
executions failed the desired contract. These network scripts are separate from
the attached minimal Maven reproduction.
h3. Expected behavior and scope
When an awaited read completes exceptionally, preserve that exceptional
completion so {{MiloClientProducer}} can fail the exchange and configured Camel
error handling can run. Preserve successful {{List<DataValue>}} results and
ordinary per-node bad statuses, such as {{Bad_NodeIdUnknown}}, inside their
individual {{DataValue}} results.
An already-disconnected manager has a separate exceptional {{No connected}}
path; the reproduction deliberately establishes a healthy connection first.
Explicit non-await mode is not the awaited defect. {{Bad_SessionClosed}} is a
controlled service-error response, not an actual session-expiry simulation. No
physical PLC or subscription behavior is claimed.
h3. Cause and proposed direction
In outer {{SubscriptionManager.readValues}}, {{handleAsync}} calls
{{handleConnectionFailure(error)}} and then returns {{nodes}}, which is null
after exceptional completion. That converts a failed future into normal
completion. The producer's existing awaited callback consequently sees no
throwable.
Proposed change, keeping the executor and recovery callback:
{code:java}
return this.connected.readValues(nodeIds).whenCompleteAsync((nodes, error) -> {
if (error != null) {
handleConnectionFailure(error);
}
}, this.executor);
{code}
This is a proposed direction, not an applied or verified fix. The completion
observer preserves both successful lists and exceptional completion.
Pinned released source:
[SubscriptionManager.readValues|https://github.com/apache/camel/blob/c056bef67cdfeffdc0ad7255e5e052cab71f2921/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java#L1014-L1032]
and
[MiloClientProducer|https://github.com/apache/camel/blob/c056bef67cdfeffdc0ad7255e5e052cab71f2921/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientProducer.java#L77-L103].
_This report and reproduction are AI-generated with Codex on behalf of GitHub
user etzel-mes._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)