o6 Automation
All articles

Feature · December 8, 2025

Async Read/Write/Call in open62541

Five Minute Feature Highlight

Tangled lines resolving into parallel paths, representing asynchronous processing

Async Read Example

An OPC UA server often represents a physical device - and those can be slow. For example, when reading from a temperature sensor, it might take time for the measurement to finish. But waiting for the results should not block the control flow of the entire OPC UA server. To overcome this, we have greatly improved the support for async operations in open62541 v1.5. An application-specific callback method is attached to the variable node of the temperature sensor in the information model. See below for the interface of the callback method.

/* Callback for reading the temperature sensor
 *
 * @param server The server executing the callback.
 * @param sessionId The identifier of the session.
 * @param sessionContext Additional data attached to the session in the
 *        access control plugin.
 * @param nodeId The identifier of the node which is read.
 * @param nodeContext Additional data attached to the node by the application.
 * @param includeSourceTimeStamp If true, then the datasource is expected to
 *        set the source timestamp in the returned value.
 * @param range If not null, then the datasource shall return only a
 *        selection of the (nonscalar) data.
 * @param value The (non-null) result that is returned to the client. The
 *        data source can set the value, the status and optionally a
 *        source-timestamp.
 * @return Returns a status code for the success of the operation. Can be
 *         UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY to complete later. */

UA_StatusCode
readTemp(UA_Server *server, const UA_NodeId *sessionId,
         void *sessionContext, const UA_NodeId *nodeId,
         void *nodeContext, UA_Boolean includeSourceTimeStamp,
         const UA_NumericRange *range, UA_DataValue *value);

Within the callback method, the implementation might first check if it has access to a recent temperature value. If yes, then the latest value is returned. Otherwise a new time-intensive measurement is started. For example in a dedicated thread or by registering a timer for later processing of the results. The callback then returns the control flow back to the server. The return code UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY is used to signal that the read results will become available at a later time. The server puts the response "on hold" until all operations in the read request have completed. Now the server can continue to process other requests without blocking.

/* Asynchronously set read results when they become available.
 * The result pointer must be reused from the read callback. */
UA_StatusCode
UA_Server_setAsyncReadResult(UA_Server *server, UA_DataValue *result);

It is the responsibility of the callback method implementation (who signaled the async status) to complete the operation eventually via UA_Server_setAsyncReadResults. Importantly, the UA_DataValue results pointer that was forwarded to the read-callback is also used for setting the results. The lifetime of the results pointer is for the entire duration of the async operation.

Cancelling Async Operations

Besides waiting indefinityely for results, a server can also cancel an async operation. This comes either from the requesting client (session) or from the server itself, e.g. during shutdown or after a timeout. The application receives a notification about the cancelling via a callback method in the server config. In the API snippet below, the "out" argument is again the results pointer from the read operation. The same cancel notification mechanism is however also used for write and call operations. After receiving the cancel notification, the results pointer is no longer valid and the async operation should no longer try to access it.

/* Cancel callback method in the server configuration.
 *
 * Notifies the application that an async operation has been canceled. The
 * memory for setting the output value is then freed internally and should
 * not be touched afterwards. */
void (*asyncOperationCancelCallback)(UA_Server *server, const void *out);

For async writes and method calls, the same basic callback approach is used. Check tutorial_server_method_async.c for a fully worked out async method example.

Now you have all the basic information to implement async operations in your own OPC UA application powered by open62541. If you want, you can continue reading for the implementation details.

Implementation Details

The implementation mainly lives in src/server/ua_server_async.h and src/server/ua_server_async.c. The implementation of Service_Read, Service_Write and Service_Call lives in these async files. They then call into Operation_Read et al. which sit in the source file for their service set. The service implementations actually have a lot in common. They decompose into an array of read/write/call operations and each operation might surprisingly yield an async result the server needs to wait for. The "results pointer" needs to be stable in case of an async operation and still, because these are very common operations, the async logic should create no overhead in the synchronous case. This is achieved as follows:

The UA_ReadResponse data structure contains an array of UA_DataValue, one for every read operation. Because C allows us to abuse memory management, we just allocate a bigger array. It not only contains the memory for the UA_DataValue, but right after comes one UA_AsyncOperation for every operation. When all operations are synchronous, we needed to allocate a bit more memory, but that has nearly no overhead. The extra memory also gets cleaned up automatically, since free() does not depend on the size of the allocated memory. But if at least one operation is async, the corresponding UA_AsyncOperation gets populated and added to an internal linked-list. Also, the server is then instructed not to send the UA_ReadResponse right away to the client, but to wait until all async operations are complete.

When UA_Server_setAsyncReadResult gets called, the implementation walks the internal linked list until it finds the entry with the matching "results pointer". Then the server also knows to which request the operation belongs and whether all remaining operations are now complete. But the response is never sent out right away. Instead we register a delayed callback in the server to be executed in the next iteration of the EventLoop. This is done to allow UA_Server_setAsyncReadResult() to be called from a worker thread. Such a worker thread should not block on sending a heavy (maybe encrypted) response message. Instead, communication is done by the main EventLoop thread only.

Also check the server configuration parameters asyncOperationTimeout and maxAsyncOperationQueueSize. They are useful if you are resource-constrained and/or the server should auto-cleanup in case an async operation forgets to return any result.

OPC UA and open62541 updates.

Technical articles, release news, training dates, and company updates from o6 Automation.

Async Read/Write/Call in open62541 | o6 Automation