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)

Reply via email to