Przeglądaj źródła

Async monitored items (#2549)

* Implemented async subscriptions/monitored items

* Extended custom callback to hold UA_Client callback specific data

* Distinguish between user and UA_Client specific callback data
* Memory management for client-specific callback data (deleter)
* Updated current highlevel_async functions to new interface

* Fix: memory issues in async subscriptions/monitored items

clang analyzer memory issues addressed

* Fix codacy errors in async subscription/monitoreditems

* Memory fix: MonitoredItems_CreateData_free(..)

* Memory fix: UA_Client_MonitoredItems_delete_async

Async handler did not copy the request into CustomCallback::clientData

* Fix: portability issues with cppcheck+gcc4.8

* Fix: callback handler for delete_subscription_async was not called

* Added test cases for async subscription functions

* Fix -Werror=c++-compat in async subscription/monitoreditems tests

* Satisfy clang-format for async subscription/monitoreditems implementation

* Rebased to master and applied clang-format again

* remove CustomCallback_new(void)

* Change UA_malloc to UA_calloc when needed

* Remove CustomCallbackDataDeleter and use stack allocation for subscriptions.

Requires the use of a flag to distinguish stack allocated data from
heap allocated data in the subscription/monitored items implementation

* Fix C++-compat warning

* Fix: clang-format for changes

* Fix: uninitialized variable on conditional jump

* Remove all UA_EXPORT occurrences from ua_client_subscriptions

* clang-analyze: fix argument to free() is the address of local variable

clang analyzer cannot infer from
memset(&cc, 0, sizeof(CustomCallback)) that CustomCallback.isAsync is false.
Iadded a __clang_analyzer__ only statement that explicitly sets isAsync to false
using preprocessor macros.

* Bugfix: async monitored items memory leak

* Satisfy clang-analyzer for creation of monitored items
wbitesi 4 lat temu
rodzic
commit
2d5355b7be

+ 72 - 1
include/open62541/client_subscriptions.h

@@ -69,13 +69,33 @@ UA_Client_Subscriptions_create(UA_Client *client,
                                UA_Client_StatusChangeNotificationCallback statusChangeCallback,
                                UA_Client_DeleteSubscriptionCallback deleteCallback);
 
+UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_create_async(
+    UA_Client *client, const UA_CreateSubscriptionRequest request,
+    void *subscriptionContext,
+    UA_Client_StatusChangeNotificationCallback statusChangeCallback,
+    UA_Client_DeleteSubscriptionCallback deleteCallback,
+    UA_ClientAsyncServiceCallback callback, void *userdata, UA_UInt32 *requestId);
+
 UA_ModifySubscriptionResponse UA_EXPORT
 UA_Client_Subscriptions_modify(UA_Client *client, const UA_ModifySubscriptionRequest request);
 
+UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_modify_async(UA_Client *client,
+                                     const UA_ModifySubscriptionRequest request,
+                                     UA_ClientAsyncServiceCallback callback,
+                                     void *userdata, UA_UInt32 *requestId);
+
 UA_DeleteSubscriptionsResponse UA_EXPORT
 UA_Client_Subscriptions_delete(UA_Client *client,
                                const UA_DeleteSubscriptionsRequest request);
 
+UA_StatusCode UA_EXPORT
+UA_Client_Subscriptions_delete_async(UA_Client *client,
+                                     const UA_DeleteSubscriptionsRequest request,
+                                     UA_ClientAsyncServiceCallback callback,
+                                     void *userdata, UA_UInt32 *requestId);
+
 /* Delete a single subscription */
 UA_StatusCode UA_EXPORT
 UA_Client_Subscriptions_deleteSingle(UA_Client *client, UA_UInt32 subscriptionId);
@@ -144,6 +164,13 @@ UA_Client_MonitoredItems_createDataChanges(UA_Client *client,
             UA_Client_DataChangeNotificationCallback *callbacks,
             UA_Client_DeleteMonitoredItemCallback *deleteCallbacks);
 
+UA_StatusCode UA_EXPORT
+UA_Client_MonitoredItems_createDataChanges_async(
+    UA_Client *client, const UA_CreateMonitoredItemsRequest request, void **contexts,
+    UA_Client_DataChangeNotificationCallback *callbacks,
+    UA_Client_DeleteMonitoredItemCallback *deleteCallbacks,
+    UA_ClientAsyncServiceCallback createCallback, void *userdata, UA_UInt32 *requestId);
+
 UA_MonitoredItemCreateResult UA_EXPORT
 UA_Client_MonitoredItems_createDataChange(UA_Client *client, UA_UInt32 subscriptionId,
           UA_TimestampsToReturn timestampsToReturn, const UA_MonitoredItemCreateRequest item,
@@ -157,6 +184,14 @@ UA_Client_MonitoredItems_createEvents(UA_Client *client,
             UA_Client_EventNotificationCallback *callback,
             UA_Client_DeleteMonitoredItemCallback *deleteCallback);
 
+/* Monitor the EventNotifier attribute only */
+UA_StatusCode UA_EXPORT
+UA_Client_MonitoredItems_createEvents_async(
+    UA_Client *client, const UA_CreateMonitoredItemsRequest request, void **contexts,
+    UA_Client_EventNotificationCallback *callbacks,
+    UA_Client_DeleteMonitoredItemCallback *deleteCallbacks,
+    UA_ClientAsyncServiceCallback createCallback, void *userdata, UA_UInt32 *requestId);
+
 UA_MonitoredItemCreateResult UA_EXPORT
 UA_Client_MonitoredItems_createEvent(UA_Client *client, UA_UInt32 subscriptionId,
           UA_TimestampsToReturn timestampsToReturn, const UA_MonitoredItemCreateRequest item,
@@ -167,7 +202,14 @@ UA_DeleteMonitoredItemsResponse UA_EXPORT
 UA_Client_MonitoredItems_delete(UA_Client *client, const UA_DeleteMonitoredItemsRequest);
 
 UA_StatusCode UA_EXPORT
-UA_Client_MonitoredItems_deleteSingle(UA_Client *client, UA_UInt32 subscriptionId, UA_UInt32 monitoredItemId);
+UA_Client_MonitoredItems_delete_async(UA_Client *client,
+                                      const UA_DeleteMonitoredItemsRequest request,
+                                      UA_ClientAsyncServiceCallback callback,
+                                      void *userdata, UA_UInt32 *requestId);
+
+UA_StatusCode UA_EXPORT
+UA_Client_MonitoredItems_deleteSingle(UA_Client *client, UA_UInt32 subscriptionId,
+                                      UA_UInt32 monitoredItemId);
 
 /* The clientHandle parameter will be filled automatically */
 UA_ModifyMonitoredItemsResponse UA_EXPORT
@@ -198,6 +240,35 @@ UA_Client_MonitoredItems_setTriggering(UA_Client *client,
     return response;
 }
 
+static UA_INLINE UA_StatusCode
+UA_Client_MonitoredItems_modify_async(UA_Client *client,
+                                      const UA_ModifyMonitoredItemsRequest request,
+                                      UA_ClientAsyncServiceCallback callback,
+                                      void *userdata, UA_UInt32 *requestId) {
+    return __UA_Client_AsyncService(
+        client, &request, &UA_TYPES[UA_TYPES_MODIFYMONITOREDITEMSREQUEST], callback,
+        &UA_TYPES[UA_TYPES_MODIFYMONITOREDITEMSRESPONSE], userdata, requestId);
+}
+
+static UA_INLINE UA_StatusCode
+UA_Client_MonitoredItems_setMonitoringMode_async(
+    UA_Client *client, const UA_SetMonitoringModeRequest request,
+    UA_ClientAsyncServiceCallback callback, void *userdata, UA_UInt32 *requestId) {
+    return __UA_Client_AsyncService(
+        client, &request, &UA_TYPES[UA_TYPES_SETMONITORINGMODEREQUEST], callback,
+        &UA_TYPES[UA_TYPES_SETMONITORINGMODERESPONSE], userdata, requestId);
+}
+
+static UA_INLINE UA_StatusCode
+UA_Client_MonitoredItems_setTriggering_async(UA_Client *client,
+                                             const UA_SetTriggeringRequest request,
+                                             UA_ClientAsyncServiceCallback callback,
+                                             void *userdata, UA_UInt32 *requestId) {
+    return __UA_Client_AsyncService(
+        client, &request, &UA_TYPES[UA_TYPES_SETTRIGGERINGREQUEST], callback,
+        &UA_TYPES[UA_TYPES_SETTRIGGERINGRESPONSE], userdata, requestId);
+}
+
 #endif
 
 _UA_END_DECLS

+ 27 - 16
src/client/ua_client_highlevel.c

@@ -753,6 +753,11 @@ cleanup:
 
 /* Async Functions */
 
+typedef struct {
+    UA_AttributeId attributeId;
+    const UA_DataType *outDataType;
+} AsyncReadData;
+
 static
 void ValueAttributeRead(UA_Client *client, void *userdata,
                         UA_UInt32 requestId, void *response) {
@@ -760,28 +765,25 @@ void ValueAttributeRead(UA_Client *client, void *userdata,
         return;
 
     /* Find the callback for the response */
-    CustomCallback *cc;
-    LIST_FOREACH(cc, &client->customCallbacks, pointers) {
-        if(cc->callbackId == requestId)
-            break;
-    }
+    CustomCallback *cc = UA_Client_findCustomCallback(client, requestId);
     if(!cc)
         return;
 
     UA_ReadResponse *rr = (UA_ReadResponse *) response;
     UA_DataValue *res = rr->results;
     UA_Boolean done = false;
+    AsyncReadData *data = (AsyncReadData *)cc->clientData;
     if(rr->resultsSize == 1 && res != NULL && res->hasValue) {
-        if(cc->attributeId == UA_ATTRIBUTEID_VALUE) {
+        if(data->attributeId == UA_ATTRIBUTEID_VALUE) {
             /* Call directly with the variant */
-            cc->callback(client, userdata, requestId, &res->value);
+            cc->userCallback(client, cc->userData, requestId, &res->value);
             done = true;
         } else if(UA_Variant_isScalar(&res->value) &&
-                  res->value.type == cc->outDataType) {
+                  res->value.type == data->outDataType) {
             /* Unpack the value */
-            UA_STACKARRAY(UA_Byte, value, cc->outDataType->memSize);
-            memcpy(&value, res->value.data, cc->outDataType->memSize);
-            cc->callback(client, userdata, requestId, &value);
+            UA_STACKARRAY(UA_Byte, value, data->outDataType->memSize);
+            memcpy(&value, res->value.data, data->outDataType->memSize);
+            cc->userCallback(client, cc->userData, requestId, &value);
             done = true;
         }
     }
@@ -792,6 +794,7 @@ void ValueAttributeRead(UA_Client *client, void *userdata,
                     "Cannot process the response to the async read "
                     "request %u", requestId);
 
+    UA_free(cc->clientData);
     LIST_REMOVE(cc, pointers);
     UA_free(cc);
 }
@@ -811,17 +814,25 @@ UA_StatusCode __UA_Client_readAttribute_async(UA_Client *client,
     request.nodesToReadSize = 1;
 
     __UA_Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_READREQUEST],
-                             ValueAttributeRead, &UA_TYPES[UA_TYPES_READRESPONSE],
-                             userdata, reqId);
+                             ValueAttributeRead, &UA_TYPES[UA_TYPES_READRESPONSE], NULL,
+                             reqId);
 
     CustomCallback *cc = (CustomCallback*) UA_malloc(sizeof(CustomCallback));
     if (!cc)
         return UA_STATUSCODE_BADOUTOFMEMORY;
-    cc->callback = callback;
+    memset(cc, 0, sizeof(CustomCallback));
+    cc->userCallback = callback;
+    cc->userData = userdata;
     cc->callbackId = *reqId;
 
-    cc->attributeId = attributeId;
-    cc->outDataType = outDataType;
+    cc->clientData = UA_malloc(sizeof(AsyncReadData));
+    if(!cc->clientData) {
+        UA_free(cc);
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    AsyncReadData *rd = (AsyncReadData *)cc->clientData;
+    rd->attributeId = attributeId;
+    rd->outDataType = outDataType;
 
     LIST_INSERT_HEAD(&client->customCallbacks, cc, pointers);
 

+ 14 - 3
src/client/ua_client_internal.h

@@ -118,10 +118,11 @@ typedef struct CustomCallback {
     //to find the correct callback
     UA_UInt32 callbackId;
 
-    UA_ClientAsyncServiceCallback callback;
+    UA_ClientAsyncServiceCallback userCallback;
+    void *userData;
 
-    UA_AttributeId attributeId;
-    const UA_DataType *outDataType;
+    bool isAsync;
+    void *clientData;
 } CustomCallback;
 
 struct UA_Client {
@@ -169,6 +170,16 @@ struct UA_Client {
     UA_Boolean pendingConnectivityCheck;
 };
 
+static UA_INLINE CustomCallback *
+UA_Client_findCustomCallback(UA_Client *client, UA_UInt32 requestId) {
+    CustomCallback *cc;
+    LIST_FOREACH(cc, &client->customCallbacks, pointers) {
+        if(cc->callbackId == requestId)
+            break;
+    }
+    return cc;
+}
+
 void
 setClientState(UA_Client *client, UA_ClientState state);
 

+ 572 - 118
src/client/ua_client_subscriptions.c

@@ -22,7 +22,57 @@
 /* Subscriptions */
 /*****************/
 
-UA_CreateSubscriptionResponse UA_EXPORT
+static UA_StatusCode
+__Subscriptions_create_prepare(
+    CustomCallback *cc, const UA_CreateSubscriptionRequest *request,
+    void *subscriptionContext,
+    UA_Client_StatusChangeNotificationCallback statusChangeCallback,
+    UA_Client_DeleteSubscriptionCallback deleteCallback) {
+    UA_Client_Subscription *sub =
+        (UA_Client_Subscription *)(cc->clientData =
+                                       UA_malloc(sizeof(UA_Client_Subscription)));
+    if(!sub)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    sub->context = subscriptionContext;
+    sub->statusChangeCallback = statusChangeCallback;
+    sub->deleteCallback = deleteCallback;
+    return UA_STATUSCODE_GOOD;
+}
+
+static void
+__Subscriptions_create_handler(UA_Client *client, void *data, UA_UInt32 requestId,
+                               void *r) {
+    UA_CreateSubscriptionResponse *response = (UA_CreateSubscriptionResponse *)r;
+    CustomCallback *cc = (CustomCallback *)data;
+    if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+        goto cleanup;
+    }
+
+    UA_Client_Subscription *newSub = (UA_Client_Subscription *)cc->clientData;
+    cc->clientData = NULL;
+
+    /* Prepare the internal representation */
+    newSub->subscriptionId = response->subscriptionId;
+    newSub->sequenceNumber = 0;
+    newSub->lastActivity = UA_DateTime_nowMonotonic();
+    newSub->publishingInterval = response->revisedPublishingInterval;
+    newSub->maxKeepAliveCount = response->revisedMaxKeepAliveCount;
+
+    LIST_INIT(&newSub->monitoredItems);
+    LIST_INSERT_HEAD(&client->subscriptions, newSub, listEntry);
+
+cleanup:
+    if(cc->clientData)
+        UA_free(cc->clientData);
+
+    if(cc->isAsync) {
+        if(cc->userCallback)
+            cc->userCallback(client, cc->userData, requestId, response);
+        UA_free(cc);
+    }
+}
+
+UA_CreateSubscriptionResponse
 UA_Client_Subscriptions_create(UA_Client *client,
                                const UA_CreateSubscriptionRequest request,
                                void *subscriptionContext,
@@ -30,12 +80,17 @@ UA_Client_Subscriptions_create(UA_Client *client,
                                UA_Client_DeleteSubscriptionCallback deleteCallback) {
     UA_CreateSubscriptionResponse response;
     UA_CreateSubscriptionResponse_init(&response);
-    
-    /* Allocate the internal representation */
-    UA_Client_Subscription *newSub = (UA_Client_Subscription*)
-        UA_malloc(sizeof(UA_Client_Subscription));
-    if(!newSub) {
-        response.responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+
+    CustomCallback cc;
+    memset(&cc, 0, sizeof(CustomCallback));
+#ifdef __clang_analyzer__
+    cc.isAsync = false;
+#endif
+
+    UA_StatusCode retval = __Subscriptions_create_prepare(
+        &cc, &request, subscriptionContext, statusChangeCallback, deleteCallback);
+    if(retval != UA_STATUSCODE_GOOD) {
+        response.responseHeader.serviceResult = retval;
         return response;
     }
 
@@ -43,26 +98,46 @@ UA_Client_Subscriptions_create(UA_Client *client,
     __UA_Client_Service(client,
                         &request, &UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONREQUEST],
                         &response, &UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONRESPONSE]);
-    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
-        UA_free(newSub);
-        return response;
-    }
 
-    /* Prepare the internal representation */
-    newSub->context = subscriptionContext;
-    newSub->subscriptionId = response.subscriptionId;
-    newSub->sequenceNumber = 0;
-    newSub->lastActivity = UA_DateTime_nowMonotonic();
-    newSub->statusChangeCallback = statusChangeCallback;
-    newSub->deleteCallback = deleteCallback;
-    newSub->publishingInterval = response.revisedPublishingInterval;
-    newSub->maxKeepAliveCount = response.revisedMaxKeepAliveCount;
-    LIST_INIT(&newSub->monitoredItems);
-    LIST_INSERT_HEAD(&client->subscriptions, newSub, listEntry);
+    __Subscriptions_create_handler(client, &cc, 0, &response);
 
     return response;
 }
 
+UA_StatusCode
+UA_Client_Subscriptions_create_async(
+    UA_Client *client, const UA_CreateSubscriptionRequest request,
+    void *subscriptionContext,
+    UA_Client_StatusChangeNotificationCallback statusChangeCallback,
+    UA_Client_DeleteSubscriptionCallback deleteCallback,
+    UA_ClientAsyncServiceCallback createCallback, void *userdata, UA_UInt32 *requestId) {
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    CustomCallback *cc = (CustomCallback *)UA_calloc(1, sizeof(CustomCallback));
+    if(!cc) {
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    cc->isAsync = true;
+    cc->userCallback = createCallback;
+    cc->userData = userdata;
+
+    retval = __Subscriptions_create_prepare(cc, &request, subscriptionContext,
+                                            statusChangeCallback, deleteCallback);
+    if(retval != UA_STATUSCODE_GOOD) {
+        goto cleanup;
+    }
+
+    /* Send the request as asynchronous service call */
+    return __UA_Client_AsyncService(
+        client, &request, &UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONREQUEST],
+        __Subscriptions_create_handler, &UA_TYPES[UA_TYPES_CREATESUBSCRIPTIONRESPONSE],
+        cc, requestId);
+cleanup:
+    if(cc->clientData)
+        UA_free(cc->clientData);
+    UA_free(cc);
+    return retval;
+}
+
 static UA_Client_Subscription *
 findSubscription(const UA_Client *client, UA_UInt32 subscriptionId) {
     UA_Client_Subscription *sub = NULL;
@@ -73,7 +148,24 @@ findSubscription(const UA_Client *client, UA_UInt32 subscriptionId) {
     return sub;
 }
 
-UA_ModifySubscriptionResponse UA_EXPORT
+static void
+__Subscriptions_modify_handler(UA_Client *client, void *data, UA_UInt32 requestId,
+                               void *r) {
+    UA_ModifySubscriptionResponse *response = (UA_ModifySubscriptionResponse *)r;
+    CustomCallback *cc = (CustomCallback *)data;
+    UA_Client_Subscription *sub = (UA_Client_Subscription *)cc->clientData;
+
+    sub->publishingInterval = response->revisedPublishingInterval;
+    sub->maxKeepAliveCount = response->revisedMaxKeepAliveCount;
+
+    if(cc->isAsync) {
+        if(cc->userCallback)
+            cc->userCallback(client, cc->userData, requestId, response);
+        UA_free(cc);
+    }
+}
+
+UA_ModifySubscriptionResponse
 UA_Client_Subscriptions_modify(UA_Client *client, const UA_ModifySubscriptionRequest request) {
     UA_ModifySubscriptionResponse response;
     UA_ModifySubscriptionResponse_init(&response);
@@ -96,6 +188,31 @@ UA_Client_Subscriptions_modify(UA_Client *client, const UA_ModifySubscriptionReq
     return response;
 }
 
+UA_StatusCode
+UA_Client_Subscriptions_modify_async(UA_Client *client,
+                                     const UA_ModifySubscriptionRequest request,
+                                     UA_ClientAsyncServiceCallback callback,
+                                     void *userdata, UA_UInt32 *requestId) {
+    /* Find the internal representation */
+    UA_Client_Subscription *sub = findSubscription(client, request.subscriptionId);
+    if(!sub)
+        return UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
+
+    CustomCallback *cc = (CustomCallback *)UA_calloc(1, sizeof(CustomCallback));
+    if(!cc)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+
+    cc->isAsync = true;
+    cc->clientData = sub;
+    cc->userData = userdata;
+    cc->userCallback = callback;
+
+    return __UA_Client_AsyncService(
+        client, &request, &UA_TYPES[UA_TYPES_MODIFYSUBSCRIPTIONREQUEST],
+        __Subscriptions_modify_handler, &UA_TYPES[UA_TYPES_MODIFYSUBSCRIPTIONRESPONSE],
+        cc, requestId);
+}
+
 static void
 UA_Client_Subscription_deleteInternal(UA_Client *client, UA_Client_Subscription *sub) {
     /* Remove the MonitoredItems */
@@ -112,35 +229,53 @@ UA_Client_Subscription_deleteInternal(UA_Client *client, UA_Client_Subscription
     UA_free(sub);
 }
 
-UA_DeleteSubscriptionsResponse UA_EXPORT
-UA_Client_Subscriptions_delete(UA_Client *client, const UA_DeleteSubscriptionsRequest request) {
-    UA_STACKARRAY(UA_Client_Subscription*, subs, request.subscriptionIdsSize);
-    memset(subs, 0, sizeof(void*) * request.subscriptionIdsSize);
+typedef struct {
+    UA_DeleteSubscriptionsRequest *request;
+    UA_Client_Subscription **subs;
+} Subscriptions_DeleteData;
 
+static void
+__Subscriptions_DeleteData_free(Subscriptions_DeleteData *data) {
+    if(!data)
+        return;
+    if(data->subs)
+        UA_free(data->subs);
+    if(data->request)
+        UA_delete(data->request, &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSREQUEST]);
+    UA_free(data);
+}
+
+static UA_INLINE void
+__Subscriptions_delete_prepare(UA_Client *client, Subscriptions_DeleteData *data) {
     /* temporary remove the subscriptions from the list */
-    for(size_t i = 0; i < request.subscriptionIdsSize; i++) {
-        subs[i] = findSubscription(client, request.subscriptionIds[i]);
-        if (subs[i])
-            LIST_REMOVE(subs[i], listEntry);
+    for(size_t i = 0; i < data->request->subscriptionIdsSize; i++) {
+        data->subs[i] = findSubscription(client, data->request->subscriptionIds[i]);
+        if(data->subs[i])
+            LIST_REMOVE(data->subs[i], listEntry);
     }
+}
 
-    /* Send the request */
-    UA_DeleteSubscriptionsResponse response;
-    __UA_Client_Service(client,
-                        &request, &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSREQUEST],
-                        &response, &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSRESPONSE]);
-    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD)
+static void
+__Subscriptions_delete_handler(UA_Client *client, void *data, UA_UInt32 requestId,
+                               void *r) {
+    UA_DeleteSubscriptionsResponse *response = (UA_DeleteSubscriptionsResponse *)r;
+    CustomCallback *cc = (CustomCallback *)data;
+    Subscriptions_DeleteData *delData = (Subscriptions_DeleteData *)cc->clientData;
+    UA_DeleteSubscriptionsRequest *request = delData->request;
+    UA_Client_Subscription **subs = delData->subs;
+
+    if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD)
         goto cleanup;
 
-    if(request.subscriptionIdsSize != response.resultsSize) {
-        response.responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
+    if(request->subscriptionIdsSize != response->resultsSize) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
         goto cleanup;
     }
 
     /* Loop over the removed subscriptions and remove internally */
-    for(size_t i = 0; i < request.subscriptionIdsSize; i++) {
-        if(response.results[i] != UA_STATUSCODE_GOOD &&
-           response.results[i] != UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID) {
+    for(size_t i = 0; i < request->subscriptionIdsSize; i++) {
+        if(response->results[i] != UA_STATUSCODE_GOOD &&
+           response->results[i] != UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID) {
             /* Something was wrong, reinsert the subscription in the list */
             if (subs[i])
                 LIST_INSERT_HEAD(&client->subscriptions, subs[i], listEntry);
@@ -150,26 +285,100 @@ UA_Client_Subscriptions_delete(UA_Client *client, const UA_DeleteSubscriptionsRe
         if(!subs[i]) {
             UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT,
                         "No internal representation of subscription %u",
-                        request.subscriptionIds[i]);
+                        delData->request->subscriptionIds[i]);
             continue;
         }
-        
+
         LIST_INSERT_HEAD(&client->subscriptions, subs[i], listEntry);
         UA_Client_Subscription_deleteInternal(client, subs[i]);
     }
 
-    return response;
-
 cleanup:
-    for(size_t i = 0; i < request.subscriptionIdsSize; i++) {
-        if (subs[i]) {
-            LIST_INSERT_HEAD(&client->subscriptions, subs[i], listEntry);
+    if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+        for(size_t i = 0; i < request->subscriptionIdsSize; i++) {
+            if(subs[i]) {
+                LIST_INSERT_HEAD(&client->subscriptions, subs[i], listEntry);
+            }
         }
     }
+
+    if(cc->isAsync) {
+        if(cc->userCallback)
+            cc->userCallback(client, cc->userData, requestId, response);
+        __Subscriptions_DeleteData_free(delData);
+        UA_free(cc);
+    }
+}
+
+UA_StatusCode
+UA_Client_Subscriptions_delete_async(UA_Client *client,
+                                     const UA_DeleteSubscriptionsRequest request,
+                                     UA_ClientAsyncServiceCallback callback,
+                                     void *userdata, UA_UInt32 *requestId) {
+    CustomCallback *cc = (CustomCallback *)UA_calloc(1, sizeof(CustomCallback));
+    if(!cc)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+
+    Subscriptions_DeleteData *data =
+        (Subscriptions_DeleteData *)UA_calloc(1, sizeof(Subscriptions_DeleteData));
+    if(cc->clientData)
+        goto cleanup;
+    cc->clientData = data;
+    data->subs = (UA_Client_Subscription **)UA_calloc(request.subscriptionIdsSize,
+                                                      sizeof(UA_Client_Subscription *));
+    if(!data->subs)
+        goto cleanup;
+
+    data->request = UA_DeleteSubscriptionsRequest_new();
+    if(!data->request)
+        goto cleanup;
+    UA_DeleteSubscriptionsRequest_copy(&request, data->request);
+
+    __Subscriptions_delete_prepare(client, data);
+    cc->isAsync = true;
+    cc->userCallback = callback;
+    cc->userData = userdata;
+
+    return __UA_Client_AsyncService(
+        client, &request, &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSREQUEST],
+        __Subscriptions_delete_handler, &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSRESPONSE],
+        cc, requestId);
+cleanup:
+    __Subscriptions_DeleteData_free(data);
+    UA_free(cc);
+    return UA_STATUSCODE_BADOUTOFMEMORY;
+}
+
+UA_DeleteSubscriptionsResponse
+UA_Client_Subscriptions_delete(UA_Client *client,
+                               const UA_DeleteSubscriptionsRequest request) {
+    UA_STACKARRAY(UA_Client_Subscription *, subs, request.subscriptionIdsSize);
+    memset(subs, 0, sizeof(void *) * request.subscriptionIdsSize);
+
+    CustomCallback cc;
+    memset(&cc, 0, sizeof(CustomCallback));
+#ifdef __clang_analyzer__
+    cc.isAsync = false;
+#endif
+
+    Subscriptions_DeleteData data;
+    cc.clientData = &data;
+    data.request = (UA_DeleteSubscriptionsRequest *)(uintptr_t)&request;
+    data.subs = subs;
+
+    __Subscriptions_delete_prepare(client, &data);
+
+    /* Send the request */
+    UA_DeleteSubscriptionsResponse response;
+
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_DELETESUBSCRIPTIONSRESPONSE]);
+
+    __Subscriptions_delete_handler(client, &cc, 0, &response);
     return response;
 }
 
-UA_StatusCode UA_EXPORT
+UA_StatusCode
 UA_Client_Subscriptions_deleteSingle(UA_Client *client, UA_UInt32 subscriptionId) {
     UA_DeleteSubscriptionsRequest request;
     UA_DeleteSubscriptionsRequest_init(&request);
@@ -210,58 +419,85 @@ UA_Client_MonitoredItem_remove(UA_Client *client, UA_Client_Subscription *sub,
     UA_free(mon);
 }
 
-static void
-__UA_Client_MonitoredItems_create(UA_Client *client,
-                                  const UA_CreateMonitoredItemsRequest *request,
-                                  void **contexts, void **handlingCallbacks,
-                                  UA_Client_DeleteMonitoredItemCallback *deleteCallbacks,
-                                  UA_CreateMonitoredItemsResponse *response) {
-    UA_CreateMonitoredItemsResponse_init(response);
+typedef struct {
+    UA_Client_Subscription *sub;
 
-    if (!request->itemsToCreateSize) {
-        response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
+    UA_Client_MonitoredItem **mis;
+    void **contexts;
+    UA_Client_DeleteMonitoredItemCallback *deleteCallbacks;
+    void **handlingCallbacks;
+
+    UA_CreateMonitoredItemsRequest *request;
+} MonitoredItems_CreateData;
+
+static void
+MonitoredItems_CreateData_deleteItems(MonitoredItems_CreateData *data,
+                                      UA_Client *client) {
+    if(!data)
         return;
-    }
 
-    /* Fix clang warning */
-    size_t itemsToCreateSize = request->itemsToCreateSize;
-    UA_Client_Subscription *sub = NULL;
-    
-    /* Allocate the memory for internal representations */
-    UA_STACKARRAY(UA_Client_MonitoredItem*, mis, itemsToCreateSize);
-    memset(mis, 0, sizeof(void*) * itemsToCreateSize);
-    for(size_t i = 0; i < itemsToCreateSize; i++) {
-        mis[i] = (UA_Client_MonitoredItem*)UA_malloc(sizeof(UA_Client_MonitoredItem));
-        if(!mis[i]) {
-            response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
-            goto cleanup;
+#ifdef __clang_analyzer__
+    /* The clang analyzer requires the information that the loop below is executed
+       which is already checked in the __UA_Client_MonitoredItems_create */
+    assert(data->request->itemsToCreateSize);
+#endif
+
+    bool hasCallbacks = data->deleteCallbacks != NULL && data->contexts != NULL;
+    if(data->request && data->mis) {
+        for(size_t i = 0; i < data->request->itemsToCreateSize; i++) {
+            if(data->mis[i]) {
+                if(hasCallbacks && data->deleteCallbacks[i]) {
+                    if(data->sub)
+                        data->deleteCallbacks[i](client, data->sub->subscriptionId,
+                                                 data->sub->context, 0,
+                                                 data->contexts[i]);
+                    else
+                        data->deleteCallbacks[i](client, 0, NULL, 0, data->contexts[i]);
+                }
+                UA_free(data->mis[i]);
+            }
         }
     }
+}
 
-    /* Get the subscription */
-    sub = findSubscription(client, request->subscriptionId);
-    if(!sub) {
-        response->responseHeader.serviceResult = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
-        goto cleanup;
-    }
+static void
+MonitoredItems_CreateData_free(MonitoredItems_CreateData *data) {
+    if(!data)
+        return;
+    /* contains contexts, deleteCallbacs, handlingCallbacks as well */
+    if(data->mis)
+        UA_free(data->mis);
+    if(data->request)
+        UA_CreateMonitoredItemsRequest_delete(data->request);
+    UA_free(data);
+}
 
-    /* Set the clientHandle */
-    for(size_t i = 0; i < itemsToCreateSize; i++)
-        request->itemsToCreate[i].requestedParameters.clientHandle = ++(client->monitoredItemHandles);
+static void
+__MonitoredItems_create_handler(UA_Client *client, void *d, UA_UInt32 requestId,
+                                void *r) {
+    UA_CreateMonitoredItemsResponse *response = (UA_CreateMonitoredItemsResponse *)r;
+    CustomCallback *cc = (CustomCallback *)d;
+    MonitoredItems_CreateData *data = (MonitoredItems_CreateData *)cc->clientData;
+
+    // introduce local pointers to the variables/parameters in the CreateData
+    // to keep the code completely intact
+    UA_CreateMonitoredItemsRequest *request = data->request;
+    UA_Client_DeleteMonitoredItemCallback *deleteCallbacks = data->deleteCallbacks;
+    UA_Client_Subscription *sub = data->sub;
+    void **contexts = data->contexts;
+    UA_Client_MonitoredItem **mis = data->mis;
+    void **handlingCallbacks = data->handlingCallbacks;
 
-    /* Call the service */
-    __UA_Client_Service(client, request, &UA_TYPES[UA_TYPES_CREATEMONITOREDITEMSREQUEST],
-                        response, &UA_TYPES[UA_TYPES_CREATEMONITOREDITEMSRESPONSE]);
     if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD)
         goto cleanup;
 
-    if(response->resultsSize != itemsToCreateSize) {
+    if(response->resultsSize != request->itemsToCreateSize) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
         goto cleanup;
     }
 
     /* Add internally */
-    for(size_t i = 0; i < itemsToCreateSize; i++) {
+    for(size_t i = 0; i < request->itemsToCreateSize; i++) {
         if(response->results[i].statusCode != UA_STATUSCODE_GOOD) {
             if (deleteCallbacks[i])
                 deleteCallbacks[i](client, sub->subscriptionId, sub->context, 0, contexts[i]);
@@ -269,7 +505,7 @@ __UA_Client_MonitoredItems_create(UA_Client *client,
             mis[i] = NULL;
             continue;
         }
-            
+
         UA_Client_MonitoredItem *newMon = mis[i];
         newMon->clientHandle = request->itemsToCreate[i].requestedParameters.clientHandle;
         newMon->monitoredItemId = response->results[i].monitoredItemId;
@@ -284,24 +520,166 @@ __UA_Client_MonitoredItems_create(UA_Client *client,
         UA_LOG_DEBUG(&client->config.logger, UA_LOGCATEGORY_CLIENT,
                     "Subscription %u | Added a MonitoredItem with handle %u",
                      sub->subscriptionId, newMon->clientHandle);
+        mis[i] = NULL;
+    }
+cleanup:
+    MonitoredItems_CreateData_deleteItems(data, client);
+    if(cc->isAsync) {
+        if(cc->userCallback)
+            cc->userCallback(client, cc->userData, requestId, response);
+        MonitoredItems_CreateData_free(data);
+        UA_free(cc);
     }
+}
+
+static UA_StatusCode
+MonitoredItems_CreateData_prepare(MonitoredItems_CreateData *data, UA_Client *client) {
+    /* Allocate the memory for internal representations */
+    for(size_t i = 0; i < data->request->itemsToCreateSize; i++) {
+        data->mis[i] =
+            (UA_Client_MonitoredItem *)UA_malloc(sizeof(UA_Client_MonitoredItem));
+        if(!data->mis[i]) {
+            return UA_STATUSCODE_BADOUTOFMEMORY;
+        }
+    }
+
+    /* Set the clientHandle */
+    for(size_t i = 0; i < data->request->itemsToCreateSize; i++)
+        data->request->itemsToCreate[i].requestedParameters.clientHandle =
+            ++(client->monitoredItemHandles);
+
+    return UA_STATUSCODE_GOOD;
+}
+
+static void
+__UA_Client_MonitoredItems_create(UA_Client *client,
+                                  const UA_CreateMonitoredItemsRequest *request,
+                                  void **contexts, void **handlingCallbacks,
+                                  UA_Client_DeleteMonitoredItemCallback *deleteCallbacks,
+                                  UA_CreateMonitoredItemsResponse *response) {
+    UA_CreateMonitoredItemsResponse_init(response);
+
+    if(!request->itemsToCreateSize) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR;
+        return;
+    }
+    CustomCallback cc;
+    memset(&cc, 0, sizeof(CustomCallback));
+#ifdef __clang_analyzer__
+    cc.isAsync = false;
+#endif
+
+    /* Fix clang warning */
+    size_t itemsToCreateSize = request->itemsToCreateSize;
+    UA_STACKARRAY(UA_Client_MonitoredItem *, mis, itemsToCreateSize);
+    memset(mis, 0, sizeof(void *) * itemsToCreateSize);
+
+    MonitoredItems_CreateData data;
+    memset(&data, 0, sizeof(MonitoredItems_CreateData));
+    data.request = (UA_CreateMonitoredItemsRequest *)(uintptr_t)request;
+    data.contexts = contexts;
+    data.handlingCallbacks = handlingCallbacks;
+    data.deleteCallbacks = deleteCallbacks;
+    data.mis = mis;
+
+    cc.clientData = &data;
+
+    /* Get the subscription */
+    data.sub = findSubscription(client, request->subscriptionId);
+    if(!data.sub) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
+        goto cleanup;
+    }
+
+    UA_StatusCode retval = MonitoredItems_CreateData_prepare(&data, client);
+    if(retval != UA_STATUSCODE_GOOD) {
+        response->responseHeader.serviceResult = retval;
+        goto cleanup;
+    }
+
+    /* Call the service */
+    __UA_Client_Service(client, request, &UA_TYPES[UA_TYPES_CREATEMONITOREDITEMSREQUEST],
+                        response, &UA_TYPES[UA_TYPES_CREATEMONITOREDITEMSRESPONSE]);
 
+    __MonitoredItems_create_handler(client, &cc, 0, response);
     return;
+cleanup:
+    MonitoredItems_CreateData_deleteItems(&data, client);
+}
 
- cleanup:
-    for(size_t i = 0; i < itemsToCreateSize; i++) {
-        if (deleteCallbacks[i]) {
-            if (sub)
-                deleteCallbacks[i](client, sub->subscriptionId, sub->context, 0, contexts[i]);
-            else
-                deleteCallbacks[i](client, 0, NULL, 0, contexts[i]);
-        }
-        if(mis[i])
-            UA_free(mis[i]);
+static UA_StatusCode
+__UA_Client_MonitoredItems_createDataChanges_async(
+    UA_Client *client, const UA_CreateMonitoredItemsRequest request, void **contexts,
+    void **callbacks, UA_Client_DeleteMonitoredItemCallback *deleteCallbacks,
+    UA_ClientAsyncServiceCallback createCallback, void *userdata, UA_UInt32 *requestId) {
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+    CustomCallback *cc = (CustomCallback *)UA_calloc(1, sizeof(CustomCallback));
+    if(!cc)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+
+    cc->userCallback = createCallback;
+    cc->userData = userdata;
+    MonitoredItems_CreateData *data =
+        (MonitoredItems_CreateData *)UA_calloc(1, sizeof(MonitoredItems_CreateData));
+    if(!data) {
+        retval = UA_STATUSCODE_BADOUTOFMEMORY;
+        goto cleanup;
+    }
+    cc->isAsync = true;
+    cc->clientData = data;
+
+    data->sub = findSubscription(client, request.subscriptionId);
+    if(!data->sub) {
+        retval = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
+        goto cleanup;
+    }
+
+    /* create a big array that holds the monitored items and parameters */
+    void **array = (void **)UA_calloc(4 * request.itemsToCreateSize, sizeof(void *));
+    if(!array) {
+        retval = UA_STATUSCODE_BADOUTOFMEMORY;
+        goto cleanup;
     }
+    data->mis = (UA_Client_MonitoredItem **)array;
+    data->contexts =
+        (void **)((uintptr_t)array + (sizeof(void *) * request.itemsToCreateSize));
+    memcpy(data->contexts, contexts, request.itemsToCreateSize * sizeof(void *));
+    data->deleteCallbacks =
+        (UA_Client_DeleteMonitoredItemCallback *)((uintptr_t)array +
+                                                  (2 * request.itemsToCreateSize *
+                                                   sizeof(void *)));
+    memcpy(data->deleteCallbacks, deleteCallbacks,
+           request.itemsToCreateSize * sizeof(UA_Client_DeleteMonitoredItemCallback));
+    data->handlingCallbacks =
+        (void **)((uintptr_t)array + (3 * request.itemsToCreateSize * sizeof(void *)));
+    memcpy(data->handlingCallbacks, callbacks,
+           request.itemsToCreateSize * sizeof(void *));
+
+    data->request = UA_CreateMonitoredItemsRequest_new();
+    if(!data->request) {
+        retval = UA_STATUSCODE_BADOUTOFMEMORY;
+        goto cleanup;
+    }
+    retval = UA_CreateMonitoredItemsRequest_copy(&request, data->request);
+    if(retval != UA_STATUSCODE_GOOD)
+        goto cleanup;
+
+    retval = MonitoredItems_CreateData_prepare(data, client);
+    if(retval != UA_STATUSCODE_GOOD)
+        goto cleanup;
+
+    return __UA_Client_AsyncService(
+        client, data->request, &UA_TYPES[UA_TYPES_CREATEMONITOREDITEMSREQUEST],
+        __MonitoredItems_create_handler, &UA_TYPES[UA_TYPES_CREATEMONITOREDITEMSRESPONSE],
+        cc, requestId);
+cleanup:
+    MonitoredItems_CreateData_deleteItems(data, client);
+    MonitoredItems_CreateData_free(data);
+    UA_free(cc);
+    return retval;
 }
 
-UA_CreateMonitoredItemsResponse UA_EXPORT
+UA_CreateMonitoredItemsResponse
 UA_Client_MonitoredItems_createDataChanges(UA_Client *client,
             const UA_CreateMonitoredItemsRequest request, void **contexts,
             UA_Client_DataChangeNotificationCallback *callbacks,
@@ -312,7 +690,18 @@ UA_Client_MonitoredItems_createDataChanges(UA_Client *client,
     return response;
 }
 
-UA_MonitoredItemCreateResult UA_EXPORT
+UA_StatusCode
+UA_Client_MonitoredItems_createDataChanges_async(
+    UA_Client *client, const UA_CreateMonitoredItemsRequest request, void **contexts,
+    UA_Client_DataChangeNotificationCallback *callbacks,
+    UA_Client_DeleteMonitoredItemCallback *deleteCallbacks,
+    UA_ClientAsyncServiceCallback createCallback, void *userdata, UA_UInt32 *requestId) {
+    return __UA_Client_MonitoredItems_createDataChanges_async(
+        client, request, contexts, (void **)(uintptr_t)callbacks, deleteCallbacks,
+        createCallback, userdata, requestId);
+}
+
+UA_MonitoredItemCreateResult
 UA_Client_MonitoredItems_createDataChange(UA_Client *client, UA_UInt32 subscriptionId,
           UA_TimestampsToReturn timestampsToReturn, const UA_MonitoredItemCreateRequest item,
           void *context, UA_Client_DataChangeNotificationCallback callback,
@@ -341,7 +730,7 @@ UA_Client_MonitoredItems_createDataChange(UA_Client *client, UA_UInt32 subscript
     return result;
 }
 
-UA_CreateMonitoredItemsResponse UA_EXPORT
+UA_CreateMonitoredItemsResponse
 UA_Client_MonitoredItems_createEvents(UA_Client *client,
             const UA_CreateMonitoredItemsRequest request, void **contexts,
             UA_Client_EventNotificationCallback *callback,
@@ -352,7 +741,19 @@ UA_Client_MonitoredItems_createEvents(UA_Client *client,
     return response;
 }
 
-UA_MonitoredItemCreateResult UA_EXPORT
+/* Monitor the EventNotifier attribute only */
+UA_StatusCode
+UA_Client_MonitoredItems_createEvents_async(
+    UA_Client *client, const UA_CreateMonitoredItemsRequest request, void **contexts,
+    UA_Client_EventNotificationCallback *callbacks,
+    UA_Client_DeleteMonitoredItemCallback *deleteCallbacks,
+    UA_ClientAsyncServiceCallback createCallback, void *userdata, UA_UInt32 *requestId) {
+    return __UA_Client_MonitoredItems_createDataChanges_async(
+        client, request, contexts, (void **)(uintptr_t)callbacks, deleteCallbacks,
+        createCallback, userdata, requestId);
+}
+
+UA_MonitoredItemCreateResult
 UA_Client_MonitoredItems_createEvent(UA_Client *client, UA_UInt32 subscriptionId,
           UA_TimestampsToReturn timestampsToReturn, const UA_MonitoredItemCreateRequest item,
           void *context, UA_Client_EventNotificationCallback callback,
@@ -379,27 +780,28 @@ UA_Client_MonitoredItems_createEvent(UA_Client *client, UA_UInt32 subscriptionId
     return result;
 }
 
-UA_DeleteMonitoredItemsResponse UA_EXPORT
-UA_Client_MonitoredItems_delete(UA_Client *client, const UA_DeleteMonitoredItemsRequest request) {
-    /* Send the request */
-    UA_DeleteMonitoredItemsResponse response;
-    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_DELETEMONITOREDITEMSREQUEST],
-                        &response, &UA_TYPES[UA_TYPES_DELETEMONITOREDITEMSRESPONSE]);
-    if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD)
-        return response;
+static void
+__MonitoredItems_delete_handler(UA_Client *client, void *d, UA_UInt32 requestId,
+                                void *r) {
+    UA_DeleteMonitoredItemsResponse *response = (UA_DeleteMonitoredItemsResponse *)r;
+    CustomCallback *cc = (CustomCallback *)d;
+    UA_DeleteMonitoredItemsRequest *request =
+        (UA_DeleteMonitoredItemsRequest *)cc->clientData;
+    if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD)
+        goto cleanup;
 
-    UA_Client_Subscription *sub = findSubscription(client, request.subscriptionId);
+    UA_Client_Subscription *sub = findSubscription(client, request->subscriptionId);
     if(!sub) {
         UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT,
                     "No internal representation of subscription %u",
-                    request.subscriptionId);
-        return response;
+                    request->subscriptionId);
+        goto cleanup;
     }
 
     /* Loop over deleted MonitoredItems */
-    for(size_t i = 0; i < response.resultsSize; i++) {
-        if(response.results[i] != UA_STATUSCODE_GOOD &&
-           response.results[i] != UA_STATUSCODE_BADMONITOREDITEMIDINVALID) {
+    for(size_t i = 0; i < response->resultsSize; i++) {
+        if(response->results[i] != UA_STATUSCODE_GOOD &&
+           response->results[i] != UA_STATUSCODE_BADMONITOREDITEMIDINVALID) {
             continue;
         }
 
@@ -408,19 +810,71 @@ UA_Client_MonitoredItems_delete(UA_Client *client, const UA_DeleteMonitoredItems
         UA_Client_MonitoredItem *mon;
         LIST_FOREACH(mon, &sub->monitoredItems, listEntry) {
             // NOLINTNEXTLINE
-            if (mon->monitoredItemId == request.monitoredItemIds[i]) {
+            if(mon->monitoredItemId == request->monitoredItemIds[i]) {
                 UA_Client_MonitoredItem_remove(client, sub, mon);
                 break;
             }
         }
 #endif
     }
+cleanup:
+    if(cc->isAsync) {
+        if(cc->userCallback)
+            cc->userCallback(client, cc->userData, requestId, response);
+        UA_DeleteMonitoredItemsRequest_delete(request);
+        UA_free(cc);
+    }
+}
+
+UA_DeleteMonitoredItemsResponse
+UA_Client_MonitoredItems_delete(UA_Client *client,
+                                const UA_DeleteMonitoredItemsRequest request) {
+    /* Send the request */
+    UA_DeleteMonitoredItemsResponse response;
+    CustomCallback cc;
+    memset(&cc, 0, sizeof(CustomCallback));
+#ifdef __clang_analyzer__
+    cc.isAsync = false;
+#endif
+    cc.clientData = (void *)(uintptr_t)&request;
+
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_DELETEMONITOREDITEMSREQUEST],
+                        &response, &UA_TYPES[UA_TYPES_DELETEMONITOREDITEMSRESPONSE]);
 
+    __MonitoredItems_delete_handler(client, &cc, 0, &response);
     return response;
 }
 
-UA_StatusCode UA_EXPORT
-UA_Client_MonitoredItems_deleteSingle(UA_Client *client, UA_UInt32 subscriptionId, UA_UInt32 monitoredItemId) {
+UA_StatusCode
+UA_Client_MonitoredItems_delete_async(UA_Client *client,
+                                      const UA_DeleteMonitoredItemsRequest request,
+                                      UA_ClientAsyncServiceCallback callback,
+                                      void *userdata, UA_UInt32 *requestId) {
+    /* Send the request */
+    CustomCallback *cc = (CustomCallback *)UA_calloc(1, sizeof(CustomCallback));
+    if(!cc)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+
+    UA_DeleteMonitoredItemsRequest *req_copy = UA_DeleteMonitoredItemsRequest_new();
+    if(!req_copy) {
+        UA_free(cc);
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+    }
+    UA_DeleteMonitoredItemsRequest_copy(&request, req_copy);
+    cc->isAsync = true;
+    cc->clientData = req_copy;
+    cc->userCallback = callback;
+    cc->userData = userdata;
+
+    return __UA_Client_AsyncService(
+        client, &request, &UA_TYPES[UA_TYPES_DELETEMONITOREDITEMSREQUEST],
+        __MonitoredItems_delete_handler, &UA_TYPES[UA_TYPES_DELETEMONITOREDITEMSRESPONSE],
+        cc, requestId);
+}
+
+UA_StatusCode
+UA_Client_MonitoredItems_deleteSingle(UA_Client *client, UA_UInt32 subscriptionId,
+                                      UA_UInt32 monitoredItemId) {
     UA_DeleteMonitoredItemsRequest request;
     UA_DeleteMonitoredItemsRequest_init(&request);
     request.subscriptionId = subscriptionId;

+ 291 - 0
tests/client/check_client_subscriptions.c

@@ -58,6 +58,41 @@ dataChangeHandler(UA_Client *client, UA_UInt32 subId, void *subContext,
     countNotificationReceived++;
 }
 
+static void
+createSubscriptionCallback(UA_Client *client, void *userdata, UA_UInt32 requestId,
+                           void *r) {
+    UA_CreateSubscriptionResponse_copy((const UA_CreateSubscriptionResponse *)r,
+                                       (UA_CreateSubscriptionResponse *)userdata);
+}
+
+static void
+modifySubscriptionCallback(UA_Client *client, void *userdata, UA_UInt32 requestId,
+                           void *r) {
+    UA_ModifySubscriptionResponse_copy((const UA_ModifySubscriptionResponse *)r,
+                                       (UA_ModifySubscriptionResponse *)userdata);
+}
+
+static void
+createDataChangesCallback(UA_Client *client, void *userdata, UA_UInt32 requestId,
+                          void *r) {
+    UA_CreateMonitoredItemsResponse_copy((const UA_CreateMonitoredItemsResponse *)r,
+                                         (UA_CreateMonitoredItemsResponse *)userdata);
+}
+
+static void
+deleteMonitoredItemsCallback(UA_Client *client, void *userdata, UA_UInt32 requestId,
+                             void *r) {
+    UA_DeleteMonitoredItemsResponse_copy((const UA_DeleteMonitoredItemsResponse *)r,
+                                         (UA_DeleteMonitoredItemsResponse *)userdata);
+}
+
+static void
+deleteSubscriptionsCallback(UA_Client *client, void *userdata, UA_UInt32 requestId,
+                            void *r) {
+    UA_DeleteSubscriptionsResponse_copy((const UA_DeleteSubscriptionsResponse *)r,
+                                        (UA_DeleteSubscriptionsResponse *)userdata);
+}
+
 START_TEST(Client_subscription) {
     UA_Client *client = UA_Client_new();
     UA_ClientConfig_setDefault(UA_Client_getConfig(client));
@@ -119,6 +154,136 @@ START_TEST(Client_subscription) {
 }
 END_TEST
 
+START_TEST(Client_subscription_async) {
+    UA_Client *client = UA_Client_new();
+    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
+
+    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_Client_recv = client->connection.recv;
+    client->connection.recv = UA_Client_recvTesting;
+
+    UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
+
+    UA_UInt32 requestId = 0;
+    UA_CreateSubscriptionResponse response;
+    retval = UA_Client_Subscriptions_create_async(client, request, NULL, NULL, NULL,
+                                                  createSubscriptionCallback, &response,
+                                                  &requestId);
+
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_realSleep(50);  // wait until response is
+    UA_Client_run_iterate(client, 0);
+
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    UA_UInt32 subId = response.subscriptionId;
+
+    UA_ModifySubscriptionRequest modifySubscriptionRequest;
+    UA_ModifySubscriptionRequest_init(&modifySubscriptionRequest);
+    modifySubscriptionRequest.subscriptionId = response.subscriptionId;
+    modifySubscriptionRequest.requestedPublishingInterval =
+        response.revisedPublishingInterval;
+    modifySubscriptionRequest.requestedLifetimeCount = response.revisedLifetimeCount;
+    modifySubscriptionRequest.requestedMaxKeepAliveCount =
+        response.revisedMaxKeepAliveCount;
+    UA_ModifySubscriptionResponse modifySubscriptionResponse;
+
+    retval = UA_Client_Subscriptions_modify_async(
+        client, modifySubscriptionRequest, modifySubscriptionCallback,
+        &modifySubscriptionResponse, &requestId);
+
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_realSleep(50);  // need to wait until response is at the client
+    retval = UA_Client_run_iterate(client, 0);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(modifySubscriptionResponse.responseHeader.serviceResult,
+                     UA_STATUSCODE_GOOD);
+
+    /* monitor the server state */
+    UA_MonitoredItemCreateRequest singleMonRequest =
+        UA_MonitoredItemCreateRequest_default(
+            UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
+    void *contexts = NULL;
+    UA_Client_DataChangeNotificationCallback notifications = dataChangeHandler;
+    UA_Client_DeleteMonitoredItemCallback deleteCallbacks = NULL;
+
+    UA_CreateMonitoredItemsRequest monRequest;
+    UA_CreateMonitoredItemsRequest_init(&monRequest);
+    monRequest.subscriptionId = subId;
+    monRequest.itemsToCreate = &singleMonRequest;
+    monRequest.itemsToCreateSize = 1;
+    UA_CreateMonitoredItemsResponse monResponse;
+    retval = UA_Client_MonitoredItems_createDataChanges_async(
+        client, monRequest, &contexts, &notifications, &deleteCallbacks,
+        createDataChangesCallback, &monResponse, &requestId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_realSleep(50);  // need to wait until response is at the client
+    retval = UA_Client_run_iterate(client, 0);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    ck_assert_uint_eq(monResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(monResponse.resultsSize, 1);
+    ck_assert_uint_eq(monResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
+    UA_UInt32 monId = monResponse.results[0].monitoredItemId;
+
+    UA_fakeSleep((UA_UInt32)publishingInterval + 1);
+    notificationReceived = false;
+
+    retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
+
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+
+    UA_DeleteMonitoredItemsRequest monDeleteRequest;
+    UA_DeleteMonitoredItemsRequest_init(&monDeleteRequest);
+    monDeleteRequest.subscriptionId = subId;
+    monDeleteRequest.monitoredItemIds = &monId;
+    monDeleteRequest.monitoredItemIdsSize = 1;
+    UA_DeleteMonitoredItemsResponse monDeleteResponse;
+
+    retval = UA_Client_MonitoredItems_delete_async(client, monDeleteRequest,
+                                                   deleteMonitoredItemsCallback,
+                                                   &monDeleteResponse, &requestId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_realSleep(50);  // need to wait until response is at the client
+    retval = UA_Client_run_iterate(client, 0);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    ck_assert_uint_eq(monDeleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(monDeleteResponse.resultsSize, 1);
+    ck_assert_uint_eq(monDeleteResponse.results[0], UA_STATUSCODE_GOOD);
+
+    UA_DeleteSubscriptionsRequest subDeleteRequest;
+    UA_DeleteSubscriptionsRequest_init(&subDeleteRequest);
+    subDeleteRequest.subscriptionIds = &monId;
+    subDeleteRequest.subscriptionIdsSize = 1;
+    UA_DeleteSubscriptionsResponse subDeleteResponse;
+    printf("will delete\n");
+    retval = UA_Client_Subscriptions_delete_async(client, subDeleteRequest,
+                                                  deleteSubscriptionsCallback,
+                                                  &subDeleteResponse, &requestId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_realSleep(50);  // need to wait until response is at the client
+    retval = UA_Client_run_iterate(client, 0);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    ck_assert_uint_eq(subDeleteResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(subDeleteResponse.resultsSize, 1);
+    ck_assert_uint_eq(subDeleteResponse.results[0], UA_STATUSCODE_GOOD);
+
+    UA_CreateSubscriptionResponse_deleteMembers(&response);
+    UA_ModifySubscriptionResponse_deleteMembers(&modifySubscriptionResponse);
+    UA_CreateMonitoredItemsResponse_deleteMembers(&monResponse);
+    UA_DeleteMonitoredItemsResponse_deleteMembers(&monDeleteResponse);
+    UA_DeleteSubscriptionsResponse_deleteMembers(&subDeleteResponse);
+
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+}
+END_TEST
+
 START_TEST(Client_subscription_createDataChanges) {
     UA_Client *client = UA_Client_new();
     UA_ClientConfig_setDefault(UA_Client_getConfig(client));
@@ -220,6 +385,130 @@ START_TEST(Client_subscription_createDataChanges) {
 }
 END_TEST
 
+START_TEST(Client_subscription_createDataChanges_async) {
+    UA_UInt32 reqId = 0;
+    UA_Client *client = UA_Client_new();
+    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
+    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_Client_recv = client->connection.recv;
+    client->connection.recv = UA_Client_recvTesting;
+
+    // Async subscription creation is tested in Client_subscription_async
+    // simplify test case using synchronous here
+    UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
+    UA_CreateSubscriptionResponse response =
+        UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL);
+    ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    UA_UInt32 subId = response.subscriptionId;
+
+    UA_MonitoredItemCreateRequest items[3];
+    UA_UInt32 newMonitoredItemIds[3];
+    UA_Client_DataChangeNotificationCallback callbacks[3];
+    UA_Client_DeleteMonitoredItemCallback deleteCallbacks[3];
+    void *contexts[3];
+
+    /* monitor the server state */
+    items[0] = UA_MonitoredItemCreateRequest_default(
+        UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
+    callbacks[0] = dataChangeHandler;
+    contexts[0] = NULL;
+    deleteCallbacks[0] = NULL;
+
+    /* monitor invalid node */
+    items[1] = UA_MonitoredItemCreateRequest_default(UA_NODEID_NUMERIC(0, 999999));
+    callbacks[1] = dataChangeHandler;
+    contexts[1] = NULL;
+    deleteCallbacks[1] = NULL;
+
+    /* monitor current time */
+    items[2] = UA_MonitoredItemCreateRequest_default(
+        UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME));
+    callbacks[2] = dataChangeHandler;
+    contexts[2] = NULL;
+    deleteCallbacks[2] = NULL;
+
+    UA_CreateMonitoredItemsRequest createRequest;
+    UA_CreateMonitoredItemsRequest_init(&createRequest);
+    createRequest.subscriptionId = subId;
+    createRequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
+    createRequest.itemsToCreate = items;
+    createRequest.itemsToCreateSize = 3;
+    UA_CreateMonitoredItemsResponse createResponse;
+
+    retval = UA_Client_MonitoredItems_createDataChanges_async(
+        client, createRequest, contexts, callbacks, deleteCallbacks,
+        createDataChangesCallback, &createResponse, &reqId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    UA_realSleep(50);
+    retval = UA_Client_run_iterate(client, 0);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    ck_assert_uint_eq(createResponse.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(createResponse.resultsSize, 3);
+    ck_assert_uint_eq(createResponse.results[0].statusCode, UA_STATUSCODE_GOOD);
+    newMonitoredItemIds[0] = createResponse.results[0].monitoredItemId;
+    ck_assert_uint_eq(createResponse.results[1].statusCode,
+                      UA_STATUSCODE_BADNODEIDUNKNOWN);
+    newMonitoredItemIds[1] = createResponse.results[1].monitoredItemId;
+    ck_assert_uint_eq(newMonitoredItemIds[1], 0);
+    ck_assert_uint_eq(createResponse.results[2].statusCode, UA_STATUSCODE_GOOD);
+    newMonitoredItemIds[2] = createResponse.results[2].monitoredItemId;
+    ck_assert_uint_eq(createResponse.results[2].statusCode, UA_STATUSCODE_GOOD);
+    UA_CreateMonitoredItemsResponse_deleteMembers(&createResponse);
+
+    UA_fakeSleep((UA_UInt32)publishingInterval + 1);
+
+    notificationReceived = false;
+    countNotificationReceived = 0;
+    retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 2);
+
+    notificationReceived = false;
+    retval = UA_Client_run_iterate(client, (UA_UInt16)(publishingInterval + 1));
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_uint_eq(notificationReceived, true);
+    ck_assert_uint_eq(countNotificationReceived, 3);
+
+    {
+        UA_DeleteMonitoredItemsRequest deleteRequest;
+        UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
+        deleteRequest.subscriptionId = subId;
+        deleteRequest.monitoredItemIds = newMonitoredItemIds;
+        deleteRequest.monitoredItemIdsSize = 3;
+
+        UA_DeleteMonitoredItemsResponse deleteResponse;
+        retval = UA_Client_MonitoredItems_delete_async(
+            client, deleteRequest, deleteMonitoredItemsCallback, &deleteResponse, &reqId);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+        UA_realSleep(50);  // need to wait until response is at the client
+        retval = UA_Client_run_iterate(client, 0);
+        ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+        ck_assert_uint_eq(deleteResponse.responseHeader.serviceResult,
+                          UA_STATUSCODE_GOOD);
+        ck_assert_uint_eq(deleteResponse.resultsSize, 3);
+        ck_assert_uint_eq(deleteResponse.results[0], UA_STATUSCODE_GOOD);
+        ck_assert_uint_eq(deleteResponse.results[1],
+                          UA_STATUSCODE_BADMONITOREDITEMIDINVALID);
+        ck_assert_uint_eq(deleteResponse.results[2], UA_STATUSCODE_GOOD);
+
+        UA_DeleteMonitoredItemsResponse_deleteMembers(&deleteResponse);
+    }
+
+    // Async subscription deletion is tested in Client_subscription_async
+    // simplify test case using synchronous here
+    retval = UA_Client_Subscriptions_deleteSingle(client, subId);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+}
+END_TEST
+
 START_TEST(Client_subscription_keepAlive) {
     UA_Client *client = UA_Client_new();
     UA_ClientConfig_setDefault(UA_Client_getConfig(client));
@@ -559,8 +848,10 @@ static Suite* testSuite_Client(void) {
     TCase *tc_client = tcase_create("Client Subscription Basic");
     tcase_add_checked_fixture(tc_client, setup, teardown);
     tcase_add_test(tc_client, Client_subscription);
+    tcase_add_test(tc_client, Client_subscription_async);
     tcase_add_test(tc_client, Client_subscription_connectionClose);
     tcase_add_test(tc_client, Client_subscription_createDataChanges);
+    tcase_add_test(tc_client, Client_subscription_createDataChanges_async);
     tcase_add_test(tc_client, Client_subscription_keepAlive);
     tcase_add_test(tc_client, Client_subscription_without_notification);
     tcase_add_test(tc_client, Client_subscription_async_sub);