Browse Source

test(server): Add multithreading tests

Ubuntu 5 years ago
parent
commit
be4330cc40

+ 34 - 0
tests/CMakeLists.txt

@@ -189,6 +189,40 @@ add_executable(check_server_callbacks server/check_server_callbacks.c $<TARGET_O
 target_link_libraries(check_server_callbacks ${LIBS})
 add_test_valgrind(server_callbacks ${TESTS_BINARY_DIR}/check_server_callbacks)
 
+if (UA_MULTITHREADING EQUAL 100)
+    add_executable(check_mt_addVariableNode multithreading/check_mt_addVariableNode.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+    target_link_libraries(check_mt_addVariableNode ${LIBS})
+    add_test_valgrind(mt_addVariableNode ${TESTS_BINARY_DIR}/check_mt_addVariableNode)
+
+    add_executable(check_mt_addVariableTypeNode multithreading/check_mt_addVariableTypeNode.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+    target_link_libraries(check_mt_addVariableTypeNode ${LIBS})
+    add_test_valgrind(mt_addVariableTypeNode ${TESTS_BINARY_DIR}/check_mt_addVariableTypeNode)
+
+    add_executable(check_mt_addObjectNode multithreading/check_mt_addObjectNode.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+    target_link_libraries(check_mt_addObjectNode ${LIBS})
+    add_test_valgrind(mt_addObjectNode ${TESTS_BINARY_DIR}/check_mt_addObjectNode)
+
+    add_executable(check_mt_readValueAttribute multithreading/check_mt_readValueAttribute.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+    target_link_libraries(check_mt_readValueAttribute ${LIBS})
+    add_test_valgrind(mt_readValueAttribute ${TESTS_BINARY_DIR}/check_mt_readValueAttribute)
+
+    add_executable(check_mt_writeValueAttribute multithreading/check_mt_writeValueAttribute.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+    target_link_libraries(check_mt_writeValueAttribute ${LIBS})
+    add_test_valgrind(mt_writeValueAttribute ${TESTS_BINARY_DIR}/check_mt_readWriteDelete)
+
+    add_executable(check_mt_readWriteDelete multithreading/check_mt_readWriteDelete.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+    target_link_libraries(check_mt_readWriteDelete ${LIBS})
+    add_test_valgrind(mt_readWriteDelete ${TESTS_BINARY_DIR}/check_mt_readWriteDelete)
+
+    add_executable(check_mt_readWriteDeleteCallback multithreading/check_mt_readWriteDeleteCallback.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+    target_link_libraries(check_mt_readWriteDeleteCallback ${LIBS})
+    add_test_valgrind(mt_readWriteDeleteCallback ${TESTS_BINARY_DIR}/check_mt_readWriteDeleteCallback)
+
+    add_executable(check_mt_addDeleteObject multithreading/check_mt_addDeleteObject.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+    target_link_libraries(check_mt_addDeleteObject ${LIBS})
+    add_test_valgrind(mt_addDeleteObject ${TESTS_BINARY_DIR}/check_mt_addDeleteObject)
+endif()
+
 add_executable(check_services_call server/check_services_call.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
 target_link_libraries(check_services_call ${LIBS})
 add_test_valgrind(services_call ${TESTS_BINARY_DIR}/check_services_call)

+ 114 - 0
tests/multithreading/check_mt_addDeleteObject.c

@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <open62541/server_config_default.h>
+#include <open62541/plugin/log_stdout.h>
+#include <open62541/client_config_default.h>
+#include <open62541/client_highlevel.h>
+#include <check.h>
+#include "thread_wrapper.h"
+#include "deviceObjectType.h"
+#include "mt_testing.h"
+
+#define NUMBER_OF_WORKERS 10
+#define ITERATIONS_PER_WORKER 10
+#define NUMBER_OF_CLIENTS 10
+#define ITERATIONS_PER_CLIENT 10
+
+
+static void setup(void) {
+    tc.running = true;
+    tc.server = UA_Server_new();
+    UA_ServerConfig_setDefault(UA_Server_getConfig(tc.server));
+    defineObjectTypes();
+    addPumpTypeConstructor(tc.server);
+    UA_Server_run_startup(tc.server);
+    THREAD_CREATE(server_thread, serverloop);
+}
+
+static void checkServer(void) {
+    for (size_t i = 0; i < NUMBER_OF_WORKERS * ITERATIONS_PER_WORKER; i++) {
+        char string_buf[20];
+        snprintf(string_buf, sizeof(string_buf), "Server %zu", i);
+        UA_NodeId reqNodeId = UA_NODEID_STRING(1, string_buf);
+        UA_NodeId resNodeId;
+        UA_StatusCode ret = UA_Server_readNodeId(tc.server, reqNodeId, &resNodeId);
+
+        ck_assert_int_eq(ret, UA_STATUSCODE_BADNODEIDUNKNOWN);
+    }
+}
+
+static
+void server_addObject(void* value) {
+    ThreadContext tmp = (*(ThreadContext *) value);
+    size_t offset = tmp.index * tmp.upperBound;
+    size_t number = offset + tmp.counter;
+    char string_buf[20];
+    snprintf(string_buf, sizeof(string_buf), "Server %zu", number);
+    UA_NodeId reqId = UA_NODEID_STRING(1, string_buf);
+    UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
+    oAttr.displayName = UA_LOCALIZEDTEXT("en-US", string_buf);
+
+    //Entering the constructor of the object (callback to user space) it is possible that a thread deletes the object while initialization
+    UA_StatusCode retval = UA_Server_addObjectNode(tc.server, reqId,
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                            UA_QUALIFIEDNAME(1, string_buf),
+                            pumpTypeId,
+                            oAttr, NULL, NULL);
+    ck_assert(retval == UA_STATUSCODE_GOOD || retval ==  UA_STATUSCODE_BADNODEIDUNKNOWN);
+}
+
+
+static
+void server_deleteObject(void* value){
+    ThreadContext tmp = (*(ThreadContext *) value);
+    size_t offset = tmp.index * tmp.upperBound;
+    size_t number = offset + tmp.counter;
+    char string_buf[20];
+    snprintf(string_buf, sizeof(string_buf), "Server %zu", number);
+    UA_StatusCode ret = UA_STATUSCODE_GOOD;
+    do {
+        ret = UA_Server_deleteNode(tc.server, UA_NODEID_STRING(1, string_buf), true);
+        sleep(1); //for valgrind
+    } while (ret != UA_STATUSCODE_GOOD);
+}
+
+static
+void initTest(void) {
+    initThreadContext(NUMBER_OF_WORKERS, NUMBER_OF_CLIENTS, checkServer);
+
+    for (size_t i = 0; i < tc.numberOfWorkers; i++) {
+        setThreadContext(&tc.workerContext[i], i, ITERATIONS_PER_WORKER, server_addObject);
+    }
+
+    for (size_t i = 0; i < tc.numberofClients; i++) {
+        setThreadContext(&tc.clientContext[i], i, ITERATIONS_PER_CLIENT, server_deleteObject);
+    }
+}
+
+START_TEST(addDeleteObjectTypeNodes) {
+        startMultithreading();
+    }
+END_TEST
+
+static Suite* testSuite_immutableNodes(void) {
+    Suite *s = suite_create("Multithreading");
+    TCase *valueCallback = tcase_create("Add-Delete nodes");
+    initTest();
+    tcase_add_checked_fixture(valueCallback, setup, teardown);
+    tcase_add_test(valueCallback, addDeleteObjectTypeNodes);
+    suite_add_tcase(s,valueCallback);
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_immutableNodes();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 129 - 0
tests/multithreading/check_mt_addObjectNode.c

@@ -0,0 +1,129 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <open62541/plugin/log_stdout.h>
+#include <open62541/client_config_default.h>
+#include <open62541/client_highlevel.h>
+#include <check.h>
+#include "thread_wrapper.h"
+#include "deviceObjectType.h"
+
+#define NUMBER_OF_WORKERS 10
+#define ITERATIONS_PER_WORKER 10
+#define NUMBER_OF_CLIENTS 10
+#define ITERATIONS_PER_CLIENT 10
+
+static void setup(void) {
+    tc.running = true;
+    tc.server = UA_Server_new();
+    UA_ServerConfig_setDefault(UA_Server_getConfig(tc.server));
+    defineObjectTypes();
+    addPumpTypeConstructor(tc.server);
+    UA_Server_run_startup(tc.server);
+    THREAD_CREATE(server_thread, serverloop);
+}
+
+static
+void checkServer(void) {
+    for (size_t i = 0; i < NUMBER_OF_WORKERS * ITERATIONS_PER_WORKER; i++) {
+        char string_buf[20];
+        snprintf(string_buf, sizeof(string_buf), "Server %zu", i);
+        UA_NodeId reqNodeId = UA_NODEID_STRING(1, string_buf);
+        UA_NodeId resNodeId;
+        UA_StatusCode ret = UA_Server_readNodeId(tc.server, reqNodeId, &resNodeId);
+
+        ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
+        ck_assert(UA_NodeId_equal(&reqNodeId, &resNodeId) == UA_TRUE);
+        UA_NodeId_deleteMembers(&resNodeId);
+    }
+
+    for (size_t i = 0; i < NUMBER_OF_CLIENTS * ITERATIONS_PER_CLIENT; i++) {
+        char string_buf[20];
+        snprintf(string_buf, sizeof(string_buf), "Client %zu", i);
+        UA_NodeId reqNodeId = UA_NODEID_STRING(1, string_buf);
+        UA_NodeId resNodeId;
+        UA_StatusCode ret = UA_Server_readNodeId(tc.server, reqNodeId, &resNodeId);
+
+        ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
+        ck_assert(UA_NodeId_equal(&reqNodeId, &resNodeId) == UA_TRUE);
+        UA_NodeId_deleteMembers(&resNodeId);
+    }
+}
+
+static
+void server_addObject(void* value) {
+    ThreadContext tmp = (*(ThreadContext *) value);
+    size_t offset = tmp.index * tmp.upperBound;
+    size_t number = offset + tmp.counter;
+    char string_buf[20];
+    snprintf(string_buf, sizeof(string_buf), "Server %zu", number);
+    UA_NodeId reqId = UA_NODEID_STRING(1, string_buf);
+    UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
+    oAttr.displayName = UA_LOCALIZEDTEXT("en-US", string_buf);
+    UA_StatusCode res = UA_Server_addObjectNode(tc.server, reqId,
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                                UA_QUALIFIEDNAME(1, string_buf),
+                                pumpTypeId,
+                                oAttr, NULL, NULL);
+
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
+}
+
+static
+void client_addObject(void* value){
+    ThreadContext tmp = (*(ThreadContext *) value);
+    size_t offset = tmp.index * tmp.upperBound;
+    size_t number = offset + tmp.counter;
+    char string_buf[20];
+    snprintf(string_buf, sizeof(string_buf), "Client %zu", number);
+    UA_NodeId reqId = UA_NODEID_STRING(1, string_buf);
+    UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
+    oAttr.displayName = UA_LOCALIZEDTEXT("en-US", string_buf);
+    UA_StatusCode retval = UA_Client_addObjectNode(tc.clients[tmp.index], reqId,
+                                         UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                                         UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
+                                         UA_QUALIFIEDNAME(1, string_buf), pumpTypeId, oAttr, NULL);
+
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+}
+
+static
+void initTest(void) {
+    initThreadContext(NUMBER_OF_WORKERS, NUMBER_OF_CLIENTS, checkServer);
+
+    for (size_t i = 0; i < tc.numberOfWorkers; i++) {
+        setThreadContext(&tc.workerContext[i], i, ITERATIONS_PER_WORKER, server_addObject);
+    }
+
+    for (size_t i = 0; i < tc.numberofClients; i++) {
+        setThreadContext(&tc.clientContext[i], i, ITERATIONS_PER_CLIENT, client_addObject);
+    }
+}
+
+START_TEST(addObjectTypeNodes) {
+        startMultithreading();
+    }
+END_TEST
+
+
+static Suite* testSuite_immutableNodes(void) {
+    Suite *s = suite_create("Multithreading");
+    TCase *valueCallback = tcase_create("Add object nodes");
+    initTest();
+    tcase_add_checked_fixture(valueCallback, setup, teardown);
+    tcase_add_test(valueCallback, addObjectTypeNodes);
+    suite_add_tcase(s,valueCallback);
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_immutableNodes();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 152 - 0
tests/multithreading/check_mt_addVariableNode.c

@@ -0,0 +1,152 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <open62541/plugin/log_stdout.h>
+#include <open62541/client_config_default.h>
+#include <open62541/client_highlevel.h>
+#include <check.h>
+#include "thread_wrapper.h"
+#include "mt_testing.h"
+
+#define NUMBER_OF_WORKERS 10
+#define ITERATIONS_PER_WORKER 10
+#define NUMBER_OF_CLIENTS 10
+#define ITERATIONS_PER_CLIENT 10
+
+static void setup(void) {
+    tc.running = true;
+    tc.server = UA_Server_new();
+    UA_ServerConfig_setDefault(UA_Server_getConfig(tc.server));
+    UA_Server_run_startup(tc.server);
+    THREAD_CREATE(server_thread, serverloop);
+}
+
+static
+void checkServer(void) {
+    for (size_t i = 0; i < NUMBER_OF_WORKERS * ITERATIONS_PER_WORKER; i++) {
+        UA_ReadValueId rvi;
+        UA_ReadValueId_init(&rvi);
+        char string_buf[20];
+        snprintf(string_buf, sizeof(string_buf), "Server %zu", i);
+        rvi.nodeId = UA_NODEID_STRING(1, string_buf);
+        rvi.attributeId = UA_ATTRIBUTEID_VALUE;
+
+        UA_DataValue resp = UA_Server_read(tc.server, &rvi, UA_TIMESTAMPSTORETURN_NEITHER);
+
+        ck_assert_int_eq(resp.status, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(0, resp.value.arrayLength);
+        ck_assert(&UA_TYPES[UA_TYPES_INT32] == resp.value.type);
+        ck_assert_int_eq(42, *(UA_Int32* )resp.value.data);
+        UA_DataValue_deleteMembers(&resp);
+    }
+
+    for (size_t i = 0; i < NUMBER_OF_CLIENTS * ITERATIONS_PER_CLIENT; i++) {
+        UA_ReadValueId rvi;
+        UA_ReadValueId_init(&rvi);
+        char string_buf[20];
+        snprintf(string_buf, sizeof(string_buf), "Client %zu", i);
+        rvi.nodeId = UA_NODEID_STRING(1, string_buf);
+        rvi.attributeId = UA_ATTRIBUTEID_VALUE;
+
+        UA_DataValue resp = UA_Server_read(tc.server, &rvi, UA_TIMESTAMPSTORETURN_NEITHER);
+
+        ck_assert_int_eq(resp.status, UA_STATUSCODE_GOOD);
+        ck_assert_int_eq(2, resp.value.arrayLength);
+        ck_assert(&UA_TYPES[UA_TYPES_INT32] == resp.value.type);
+        ck_assert_int_eq(10, *(UA_Int32 *)resp.value.data);
+        ck_assert_int_eq(20, *((UA_Int32 *)resp.value.data + 1));
+        UA_DataValue_deleteMembers(&resp);
+    }
+}
+
+static
+void server_addVariable(void* value) {
+        ThreadContext tmp = (*(ThreadContext *) value);
+        size_t offset = tmp.index * tmp.upperBound;
+        size_t number = offset + tmp.counter;
+        char string_buf[20];
+        snprintf(string_buf, sizeof(string_buf), "Server %zu", number);
+        UA_VariableAttributes attr = UA_VariableAttributes_default;
+        UA_Int32 myInteger = 42;
+        UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
+        attr.description = UA_LOCALIZEDTEXT("en-US",string_buf);
+        attr.displayName = UA_LOCALIZEDTEXT("en-US",string_buf);
+        attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+        attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
+
+        UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, string_buf);
+        UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, string_buf);
+        UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+        UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
+        UA_StatusCode ret = UA_Server_addVariableNode(tc.server, myIntegerNodeId, parentNodeId,
+                                                      parentReferenceNodeId, myIntegerName,
+                                                      UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);
+        ck_assert_uint_eq(ret, UA_STATUSCODE_GOOD);
+}
+
+static
+void client_addVariable(void* value){
+    ThreadContext tmp = (*(ThreadContext *) value);
+    size_t offset = tmp.index * tmp.upperBound;
+    size_t number = offset + tmp.counter;
+    char string_buf[20];
+    snprintf(string_buf, sizeof(string_buf), "Client %zu", number);
+    UA_VariableAttributes attr = UA_VariableAttributes_default;
+    attr.description = UA_LOCALIZEDTEXT("en-US", string_buf);
+    attr.displayName = UA_LOCALIZEDTEXT("en-US", string_buf);
+    UA_NodeId nodeId = UA_NODEID_STRING(1, string_buf);
+    UA_QualifiedName integerName = UA_QUALIFIEDNAME(1, string_buf);
+    UA_Int32 values[2] = {10, 20};
+    UA_Variant_setArray(&attr.value, values, 2, &UA_TYPES[UA_TYPES_INT32]);
+    attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+    attr.valueRank = UA_VALUERANK_ONE_DIMENSION;
+    UA_UInt32 arrayDims[1] = {2};
+    attr.arrayDimensions = arrayDims;
+    attr.arrayDimensionsSize = 1;
+    UA_StatusCode retval = UA_Client_addVariableNode(tc.clients[tmp.index], nodeId,
+                                               UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
+                                               integerName,
+                                               UA_NODEID_NULL, attr, NULL);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+}
+
+static
+void initTest(void) {
+    initThreadContext(NUMBER_OF_WORKERS, NUMBER_OF_CLIENTS, checkServer);
+
+    for (size_t i = 0; i < tc.numberOfWorkers; i++) {
+        setThreadContext(&tc.workerContext[i], i, ITERATIONS_PER_WORKER, server_addVariable);
+    }
+
+    for (size_t i = 0; i < tc.numberofClients; i++) {
+        setThreadContext(&tc.clientContext[i], i, ITERATIONS_PER_CLIENT, client_addVariable);
+    }
+}
+
+START_TEST(addVariableNodes) {
+        startMultithreading();
+    }
+END_TEST
+
+static Suite* testSuite_immutableNodes(void) {
+    Suite *s = suite_create("Multithreading");
+    TCase *valueCallback = tcase_create("Add variable nodes");
+    initTest();
+    tcase_add_checked_fixture(valueCallback, setup, teardown);
+    tcase_add_test(valueCallback, addVariableNodes);
+    suite_add_tcase(s,valueCallback);
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_immutableNodes();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 146 - 0
tests/multithreading/check_mt_addVariableTypeNode.c

@@ -0,0 +1,146 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <open62541/server_config_default.h>
+#include <open62541/plugin/log_stdout.h>
+#include <open62541/client_config_default.h>
+#include <open62541/client_highlevel.h>
+#include <check.h>
+#include "thread_wrapper.h"
+#include "mt_testing.h"
+
+#define NUMBER_OF_WORKERS 10
+#define ITERATIONS_PER_WORKER 10
+#define NUMBER_OF_CLIENTS 10
+#define ITERATIONS_PER_CLIENT 10
+
+static void setup(void) {
+    tc.running = true;
+    tc.server = UA_Server_new();
+    UA_ServerConfig_setDefault(UA_Server_getConfig(tc.server));
+    UA_Server_run_startup(tc.server);
+    THREAD_CREATE(server_thread, serverloop);
+}
+
+static void checkServer(void) {
+    for (size_t i = 0; i <  NUMBER_OF_WORKERS * ITERATIONS_PER_WORKER; i++) {
+        char string_buf[20];
+        snprintf(string_buf, sizeof(string_buf), "Server %zu", i);
+        UA_NodeId reqNodeId = UA_NODEID_STRING(1, string_buf);
+        UA_NodeId resNodeId;
+        UA_StatusCode ret = UA_Server_readNodeId(tc.server, reqNodeId, &resNodeId);
+
+        ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
+        ck_assert(UA_NodeId_equal(&reqNodeId, &resNodeId) == UA_TRUE);
+        UA_NodeId_deleteMembers(&resNodeId);
+    }
+
+    for (size_t i = 0; i < NUMBER_OF_CLIENTS * ITERATIONS_PER_CLIENT; i++) {
+        char string_buf[20];
+        snprintf(string_buf, sizeof(string_buf), "Client %zu", i);
+        UA_NodeId reqNodeId = UA_NODEID_STRING(1, string_buf);
+        UA_NodeId resNodeId;
+        UA_StatusCode ret = UA_Server_readNodeId(tc.server, reqNodeId, &resNodeId);
+
+        ck_assert_int_eq(ret, UA_STATUSCODE_GOOD);
+        ck_assert(UA_NodeId_equal(&reqNodeId, &resNodeId) == UA_TRUE);
+        UA_NodeId_deleteMembers(&resNodeId);
+    }
+}
+
+static
+void server_addVariableType(void* value) {
+    ThreadContext tmp = (*(ThreadContext *) value);
+    size_t offset = tmp.index * tmp.upperBound;
+    size_t number = offset + tmp.counter;
+    char string_buf[20];
+    snprintf(string_buf, sizeof(string_buf), "Server %zu", number);
+    UA_NodeId myNodeId = UA_NODEID_STRING(1, string_buf);
+    UA_VariableTypeAttributes vtAttr = UA_VariableTypeAttributes_default;
+    vtAttr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
+    vtAttr.valueRank = UA_VALUERANK_ONE_DIMENSION;
+    UA_UInt32 arrayDims[1] = {2};
+    vtAttr.arrayDimensions = arrayDims;
+    vtAttr.arrayDimensionsSize = 1;
+    vtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "2DPoint Type");
+
+    /* a matching default value is required */
+    UA_Double zero[2] = {0.0, 0.0};
+    UA_Variant_setArray(&vtAttr.value, zero, 2, &UA_TYPES[UA_TYPES_DOUBLE]);
+
+    UA_StatusCode res =
+            UA_Server_addVariableTypeNode(tc.server, myNodeId,
+                                          UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
+                                          UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                          UA_QUALIFIEDNAME(1, "2DPoint Type"), UA_NODEID_NULL,
+                                          vtAttr, NULL, NULL);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
+
+}
+
+static
+void client_addVariableType(void* value){
+    ThreadContext tmp = (*(ThreadContext *) value);
+    size_t offset = tmp.index * tmp.upperBound;
+    size_t number = offset + tmp.counter;
+    char string_buf[20];
+    snprintf(string_buf, sizeof(string_buf), "Client %zu", number);
+    UA_NodeId myNodeId = UA_NODEID_STRING(1, string_buf);
+
+    UA_VariableTypeAttributes attr = UA_VariableTypeAttributes_default;
+    attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
+    attr.valueRank = UA_VALUERANK_ONE_DIMENSION;
+    UA_UInt32 arrayDims[1] = {2};
+    attr.arrayDimensions = arrayDims;
+    attr.arrayDimensionsSize = 1;
+    attr.displayName = UA_LOCALIZEDTEXT("en-US", "PointType");
+
+    /* a matching default value is required */
+    UA_Double zero[2] = {0.0, 0.0};
+    UA_Variant_setArray(&attr.value, zero, 2, &UA_TYPES[UA_TYPES_INT32]);
+    UA_StatusCode  retval = UA_Client_addVariableTypeNode(tc.clients[tmp.index], myNodeId,
+                                                          UA_NODEID_NUMERIC(0, UA_NS0ID_BASEVARIABLETYPE),
+                                                          UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                                          UA_QUALIFIEDNAME(1, "PointType"), attr, NULL);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+}
+
+static
+void initTest(void) {
+    initThreadContext(NUMBER_OF_WORKERS, NUMBER_OF_CLIENTS, checkServer);
+    
+    for (size_t i = 0; i < tc.numberOfWorkers; i++) {
+        setThreadContext(&tc.workerContext[i], i, ITERATIONS_PER_WORKER, server_addVariableType);
+    }
+
+    for (size_t i = 0; i < tc.numberofClients; i++) {
+        setThreadContext(&tc.clientContext[i], i, ITERATIONS_PER_CLIENT, client_addVariableType);
+    }
+}
+
+START_TEST(addVariableTypeNodes) {
+        startMultithreading();
+    }
+END_TEST
+
+static Suite* testSuite_immutableNodes(void) {
+    Suite *s = suite_create("Multithreading");
+    TCase *valueCallback = tcase_create("Add variable type nodes");
+    initTest();
+    tcase_add_checked_fixture(valueCallback, setup, teardown);
+    tcase_add_test(valueCallback, addVariableTypeNodes);
+    suite_add_tcase(s,valueCallback);
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_immutableNodes();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 121 - 0
tests/multithreading/check_mt_readValueAttribute.c

@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <open62541/plugin/log_stdout.h>
+#include <open62541/client_config_default.h>
+#include <open62541/client_highlevel.h>
+#include <check.h>
+#include "thread_wrapper.h"
+#include "mt_testing.h"
+
+
+#define NUMBER_OF_WORKERS 10
+#define ITERATIONS_PER_WORKER 10
+#define NUMBER_OF_CLIENTS 10
+#define ITERATIONS_PER_CLIENT 10
+
+UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
+
+static
+void addVariableNode(void) {
+    UA_VariableAttributes attr = UA_VariableAttributes_default;
+    UA_Int32 myInteger = 42;
+    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
+    attr.description = UA_LOCALIZEDTEXT("en-US","Temperature");
+    attr.displayName = UA_LOCALIZEDTEXT("en-US","Temperature");
+    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "Temperature");
+    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
+    UA_StatusCode res =
+            UA_Server_addVariableNode(tc.server, pumpTypeId, parentNodeId,
+                                      parentReferenceNodeId, myIntegerName,
+                                      UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
+                                      attr, NULL, NULL);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
+}
+
+static void setup(void) {
+    tc.running = true;
+    tc.server = UA_Server_new();
+    UA_ServerConfig_setDefault(UA_Server_getConfig(tc.server));
+    addVariableNode();
+    UA_Server_run_startup(tc.server);
+    THREAD_CREATE(server_thread, serverloop);
+}
+
+static
+void server_readValueAttribute(void * value) {
+    UA_ReadValueId rvi;
+    UA_ReadValueId_init(&rvi);
+    rvi.nodeId = pumpTypeId;
+    rvi.attributeId = UA_ATTRIBUTEID_VALUE;
+
+    // read 1
+    UA_DataValue resp = UA_Server_read(tc.server, &rvi, UA_TIMESTAMPSTORETURN_NEITHER);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, resp.status);
+    ck_assert_int_eq(true, resp.hasValue);
+    ck_assert_int_eq(0, resp.value.arrayLength);
+    ck_assert(&UA_TYPES[UA_TYPES_INT32] == resp.value.type);
+    ck_assert_int_eq(42, *(UA_Int32* )resp.value.data);
+    UA_DataValue_deleteMembers(&resp);
+
+    // read 2
+    UA_Variant var;
+    UA_Variant_init(&var);
+    UA_StatusCode ret = UA_Server_readValue(tc.server, rvi.nodeId, &var);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, ret);
+    ck_assert_int_eq(42, *(UA_Int32 *)var.data);
+
+    UA_Variant_deleteMembers(&var);
+}
+
+
+static
+void client_readValueAttribute(void * value) {
+    ThreadContext tmp = (*(ThreadContext *) value);
+    UA_Variant val;
+    UA_NodeId nodeId = pumpTypeId;
+    UA_StatusCode retval = UA_Client_readValueAttribute(tc.clients[tmp.index], nodeId, &val);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(42, *(UA_Int32 *)val.data);
+    UA_Variant_deleteMembers(&val);
+}
+
+static
+void initTest(void) {
+    initThreadContext(NUMBER_OF_WORKERS, NUMBER_OF_CLIENTS, NULL);
+
+    for (size_t i = 0; i < tc.numberOfWorkers; i++) {
+        setThreadContext(&tc.workerContext[i], i, ITERATIONS_PER_WORKER, server_readValueAttribute);
+    }
+
+    for (size_t i = 0; i < tc.numberofClients; i++) {
+        setThreadContext(&tc.clientContext[i], i, ITERATIONS_PER_CLIENT, client_readValueAttribute);
+    }
+}
+
+START_TEST(readValueAttribute) {
+        startMultithreading();
+    }
+END_TEST
+
+static Suite* testSuite_immutableNodes(void) {
+    Suite *s = suite_create("Multithreading");
+    TCase *valueCallback = tcase_create("Read Write attribute");
+    initTest();
+    tcase_add_checked_fixture(valueCallback, setup, teardown);
+    tcase_add_test(valueCallback, readValueAttribute);
+    suite_add_tcase(s,valueCallback);
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_immutableNodes();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 166 - 0
tests/multithreading/check_mt_readWriteDelete.c

@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <open62541/server_config_default.h>
+#include <open62541/plugin/log_stdout.h>
+#include <open62541/client_config_default.h>
+#include <open62541/client_highlevel.h>
+#include <check.h>
+#include <testing_clock.h>
+#include "thread_wrapper.h"
+#include "mt_testing.h"
+
+
+#define NUMBER_OF_READ_WORKERS 10
+#define NUMBER_OF_WRITE_WORKERS 10
+#define ITERATIONS_PER_WORKER 100
+
+#define NUMBER_OF_READ_CLIENTS 10
+#define NUMBER_OF_WRITE_CLIENTS 10
+#define ITERATIONS_PER_CLIENT 100
+
+UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
+
+static
+void AddVariableNode(void) {
+    /* Add a variable node to the address space */
+    UA_VariableAttributes attr = UA_VariableAttributes_default;
+    UA_Int32 myInteger = 42;
+    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
+    attr.description = UA_LOCALIZEDTEXT("en-US","Temperature");
+    attr.displayName = UA_LOCALIZEDTEXT("en-US","Temperature");
+    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
+    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "Temperature");
+    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
+
+    UA_StatusCode res =
+            UA_Server_addVariableNode(tc.server, pumpTypeId, parentNodeId,
+                                      parentReferenceNodeId, myIntegerName,
+                                      UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
+                                      attr, NULL, NULL);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
+}
+
+static void setup(void) {
+    tc.running = true;
+    tc.server = UA_Server_new();
+    UA_ServerConfig_setDefault(UA_Server_getConfig(tc.server));
+    AddVariableNode();
+    UA_Server_run_startup(tc.server);
+    THREAD_CREATE(server_thread, serverloop);
+}
+
+static
+void server_deleteValue(void *value) {
+    UA_StatusCode ret = UA_Server_deleteNode(tc.server, pumpTypeId, true);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, ret);
+}
+
+static
+void server_readValue(void *value) {
+    UA_ReadValueId rvi;
+    UA_ReadValueId_init(&rvi);
+    rvi.nodeId = pumpTypeId;
+    rvi.attributeId = UA_ATTRIBUTEID_VALUE;
+
+    UA_Variant var;
+    UA_Variant_init(&var);
+    UA_StatusCode retval = UA_Server_readValue(tc.server, rvi.nodeId, &var);
+    if (retval == UA_STATUSCODE_GOOD) {
+        ck_assert_int_eq(42, *(UA_Int32 *)var.data);
+        UA_Variant_deleteMembers(&var);
+    }
+    else {
+        ck_assert_int_eq(retval, UA_STATUSCODE_BADNODEIDUNKNOWN);
+    }
+}
+
+static
+void server_writeValue(void *value) {
+    UA_WriteValue wValue;
+    UA_WriteValue_init(&wValue);
+    UA_Int32 testValue = 42;
+    UA_Variant_setScalar(&wValue.value.value, &testValue, &UA_TYPES[UA_TYPES_INT32]);
+    wValue.nodeId = pumpTypeId;
+    wValue.attributeId = UA_ATTRIBUTEID_VALUE;
+    wValue.value.hasValue = true;
+    UA_StatusCode retval = UA_Server_write(tc.server, &wValue);
+    ck_assert(retval == UA_STATUSCODE_BADNODEIDUNKNOWN || retval == UA_STATUSCODE_GOOD);
+}
+
+static
+void client_writeValue(void *value) {
+    ThreadContext tmp = (*(ThreadContext *) value);
+    UA_Variant val;
+    UA_Int32 testValue = 42;
+    UA_Variant_setScalar(&val, &testValue, &UA_TYPES[UA_TYPES_INT32]);
+    UA_StatusCode  retval = UA_Client_writeValueAttribute(tc.clients[tmp.index], pumpTypeId, &val);
+    ck_assert(retval == UA_STATUSCODE_BADNODEIDUNKNOWN || retval == UA_STATUSCODE_GOOD);
+}
+
+static
+void client_readValue(void *value) {
+    ThreadContext tmp = (*(ThreadContext *) value);
+    UA_Variant val;
+    UA_NodeId nodeId = pumpTypeId;
+    UA_StatusCode retval = UA_Client_readValueAttribute(tc.clients[tmp.index], nodeId, &val);
+    if (retval == UA_STATUSCODE_GOOD) {
+        ck_assert_int_eq(42, *(UA_Int32 *)val.data);
+        UA_Variant_deleteMembers(&val);
+    }
+    else {
+        ck_assert_int_eq(retval, UA_STATUSCODE_BADNODEIDUNKNOWN);
+    }
+}
+
+
+static
+void initTest(void) {
+    initThreadContext(NUMBER_OF_READ_WORKERS + NUMBER_OF_WRITE_WORKERS + 1, NUMBER_OF_READ_CLIENTS + NUMBER_OF_WRITE_CLIENTS, NULL);
+
+    size_t i = 0;
+    for (; i < NUMBER_OF_READ_WORKERS; i++) {
+        setThreadContext(&tc.workerContext[i], i, ITERATIONS_PER_WORKER, server_readValue);
+    }
+    for (; i < NUMBER_OF_READ_WORKERS + NUMBER_OF_WRITE_WORKERS; i++) {
+        setThreadContext(&tc.workerContext[i], i, ITERATIONS_PER_WORKER, server_writeValue);
+    }
+
+    //Thread deleting the variable
+    setThreadContext(&tc.workerContext[tc.numberOfWorkers - 1], i, 1, server_deleteValue);
+
+    i = 0;
+    for (; i < NUMBER_OF_READ_CLIENTS; i++) {
+        setThreadContext(&tc.clientContext[i], i, ITERATIONS_PER_CLIENT, client_readValue);
+    }
+    for (; i < NUMBER_OF_READ_CLIENTS + NUMBER_OF_WRITE_CLIENTS; i++) {
+        setThreadContext(&tc.clientContext[i], i, ITERATIONS_PER_CLIENT, client_writeValue);
+    }
+}
+
+START_TEST(readWriteDelete) {
+        startMultithreading();
+    }
+END_TEST
+
+static Suite* testSuite_immutableNodes(void) {
+    Suite *s = suite_create("Multithreading");
+    TCase *valueCallback = tcase_create("Read-Write-Delete attribute");
+    initTest();
+    tcase_add_checked_fixture(valueCallback, setup, teardown);
+    tcase_add_test(valueCallback, readWriteDelete);
+    suite_add_tcase(s,valueCallback);
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_immutableNodes();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+

+ 193 - 0
tests/multithreading/check_mt_readWriteDeleteCallback.c

@@ -0,0 +1,193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <open62541/server_config_default.h>
+#include <open62541/plugin/log_stdout.h>
+#include <open62541/client_config_default.h>
+#include <open62541/client_highlevel.h>
+#include <check.h>
+#include <testing_clock.h>
+#include "thread_wrapper.h"
+#include "mt_testing.h"
+
+
+#define NUMBER_OF_READ_WORKERS 10
+#define NUMBER_OF_WRITE_WORKERS 10
+#define ITERATIONS_PER_WORKER 100
+
+#define NUMBER_OF_READ_CLIENTS 10
+#define NUMBER_OF_WRITE_CLIENTS 10
+#define ITERATIONS_PER_CLIENT 100
+
+UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
+UA_Int32 temperature = 42;
+
+UA_LOCK_TYPE(mu)
+
+static UA_StatusCode
+readTemperature(UA_Server *tmpServer,
+                const UA_NodeId *sessionId, void *sessionContext,
+                const UA_NodeId *nodeId, void *nodeContext,
+                UA_Boolean sourceTimeStamp, const UA_NumericRange *range,
+                UA_DataValue *dataValue) {
+    UA_LOCK(mu);
+    UA_Variant_setScalarCopy(&dataValue->value, &temperature, &UA_TYPES[UA_TYPES_INT32]);
+    UA_UNLOCK(mu);
+    dataValue->hasValue = true;
+    return UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode
+writeTemperature(UA_Server *tmpServer,
+                 const UA_NodeId *sessionId, void *sessionContext,
+                 const UA_NodeId *nodeId, void *nodeContext,
+                 const UA_NumericRange *range, const UA_DataValue *data) {
+    UA_LOCK(mu);
+    temperature = *(UA_Int32 *) data->value.data;
+    UA_UNLOCK(mu);
+    return UA_STATUSCODE_GOOD;
+}
+
+static
+void AddVariableNode(void) {
+    UA_VariableAttributes attr = UA_VariableAttributes_default;
+    attr.displayName = UA_LOCALIZEDTEXT("en-US", "Temperature");
+    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
+
+    UA_DataSource temperatureSource;
+    temperatureSource.read = readTemperature;
+    temperatureSource.write = writeTemperature;
+    UA_StatusCode retval = UA_Server_addDataSourceVariableNode(tc.server, pumpTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+                                                               UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), UA_QUALIFIEDNAME(1, "Temperature"),
+                                                               UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr,
+                                                               temperatureSource, NULL, NULL);
+
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+}
+
+static void setup(void) {
+    tc.running = true;
+    tc.server = UA_Server_new();
+    UA_ServerConfig_setDefault(UA_Server_getConfig(tc.server));
+    AddVariableNode();
+    UA_Server_run_startup(tc.server);
+    THREAD_CREATE(server_thread, serverloop);
+}
+
+static
+void server_deleteValue(void *value) {
+    //UA_fakeSleep(100);
+    UA_StatusCode ret = UA_Server_deleteNode(tc.server, pumpTypeId, true);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, ret);
+}
+
+static
+void server_readValue(void *value) {
+    UA_ReadValueId rvi;
+    UA_ReadValueId_init(&rvi);
+    rvi.nodeId = pumpTypeId;
+    rvi.attributeId = UA_ATTRIBUTEID_VALUE;
+
+    UA_Variant var;
+    UA_Variant_init(&var);
+    UA_StatusCode retval = UA_Server_readValue(tc.server, rvi.nodeId, &var);
+    if (retval == UA_STATUSCODE_GOOD) {
+        ck_assert_int_eq(42, *(UA_Int32 *)var.data);
+        ck_assert_int_eq(UA_STATUSCODE_GOOD, retval);
+        UA_Variant_deleteMembers(&var);
+    }
+    else {
+        ck_assert_int_eq(retval, UA_STATUSCODE_BADNODEIDUNKNOWN);
+    }
+}
+
+static
+void server_writeValue(void *value) {
+        UA_WriteValue wValue;
+        UA_WriteValue_init(&wValue);
+        UA_Int32 testValue = 42;
+        UA_Variant_setScalar(&wValue.value.value, &testValue, &UA_TYPES[UA_TYPES_INT32]);
+        wValue.nodeId = pumpTypeId;
+        wValue.attributeId = UA_ATTRIBUTEID_VALUE;
+        wValue.value.hasValue = true;
+        UA_StatusCode retval = UA_Server_write(tc.server, &wValue);
+        ck_assert(retval == UA_STATUSCODE_BADNODEIDUNKNOWN || retval == UA_STATUSCODE_GOOD);
+}
+
+static
+void client_writeValue(void *value) {
+    ThreadContext tmp = (*(ThreadContext *) value);
+    UA_Variant val;
+    UA_Int32 testValue = 42;
+    UA_Variant_setScalar(&val, &testValue, &UA_TYPES[UA_TYPES_INT32]);
+    UA_StatusCode retval = UA_Client_writeValueAttribute(tc.clients[tmp.index], pumpTypeId, &val);
+    ck_assert(retval == UA_STATUSCODE_BADNODEIDUNKNOWN || retval == UA_STATUSCODE_GOOD);
+}
+
+static
+void client_readValue(void *value) {
+    ThreadContext tmp = (*(ThreadContext *) value);
+    UA_Variant val;
+    UA_NodeId nodeId = pumpTypeId;
+    UA_StatusCode retval = UA_Client_readValueAttribute(tc.clients[tmp.index], nodeId, &val);
+    if (retval == UA_STATUSCODE_GOOD) {
+        ck_assert_int_eq(42, *(UA_Int32 *)val.data);
+        UA_Variant_deleteMembers(&val);
+    }
+    else {
+        ck_assert_int_eq(retval, UA_STATUSCODE_BADNODEIDUNKNOWN);
+    }
+
+}
+
+static
+void initTest(void) {
+    UA_LOCK_INIT(mu);
+
+    initThreadContext(NUMBER_OF_READ_WORKERS + NUMBER_OF_WRITE_WORKERS + 1, NUMBER_OF_READ_CLIENTS + NUMBER_OF_WRITE_CLIENTS, NULL);
+
+    size_t i = 0;
+    for (; i < NUMBER_OF_READ_WORKERS; i++) {
+        setThreadContext(&tc.workerContext[i], i, ITERATIONS_PER_WORKER, server_readValue);
+    }
+    for (; i < NUMBER_OF_READ_WORKERS + NUMBER_OF_WRITE_WORKERS; i++) {
+        setThreadContext(&tc.workerContext[i], i, ITERATIONS_PER_WORKER, server_writeValue);
+    }
+
+    //Thread deleting the variable
+    setThreadContext(&tc.workerContext[tc.numberOfWorkers - 1], i, 1, server_deleteValue);
+
+    i = 0;
+    for (; i < NUMBER_OF_READ_CLIENTS; i++) {
+        setThreadContext(&tc.clientContext[i], i, ITERATIONS_PER_CLIENT, client_readValue);
+    }
+    for (; i < NUMBER_OF_READ_CLIENTS + NUMBER_OF_WRITE_CLIENTS; i++) {
+        setThreadContext(&tc.clientContext[i], i, ITERATIONS_PER_CLIENT, client_writeValue);
+    }
+}
+
+START_TEST(readWriteDeleteCallback) {
+        startMultithreading();
+    }
+END_TEST
+
+static Suite* testSuite_immutableNodes(void) {
+    Suite *s = suite_create("Multithreading");
+    TCase *valueCallback = tcase_create("Read-Write-Delete-Callback");
+    initTest();
+    tcase_add_checked_fixture(valueCallback, setup, teardown);
+    tcase_add_test(valueCallback, readWriteDeleteCallback);
+    suite_add_tcase(s,valueCallback);
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_immutableNodes();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+

+ 127 - 0
tests/multithreading/check_mt_writeValueAttribute.c

@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <open62541/server_config_default.h>
+#include <open62541/plugin/log_stdout.h>
+#include <open62541/client_config_default.h>
+#include <open62541/client_highlevel.h>
+#include <check.h>
+#include "thread_wrapper.h"
+#include "mt_testing.h"
+
+#define NUMBER_OF_WORKERS 10
+#define ITERATIONS_PER_WORKER 10
+#define NUMBER_OF_CLIENTS 10
+#define ITERATIONS_PER_CLIENT 10
+
+UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
+
+static
+void addVariableNode(void) {
+    /* Add a variable node to the address space */
+    UA_VariableAttributes attr = UA_VariableAttributes_default;
+    UA_Int32 myInteger = 42;
+    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
+    attr.description = UA_LOCALIZEDTEXT("en-US","Temperature");
+    attr.displayName = UA_LOCALIZEDTEXT("en-US","Temperature");
+    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
+    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "Temperature");
+    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
+
+    UA_StatusCode res =
+            UA_Server_addVariableNode(tc.server, pumpTypeId, parentNodeId,
+                                      parentReferenceNodeId, myIntegerName,
+                                      UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
+                                      attr, NULL, NULL);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, res);
+}
+
+static void setup(void) {
+    tc.running = true;
+    tc.server = UA_Server_new();
+    UA_ServerConfig_setDefault(UA_Server_getConfig(tc.server));
+    addVariableNode();
+    UA_Server_run_startup(tc.server);
+    THREAD_CREATE(server_thread, serverloop);
+}
+
+static void checkServer(void) {
+    UA_Variant var;
+    UA_Variant_init(&var);
+    UA_StatusCode ret = UA_Server_readValue(tc.server, pumpTypeId, &var);
+    ck_assert_int_eq(42, *(UA_Int32 *)var.data);
+    ck_assert_int_eq(UA_STATUSCODE_GOOD, ret);
+    UA_Variant_deleteMembers(&var);
+}
+
+static
+void server_writeValueAttribute(void *value) {
+    UA_WriteValue wValue;
+    UA_WriteValue_init(&wValue);
+    UA_Int32 testValue = 42;
+    UA_Variant_setScalar(&wValue.value.value, &testValue, &UA_TYPES[UA_TYPES_INT32]);
+    wValue.nodeId = pumpTypeId;
+    wValue.attributeId = UA_ATTRIBUTEID_VALUE;
+    wValue.value.hasValue = true;
+    UA_StatusCode retval = UA_Server_write(tc.server, &wValue);
+    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
+}
+
+static
+void client_writeValueAttribute(void *value) {
+    ThreadContext tmp = (*(ThreadContext *) value);
+    UA_Variant val;
+    UA_Int32 testValue = 42;
+    UA_Variant_setScalar(&val, &testValue, &UA_TYPES[UA_TYPES_INT32]);
+    UA_StatusCode retval = UA_Client_writeValueAttribute(tc.clients[tmp.index], pumpTypeId, &val);
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+}
+
+static
+void initTest(void) {
+    tc.numberOfWorkers = NUMBER_OF_WORKERS;
+    tc.numberofClients = NUMBER_OF_CLIENTS;
+    tc.checkServerNodes = checkServer;
+    tc.workerContext = (ThreadContext*) UA_calloc(tc.numberOfWorkers, sizeof(ThreadContext));
+    tc.clients =  (UA_Client**) UA_calloc(tc.numberofClients, sizeof(UA_Client*));
+    tc.clientContext = (ThreadContext*) UA_calloc(tc.numberofClients, sizeof(ThreadContext));
+    for (size_t i = 0; i < tc.numberOfWorkers; i++) {
+        tc.workerContext[i].index = i;
+        tc.workerContext[i].upperBound = ITERATIONS_PER_WORKER;
+        tc.workerContext[i].func = server_writeValueAttribute;
+    }
+
+    for (size_t i = 0; i < tc.numberofClients; i++) {
+        tc.clientContext[i].index = i;
+        tc.clientContext[i].upperBound = ITERATIONS_PER_CLIENT;
+        tc.clientContext[i].func = client_writeValueAttribute;
+    }
+}
+
+START_TEST(writeValueAttribute) {
+        startMultithreading();
+    }
+END_TEST
+
+static Suite* testSuite_immutableNodes(void) {
+    Suite *s = suite_create("Multithreading");
+    TCase *valueCallback = tcase_create("Write value attribute");
+    initTest();
+    tcase_add_checked_fixture(valueCallback, setup, teardown);
+    tcase_add_test(valueCallback, writeValueAttribute);
+    suite_add_tcase(s,valueCallback);
+    return s;
+}
+
+int main(void) {
+    Suite *s = testSuite_immutableNodes();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+

+ 110 - 0
tests/multithreading/deviceObjectType.h

@@ -0,0 +1,110 @@
+#ifndef OPEN62541_MT_COMMON
+#define OPEN62541_MT_COMMON
+
+#include "mt_testing.h"
+
+UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
+
+static void
+defineObjectTypes(void) {
+    UA_NodeId deviceTypeId;
+    UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
+    dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "DeviceType");
+    UA_Server_addObjectTypeNode(tc.server, UA_NODEID_NULL,
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
+                                UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                UA_QUALIFIEDNAME(1, "DeviceType"), dtAttr,
+                                NULL, &deviceTypeId);
+
+    UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
+    mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ManufacturerName");
+    UA_NodeId manufacturerNameId;
+    UA_Server_addVariableNode(tc.server, UA_NODEID_NULL, deviceTypeId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "ManufacturerName"),
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
+
+    UA_Server_addReference(tc.server, manufacturerNameId,
+                           UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
+                           UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
+
+
+    UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
+    modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ModelName");
+    UA_Server_addVariableNode(tc.server, UA_NODEID_NULL, deviceTypeId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "ModelName"),
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, NULL);
+
+    UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
+    ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "PumpType");
+    UA_Server_addObjectTypeNode(tc.server, pumpTypeId,
+                                deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
+                                UA_QUALIFIEDNAME(1, "PumpType"), ptAttr,
+                                NULL, NULL);
+
+    UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
+    statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
+    statusAttr.valueRank = UA_VALUERANK_SCALAR;
+    UA_NodeId statusId;
+    UA_Server_addVariableNode(tc.server, UA_NODEID_NULL, pumpTypeId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "Status"),
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
+
+    UA_Server_addReference(tc.server, statusId,
+                           UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
+                           UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
+
+    UA_VariableAttributes rpmAttr = UA_VariableAttributes_default;
+    rpmAttr.displayName = UA_LOCALIZEDTEXT("en-US", "MotorRPM");
+    rpmAttr.valueRank = UA_VALUERANK_SCALAR;
+    UA_Server_addVariableNode(tc.server, UA_NODEID_NULL, pumpTypeId,
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
+                              UA_QUALIFIEDNAME(1, "MotorRPMs"),
+                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), rpmAttr, NULL, NULL);
+}
+
+static UA_StatusCode
+pumpTypeConstructor(UA_Server *servertmp,
+                    const UA_NodeId *sessionId, void *sessionContext,
+                    const UA_NodeId *typeId, void *typeContext,
+                    const UA_NodeId *nodeId, void **nodeContext) {
+    UA_RelativePathElement rpe;
+    UA_RelativePathElement_init(&rpe);
+    rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
+    rpe.isInverse = false;
+    rpe.includeSubtypes = false;
+    rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
+
+    UA_BrowsePath bp;
+    UA_BrowsePath_init(&bp);
+    bp.startingNode = *nodeId;
+    bp.relativePath.elementsSize = 1;
+    bp.relativePath.elements = &rpe;
+
+    UA_BrowsePathResult bpr =
+            UA_Server_translateBrowsePathToNodeIds(tc.server, &bp);
+    if(bpr.statusCode != UA_STATUSCODE_GOOD ||
+       bpr.targetsSize < 1)
+        return bpr.statusCode;
+
+    /* Set the status value */
+    UA_Boolean status = true;
+    UA_Variant value;
+    UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
+    UA_Server_writeValue(tc.server, bpr.targets[0].targetId.nodeId, value);
+    UA_BrowsePathResult_clear(&bpr);
+
+    return UA_STATUSCODE_GOOD;
+}
+
+static void
+addPumpTypeConstructor(UA_Server *servertmp) {
+    UA_NodeTypeLifecycle lifecycle;
+    lifecycle.constructor = pumpTypeConstructor;
+    lifecycle.destructor = NULL;
+    UA_Server_setNodeTypeLifecycle(tc.server, pumpTypeId, lifecycle);
+}
+
+#endif //OPEN62541_MT_COMMON

+ 104 - 0
tests/multithreading/mt_testing.h

@@ -0,0 +1,104 @@
+#ifndef OPEN62541_MT_TESTING_H
+#define OPEN62541_MT_TESTING_H
+
+#include <open62541/server_config_default.h>
+
+#define THREAD_CALLBACK_PARAM(name, param) static void * name(void *param)
+#define THREAD_CREATE_PARAM(handle, callback, param) pthread_create(&(handle), NULL, callback, (void*) &(param))
+
+typedef struct {
+    void (*func)(void *param); //function to execute
+    size_t counter; //index of the iteration
+    size_t index; // index within workerContext array of global TestContext
+    size_t upperBound; //number of iterations each thread schould execute func
+    THREAD_HANDLE handle;
+} ThreadContext;
+
+typedef struct {
+    size_t numberOfWorkers;
+    ThreadContext *workerContext;
+    size_t numberofClients;
+    ThreadContext *clientContext;
+    UA_Boolean running;
+    UA_Server *server;
+    UA_Client **clients;
+    void (*checkServerNodes)(void);
+} TestContext;
+
+TestContext tc;
+THREAD_HANDLE server_thread;
+
+THREAD_CALLBACK(serverloop) {
+    while(tc.running)
+        UA_Server_run_iterate(tc.server, true);
+    return 0;
+}
+
+static
+void teardown(void) {
+    for (size_t i = 0; i < tc.numberOfWorkers; i++)
+        THREAD_JOIN(tc.workerContext[i].handle);
+
+    for (size_t i = 0; i < tc.numberofClients; i++)
+        THREAD_JOIN(tc.clientContext[i].handle);
+
+    tc.running = false;
+    THREAD_JOIN(server_thread);
+    if (tc.checkServerNodes)
+        tc.checkServerNodes();
+    UA_Server_run_shutdown(tc.server);
+    UA_Server_delete(tc.server);
+}
+
+THREAD_CALLBACK_PARAM(workerLoop, val) {
+    ThreadContext tmp = (*(ThreadContext *) val);
+    for (size_t i = 0; i < tmp.upperBound; i++) {
+        tmp.counter = i;
+        tmp.func(&tmp);
+    }
+    return NULL;
+}
+
+THREAD_CALLBACK_PARAM(clientLoop, val) {
+    ThreadContext tmp = (*(ThreadContext *) val);
+    tc.clients[tmp.index] = UA_Client_new();
+    UA_ClientConfig_setDefault(UA_Client_getConfig(tc.clients[tmp.index]));
+    UA_StatusCode retval = UA_Client_connect(tc.clients[tmp.index], "opc.tcp://localhost:4840");
+    ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
+
+    for(size_t i = 0; i < tmp.upperBound; i++) {
+        tmp.counter = i;
+        tmp.func(&tmp);
+    }
+    UA_Client_disconnect(tc.clients[tmp.index]);
+    UA_Client_delete(tc.clients[tmp.index]);
+    return NULL;
+}
+
+static UA_INLINE void
+initThreadContext(size_t numberOfWorkers, size_t numberOfClients, void (*checkServerNodes)(void)) {
+    tc.numberOfWorkers = numberOfWorkers;
+    tc.numberofClients = numberOfClients;
+    tc.checkServerNodes = checkServerNodes;
+    tc.workerContext = (ThreadContext*) UA_calloc(tc.numberOfWorkers, sizeof(ThreadContext));
+    tc.clients =  (UA_Client**) UA_calloc(tc.numberofClients, sizeof(UA_Client*));
+    tc.clientContext = (ThreadContext*) UA_calloc(tc.numberofClients, sizeof(ThreadContext));
+}
+
+static UA_INLINE void
+setThreadContext(ThreadContext *workerContext, size_t index, size_t upperBound, void (*func)(void *param)) {
+    workerContext->index = index;
+    workerContext->upperBound = upperBound;
+    workerContext->func = func;
+}
+
+static UA_INLINE void
+startMultithreading(void) {
+        for (size_t i = 0; i < tc.numberOfWorkers; i++)
+            THREAD_CREATE_PARAM(tc.workerContext[i].handle, workerLoop, tc.workerContext[i]);
+
+        for (size_t i = 0; i < tc.numberofClients; i++)
+            THREAD_CREATE_PARAM(tc.clientContext[i].handle, clientLoop, tc.clientContext[i]);
+}
+
+#endif //OPEN62541_MT_TESTING_H