OPC UA Batch Service Results: Handle Per-Operation Failure After a Successful Service Call

A successful OPC UA Read or Write service response does not mean every requested item succeeded. Decode two levels: the ResponseHeader serviceResult, then the ordered result for each operation. Preserve that distinction through cache updates, command reporting and retry decisions instead of collapsing the batch into one success boolean.

This guide pins OPC UA Part 4, Services, version 1.05.07, sections 5.11.2, 5.11.4, 7.11, 7.33 and 7.34. It focuses on Read DataValues and Write StatusCodes. An original post-decoding result model was executed locally; it does not implement a UA wire codec or perform real writes.

Preserve request slots until completion

Keep an immutable ordered request record containing each node, Attribute, optional index range and local operation identity. A batch may refer to several Attributes of one node, so a map keyed only by node name can merge operations that must remain distinct.

Bind the record to the Session and configuration generation used when submitting it. Reordering a mutable configuration list while a response is in flight must not change which result slot updates which application value.

The server’s response requestHandle helps correlate the service invocation. It does not replace the per-operation ordering contract. Let the SDK establish message association, then use the preserved request vector to interpret the result vector.

First classify the service result. A bad service result is a ServiceFault and supplies no normal item-success evidence. For a successful Read or Write response, validate result cardinality against the request before matching slots. Then preserve individual Good, Uncertain or Bad outcomes rather than turning the batch into one boolean.
Validate response structure before cache effects. Per-item failure remains visible even when the service invocation succeeds.

Handle service failure before item interpretation

ResponseHeader describes the service invocation. When serviceResult has Bad severity, Part 4 specifies a ServiceFault instead of the normal service response. Do not process a stale result array left in a reused application object as if it belonged to that failed call.

A service-level Bad_TooManyOperations, for example, calls for reviewing request limits. A per-item Bad_NodeIdUnknown instead identifies one failed node operation within an otherwise processed request. These errors deserve different diagnostics and corrective actions.

If transport completion itself is missing, classify that separately from a received ServiceFault. For consequential writes, an absent response may leave the effect unknown. The result decoder must not manufacture a per-item rejection merely because it has no response to decode.

Validate cardinality before updating the cache

Read results match nodesToRead in both size and order. Write results similarly match nodesToWrite. Check that relationship before performing any application updates, and reject or quarantine a structurally inconsistent decoded response.

This is a local defensive rule layered on the protocol contract. It prevents a partial loop from updating the first few cache entries before discovering that the final result is missing. It does not roll back a write already executed by the server.

Diagnostic arrays may legitimately be empty when not requested or unavailable. Do not require diagnosticInfos to contain one element merely because normal results must. When diagnostics are present, preserve their defined positional association and resolve any string-table references through the supported decoder.

Keep Read quality attached to each value

Each Read result is a DataValue with its own status and timestamps. A Good service envelope can contain a mixture of Good, Uncertain and Bad DataValues. Forward the complete accepted DataValue contract, not only its numeric payload.

For a Bad DataValue status, the server returns a null value and the client disregards its value. A defensive consumer should not turn an unexpected non-null payload accompanying Bad status into usable data. Keep historical last-good values separately with their original quality and age.

Uncertain quality needs the application’s explicit acceptance policy. Do not convert it to Good because the payload can be parsed. Likewise, preserve relevant status information bits rather than reducing every status to a severity-only display if the application relies on those bits.

Read success also does not prove one simultaneous snapshot across all requested nodes. This article’s batch association guarantees concern result mapping, not source acquisition timing or process coherence.

Report Write outcomes individually

Write returns a StatusCode for each operation. Some operations can succeed while others fail, and the service does not supply a general transaction rollback. A response with one successful write and one access denial must be reported as mixed, with both results retained.

Good_CompletesAsynchronously is another important distinction: it indicates a write to an intermediate system without knowledge that the data source was updated properly. Preserve that status instead of presenting it as unconditional final completion.

Do not retry the complete batch automatically after one item fails. That can repeat operations that already succeeded, including command-like writes. Use the operation’s documented retry and completion contract, and correct permanent failures such as unsupported Attributes or denied access rather than blindly resubmitting.

An original three-item fixture

The hypothetical request contains slots A, B and C. The executed Read model receives Good with value ten, Bad with an intentionally invalid extra payload, and Uncertain with value thirty. It maps A to usable Good data, B to unavailable data with no adopted payload, and C to retained Uncertain data.

A second response contains only one result for the three-item request. The model rejects it before changing any cache entry. A bad service result similarly leaves the cache unchanged.

The Write fixture returns Good, Bad_UserAccessDenied and Good_CompletesAsynchronously for A, B and C respectively. All three remain distinct in the outcome table. The test makes no claim that a server performed these operations; it exercises the bridge’s post-decoding propagation rules.

A release checklist

  1. Keep immutable request ordering through response delivery and configuration changes.
  2. Distinguish transport uncertainty, bad service result and per-operation failure.
  3. Validate result count before application cache effects.
  4. Exercise mixed Good, Uncertain and Bad Read values with status and timestamp preservation.
  5. Exercise mixed Write results without whole-batch retry or invented rollback.
  6. Test empty diagnostics, malformed cardinality and delayed responses from retired generations.

The local model covered result association, severity propagation, ignored bad payloads and cardinality gates. It did not test timestamps, wire defaults, diagnostics decoding or server execution. Acceptance on the actual SDK should produce an outcome for every requested slot, with an explanation of which boundary supplied that evidence and which outcome remains unknown.

Similar Posts