Skip to Content

Async Read/Write/Call in open62541

Five Minute Feature Highlight
December 8, 2025 by
Async Read/Write/Call in open62541
Julius Pfrommer
| No comments yet

The Five Minute Feature Highlight series showcases new and important features of open62541 and the OPC UA standard. Time well spent while waiting for the compiler!

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.

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.

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.

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.

Sign in to leave a comment