Browse Source

initial structure for transaction support

Julius Pfrommer 10 years ago
parent
commit
bee85dd24c

+ 1 - 1
examples/src/xml2ns0.c

@@ -105,7 +105,7 @@ typedef struct UA_NodeSet {
 	UA_NodeSetAliases aliases;
 } UA_NodeSet;
 UA_Int32 UA_NodeSet_init(UA_NodeSet* p) {
-	Namespace_create(&(p->ns), 100);
+	Namespace_new(&(p->ns), 100, 9999); // FIXME: Set a correct nsid
 	p->aliases.size = -1;
 	p->aliases.aliases = UA_NULL;
 	return UA_SUCCESS;

+ 24 - 4
include/ua_list.h

@@ -2,10 +2,30 @@
 #define UA_LIST_H_
 
 #include "opcua.h"
-
-/*
- * Double Linked Lists
- */
+#include <stddef.h> /* Needed for sys/queue.h */
+#include <sys/queue.h>
+
+
+/**********************/
+/* Singly Linked List */
+/**********************/
+
+#define UA_SLIST_HEAD(name, type) SLIST_HEAD(name, type)
+#define UA_SLIST_HEAD_INITIALIZER(head) SLIST_HEAD_INITILIZER(head)
+#define UA_SLIST_ENTRY(type) SLIST_ENTRY(type)
+#define UA_SLIST_INIT(head) SLIST_INIT(head)
+#define UA_SLIST_INSERT_AFTER(slistelm, elm, field) SLIST_INSERT_AFTER(slistelm, elm, field)
+#define UA_SLIST_INSERT_HEAD(head, elm, field) SLIST_INSERT_HEAD(head, elm, field)
+#define UA_SLIST_REMOVE_HEAD(head, field) SLIST_REMOVE_HEAD(head, field)
+#define UA_SLIST_REMOVE(head, elm, type, field) SLIST_REMOVE(head, elm, type, field)
+#define UA_SLIST_FOREACH(var, head, field) SLIST_FOREACH(var, head, field)
+#define UA_SLIST_EMPTY(head) SLIST_EMPTY(head)
+#define UA_SLIST_FIRST(head) SLIST_FIRST(head)
+#define UA_SLIST_NEXT(elm, field) SLIST_NEXT(elm, field)
+
+/**********************/
+/* Doubly Linked List */
+/**********************/
 
 typedef void (*UA_list_PayloadVisitor)(void* payload);
 

+ 1 - 0
src/Makefile.am

@@ -11,6 +11,7 @@ libopen62541_la_SOURCES = opcua.c \
 						  ua_transport_binary.c \
 						  ua_transport_binary_secure.c \
 						  ua_namespace.c \
+						  ua_namespace_transactions.c \
 						  ua_services_attribute.c \
 						  ua_services_session.c \
 						  ua_services_discovery.c \

+ 3 - 6
src/ua_application.c

@@ -27,15 +27,12 @@ UA_Node* create_node_ns0(UA_Int32 class, UA_Int32 nodeClass, UA_Int32 const id,
 #define C2UA_STRING(s) (UA_String) { sizeof(s)-1, (UA_Byte*) s }
 void appMockup_init() {
 	// create namespaces
+	// TODO: A table that maps the namespaceUris to Ids
 	Namespace* ns0;
-	Namespace_create(&ns0,100);
-	ns0->namespaceId = 0;
-	ns0->namespaceUri = C2UA_STRING("http://opcfoundation.org/UA/");
+	Namespace_new(&ns0, 100, 0); //C2UA_STRING("http://opcfoundation.org/UA/"));
 
 	Namespace* local;
-	Namespace_create(&local,100);
-	local->namespaceId = 1;
-	local->namespaceUri = C2UA_STRING("http://localhost:16664/open62541/");
+	Namespace_new(&local, 100, 1); //C2UA_STRING("http://localhost:16664/open62541/"));
 
 	// add to list of namespaces
 	UA_indexedList_init(appMockup.namespaces);

+ 106 - 178
src/ua_namespace.c

@@ -6,15 +6,32 @@
 /* Internal (not exported) functionality */
 /*****************************************/
 
-UA_Int32 Namespace_TransactionContext_init(Namespace_TransactionContext * tc) {
-	return UA_list_init((UA_list_List *) tc);
-}
+typedef struct Namespace_Entry {
+	UA_UInt64 status;	/* 2 bits status | 14 bits checkout count | 48 bits timestamp */
+	const UA_Node *node;	/* Nodes are immutable. It is not recommended to change nodes in place */
+} Namespace_Entry;
+
+struct Namespace_Entry_Lock {
+	Namespace_Entry *entry;
+};
+
+struct Namespace {
+	UA_UInt32 namespaceId;
+	Namespace_Entry *entries;
+	UA_UInt32 size;
+	UA_UInt32 count;
+	UA_UInt32 sizePrimeIndex;	/* Current size, as an index into the table of primes.  */
+};
+
+/* The tombstone (entry.node == 0x01) indicates that an entry was deleted at the position in the
+   hash-map. This is information is used to decide whether the entire table shall be rehashed so
+   that entries are found faster. */
+#define ENTRY_EMPTY UA_NULL
+#define ENTRY_TOMBSTONE 0x01
 
 /* The central data structure is a hash-map of UA_Node objects. Entry lookup via Algorithm D from
    Knuth's TAOCP (no linked lists here). Table of primes and mod-functions are from libiberty
-   (licensed under LGPL) */
-
-typedef UA_UInt32 hash_t;
+   (licensed under LGPL) */ typedef UA_UInt32 hash_t;
 struct prime_ent {
 	hash_t prime;
 	hash_t inv;
@@ -22,7 +39,8 @@ struct prime_ent {
 	hash_t shift;
 };
 
-static struct prime_ent const prime_tab[] = {
+static
+struct prime_ent const prime_tab[] = {
 	{7, 0x24924925, 0x9999999b, 2},
 	{13, 0x3b13b13c, 0x745d1747, 3},
 	{31, 0x08421085, 0x1a7b9612, 4},
@@ -183,61 +201,56 @@ static inline hash_t mod_m2(hash_t h, const Namespace * ns) {
 	return 1 + mod_1(h, p->prime - 2, p->inv_m2, p->shift);
 }
 
-static inline void clear_slot(Namespace * ns, Namespace_Entry * slot) {
-	if(slot->node == UA_NULL)
+static inline void clear_entry(Namespace * ns, Namespace_Entry * entry) {
+	if(entry->node == UA_NULL)
 		return;
 
-#ifdef MULTITHREADING
-	pthread_rwlock_wrlock((pthread_rwlock_t *) slot->lock);	/* Get write lock. */
-#endif
-
-	switch (slot->node->nodeClass) {
+	switch (entry->node->nodeClass) {
 	case UA_NODECLASS_OBJECT:
-		UA_ObjectNode_delete((UA_ObjectNode *) slot->node);
+		UA_ObjectNode_delete((UA_ObjectNode *) entry->node);
 		break;
 	case UA_NODECLASS_VARIABLE:
-		UA_VariableNode_delete((UA_VariableNode *) slot->node);
+		UA_VariableNode_delete((UA_VariableNode *) entry->node);
 		break;
 	case UA_NODECLASS_METHOD:
-		UA_MethodNode_delete((UA_MethodNode *) slot->node);
+		UA_MethodNode_delete((UA_MethodNode *) entry->node);
 		break;
 	case UA_NODECLASS_OBJECTTYPE:
-		UA_ObjectTypeNode_delete((UA_ObjectTypeNode *) slot->node);
+		UA_ObjectTypeNode_delete((UA_ObjectTypeNode *) entry->node);
 		break;
 	case UA_NODECLASS_VARIABLETYPE:
-		UA_VariableTypeNode_delete((UA_VariableTypeNode *) slot->node);
+		UA_VariableTypeNode_delete((UA_VariableTypeNode *) entry->node);
 		break;
 	case UA_NODECLASS_REFERENCETYPE:
-		UA_ReferenceTypeNode_delete((UA_ReferenceTypeNode *) slot->node);
+		UA_ReferenceTypeNode_delete((UA_ReferenceTypeNode *) entry->node);
 		break;
 	case UA_NODECLASS_DATATYPE:
-		UA_DataTypeNode_delete((UA_DataTypeNode *) slot->node);
+		UA_DataTypeNode_delete((UA_DataTypeNode *) entry->node);
 		break;
 	case UA_NODECLASS_VIEW:
-		UA_ViewNode_delete((UA_ViewNode *) slot->node);
+		UA_ViewNode_delete((UA_ViewNode *) entry->node);
 		break;
 	default:
 		break;
 	}
-	slot->node = UA_NULL;
-
-#ifdef MULTITHREADING
-	pthread_rwlock_destroy((pthread_rwlock_t *) slot->lock);
-	UA_free(slot->lock);
-#endif
-
+	entry->node = UA_NULL;
 }
 
-static inline UA_Int32 find_slot(const Namespace * ns, Namespace_Entry ** slot, const UA_NodeId * nodeid) {
+/* Returns UA_SUCCESS if an entry was found. Otherwise, UA_ERROR is returned and the "entry"
+   argument points to the first free entry under the NodeId. */
+static inline UA_Int32 find_entry(const Namespace * ns, const UA_NodeId * nodeid, Namespace_Entry ** entry) {
 	hash_t h = hash(nodeid);
 	hash_t index = mod(h, ns);
 	UA_UInt32 size = ns->size;
-	Namespace_Entry *entry = &ns->entries[index];
+	Namespace_Entry *e = &ns->entries[index];
 
-	if(entry == UA_NULL)
+	if(e->node == UA_NULL) {
+		*entry = e;
 		return UA_ERROR;
-	if(UA_NodeId_compare(&entry->node->nodeId, nodeid) == UA_EQUAL) {
-		*slot = entry;
+	}
+
+	if(UA_NodeId_compare(&e->node->nodeId, nodeid) == UA_EQUAL) {
+		*entry = e;
 		return UA_SUCCESS;
 	}
 
@@ -247,37 +260,21 @@ static inline UA_Int32 find_slot(const Namespace * ns, Namespace_Entry ** slot,
 		if(index >= size)
 			index -= size;
 
-		entry = &ns->entries[index];
-		if(entry == UA_NULL)
+		e = &ns->entries[index];
+
+		if(e->node == UA_NULL) {
+			*entry = e;
 			return UA_ERROR;
-		if(UA_NodeId_compare(&entry->node->nodeId, nodeid) == UA_EQUAL) {
-			*slot = entry;
+		}
+
+		if(UA_NodeId_compare(&e->node->nodeId, nodeid) == UA_EQUAL) {
+			*entry = e;
 			return UA_SUCCESS;
 		}
 	}
-	return UA_SUCCESS;
-}
-
-/* Always returns an empty slot. This is inevitable if the entries are not completely full. */
-static Namespace_Entry *find_empty_slot(const Namespace * ns, hash_t h) {
-	hash_t index = mod(h, ns);
-	UA_UInt32 size = ns->size;
-	Namespace_Entry *slot = &ns->entries[index];
-
-	if(slot->node == UA_NULL)
-		return slot;
-
-	hash_t hash2 = mod_m2(h, ns);
-	for(;;) {
-		index += hash2;
-		if(index >= size)
-			index -= size;
 
-		slot = &ns->entries[index];
-		if(slot->node == UA_NULL)
-			return slot;
-	}
-	return UA_NULL;
+	/* NOTREACHED */
+	return UA_SUCCESS;
 }
 
 /* The following function changes size of memory allocated for the entries and repeatedly inserts
@@ -285,7 +282,8 @@ static Namespace_Entry *find_empty_slot(const Namespace * ns, hash_t h) {
    hash table must already exist. Remember also that the place of the table entries is changed. If
    memory allocation failures are allowed, this function will return zero, indicating that the
    table could not be expanded. If all goes well, it will return a non-zero value. */
-static UA_Int32 expand(Namespace * ns) {
+static
+UA_Int32 expand(Namespace * ns) {
 	Namespace_Entry *nentries;
 	int32_t nsize;
 	UA_UInt32 nindex;
@@ -312,8 +310,9 @@ static UA_Int32 expand(Namespace * ns) {
 	Namespace_Entry *p = oentries;
 	do {
 		if(p->node != UA_NULL) {
-			Namespace_Entry *q = find_empty_slot(ns, hash(&p->node->nodeId));
-			*q = *p;
+			Namespace_Entry *e;
+			find_entry(ns, &p->node->nodeId, &e);	/* We know this returns an empty entry here */
+			*e = *p;
 		}
 		p++;
 	} while(p < olimit);
@@ -326,29 +325,23 @@ static UA_Int32 expand(Namespace * ns) {
 /* Exported functions */
 /**********************/
 
-UA_Int32 Namespace_create(Namespace ** result, UA_UInt32 size) {
-	Namespace *ns = UA_NULL;
-	UA_UInt32 sizePrimeIndex = higher_prime_index(size);
-	size = prime_tab[sizePrimeIndex].prime;
-
+UA_Int32 Namespace_new(Namespace ** result, UA_UInt32 size, UA_UInt32 namespaceId) {
+	Namespace *ns;
 	if(UA_alloc((void **)&ns, sizeof(Namespace)) != UA_SUCCESS)
 		return UA_ERR_NO_MEMORY;
 
+	UA_UInt32 sizePrimeIndex = higher_prime_index(size);
+	size = prime_tab[sizePrimeIndex].prime;
 	if(UA_alloc((void **)&ns->entries, sizeof(Namespace_Entry) * size) != UA_SUCCESS) {
 		UA_free(ns);
 		return UA_ERR_NO_MEMORY;
 	}
 
-	/**
-	   set entries to zero:
-	   ns->entries[i].lock = UA_NUll;
-	   ns->entries[i].node = UA_NULL;
-	*/
+	/* set entries to zero */
 	memset(ns->entries, 0, size * sizeof(Namespace_Entry));
 
-	ns->size = size;
-	ns->count = 0;
-	ns->sizePrimeIndex = sizePrimeIndex;
+	*ns = (Namespace) {
+	namespaceId, ns->entries, size, 0, sizePrimeIndex};
 	*result = ns;
 	return UA_SUCCESS;
 }
@@ -357,7 +350,7 @@ static void Namespace_clear(Namespace * ns) {
 	UA_UInt32 size = ns->size;
 	Namespace_Entry *entries = ns->entries;
 	for(UA_UInt32 i = 0; i < size; i++)
-		clear_slot(ns, &entries[i]);
+		clear_entry(ns, &entries[i]);
 	ns->count = 0;
 }
 
@@ -382,149 +375,80 @@ void Namespace_delete(Namespace * ns) {
 	UA_free(ns);
 }
 
-UA_Int32 Namespace_insert(Namespace * ns, UA_Node * node) {
+UA_Int32 Namespace_insert(Namespace * ns, const UA_Node * node) {
 	if(ns->size * 3 <= ns->count * 4) {
 		if(expand(ns) != UA_SUCCESS)
 			return UA_ERROR;
 	}
 
-	hash_t h = hash(&node->nodeId);
-	Namespace_Entry *slot = find_empty_slot(ns, h);
+	Namespace_Entry *entry;
+	UA_Int32 found = find_entry(ns, &node->nodeId, &entry);
 
-#ifdef MULTITHREADING
-	if(UA_alloc((void **)&slot->lock, sizeof(pthread_rwlock_t)) != UA_SUCCESS)
-		return UA_ERR_NO_MEMORY;
-	pthread_rwlock_init((pthread_rwlock_t *) slot->lock, NULL);
-#endif
+	if(found == UA_SUCCESS)
+		return UA_ERROR;	/* There is already an entry for that nodeid */
 
-	slot->node = node;
+	entry->node = node;
 	ns->count++;
 	return UA_SUCCESS;
 }
 
-UA_Int32 Namespace_insertUnique(Namespace * ns, UA_Node * node, UA_NodeId * new_nodeid) {
+UA_Int32 Namespace_insertUnique(Namespace * ns, UA_Node * node) {
 	if(ns->size * 3 <= ns->count * 4) {
 		if(expand(ns) != UA_SUCCESS)
 			return UA_ERROR;
 	}
-
 	// find unoccupied numeric nodeid
-	new_nodeid->identifier.numeric = ns->count;
-	Namespace_Entry *slot = UA_NULL;
+	node->nodeId.namespace = ns->namespaceId;
+	node->nodeId.encodingByte = UA_NODEIDTYPE_NUMERIC;
+	node->nodeId.identifier.numeric = ns->count;
 
-	hash_t h = hash(new_nodeid);
+	hash_t h = hash(&node->nodeId);
 	hash_t hash2 = mod_m2(h, ns);
 	UA_UInt32 size = ns->size;
 
-	// change integer pseudo-randomly until a free slot is found
+	// advance integer (hash) until a free entry is found
+	Namespace_Entry *entry = UA_NULL;
 	while(1) {
-		if(find_slot(ns, &slot, new_nodeid) != UA_SUCCESS)
+		if(find_entry(ns, &node->nodeId, &entry) != UA_SUCCESS)
 			break;
-		new_nodeid->identifier.numeric += hash2;
-		if(new_nodeid->identifier.numeric >= size)
-			new_nodeid->identifier.numeric -= size;
+		node->nodeId.identifier.numeric += hash2;
+		if(node->nodeId.identifier.numeric >= size)
+			node->nodeId.identifier.numeric -= size;
 	}
 
-#ifdef MULTITHREADING
-	if(UA_alloc((void **)&slot->lock, sizeof(pthread_rwlock_t)) != UA_SUCCESS)
-		return UA_ERR_NO_MEMORY;
-	pthread_rwlock_init((pthread_rwlock_t *) slot->lock, NULL);
-#endif
-
-	slot->node = node;
+	entry->node = node;
 	ns->count++;
 	return UA_SUCCESS;
 }
 
-UA_Int32 Namespace_contains(Namespace * ns, UA_NodeId * nodeid) {
-	Namespace_Entry *slot;
-	return find_slot(ns, &slot, nodeid);
-}
-
-UA_Int32 Namespace_get(Namespace const *ns, const UA_NodeId * nodeid, UA_Node const **result, Namespace_Lock ** lock) {
-	Namespace_Entry *slot;
-	if(find_slot(ns, &slot, nodeid) != UA_SUCCESS)
-		return UA_ERROR;
-
-#ifdef MULTITHREADING
-	if(pthread_rwlock_rdlock((pthread_rwlock_t *) slot->lock) != 0)
-		return UA_ERROR;
-	*lock = slot->lock;
-#endif
-
-	*result = slot->node;
-	return UA_SUCCESS;
-}
-
-UA_Int32 Namespace_getWritable(const Namespace * ns, const UA_NodeId * nodeid, UA_Node ** result, Namespace_Lock ** lock) {
-	Namespace_Entry *slot;
-	if(find_slot(ns, &slot, nodeid) != UA_SUCCESS)
-		return UA_ERROR;
-
-#ifdef MULTITHREADING
-	if(pthread_rwlock_wrlock((pthread_rwlock_t *) slot->lock) != 0)
-		return UA_ERROR;
-	*lock = slot->lock;
-#endif
-
-	*result = slot->node;
-	return UA_SUCCESS;
-}
-
-#ifdef MULTITHREADING
-static inline void release_context_walker(void *lock) {
-	pthread_rwlock_unlock(lock);
+UA_Int32 Namespace_contains(const Namespace * ns, const UA_NodeId * nodeid) {
+	Namespace_Entry *entry;
+	return find_entry(ns, nodeid, &entry);
 }
-#endif
-
-UA_Int32 Namespace_transactionGet(Namespace * ns, Namespace_TransactionContext * tc, const UA_NodeId * nodeid, UA_Node ** const result, Namespace_Lock ** lock) {
-	Namespace_Entry *slot;
-	if(find_slot(ns, &slot, nodeid) != UA_SUCCESS)
-		return UA_ERROR;
 
-#ifdef MULTITHREADING
-	if(pthread_rwlock_tryrdlock((pthread_rwlock_t *) slot->lock) != 0) {
-		/* Transaction failed. Release all acquired locks and bail out. */
-		UA_list_destroy((UA_list_List *) tc, release_context_walker);
+UA_Int32 Namespace_get(Namespace const *ns, const UA_NodeId * nodeid, UA_Node const **result,
+					   Namespace_Entry_Lock ** lock) {
+	Namespace_Entry *entry;
+	if(find_entry(ns, nodeid, &entry) != UA_SUCCESS)
 		return UA_ERROR;
-	}
-	UA_list_addPayloadToBack((UA_list_List *) tc, slot->lock);
-	*lock = slot->lock;
-#endif
 
-	*result = slot->node;
+	*result = entry->node;
 	return UA_SUCCESS;
 }
 
-UA_Int32 Namespace_transactionGetWritable(Namespace * ns, Namespace_TransactionContext * tc, const UA_NodeId * nodeid, UA_Node ** result, Namespace_Lock ** lock) {
-	Namespace_Entry *slot;
-	if(find_slot(ns, &slot, nodeid) != UA_SUCCESS)
-		return UA_ERROR;
-
-#ifdef MULTITHREADING
-	if(pthread_rwlock_trywrlock((pthread_rwlock_t *) slot->lock) != 0) {
-		/* Transaction failed. Release all acquired locks and bail out. */
-		UA_list_destroy((UA_list_List *) tc, release_context_walker);
+UA_Int32 Namespace_remove(Namespace * ns, const UA_NodeId * nodeid) {
+	Namespace_Entry *entry;
+	if(find_entry(ns, nodeid, &entry) != UA_SUCCESS)
 		return UA_ERROR;
-	}
-	UA_list_addPayloadToBack((UA_list_List *) tc, slot->lock);
-	*lock = slot->lock;
-#endif
-
-	*result = slot->node;
-	return UA_SUCCESS;
-}
 
-void Namespace_remove(Namespace * ns, UA_NodeId * nodeid) {
-	Namespace_Entry *slot;
-	if(find_slot(ns, &slot, nodeid) != UA_SUCCESS)
-		return;
 	// TODO: Check if deleting the node makes the Namespace inconsistent.
-	clear_slot(ns, slot);
+	clear_entry(ns, entry);
 
 	/* Downsize the hashmap if it is very empty */
 	if(ns->count * 8 < ns->size && ns->size > 32)
 		expand(ns);
+
+	return UA_SUCCESS;
 }
 
 UA_Int32 Namespace_iterate(const Namespace * ns, Namespace_nodeVisitor visitor) {
@@ -536,3 +460,7 @@ UA_Int32 Namespace_iterate(const Namespace * ns, Namespace_nodeVisitor visitor)
 	}
 	return UA_SUCCESS;
 }
+
+void Namespace_Entry_Lock_release(Namespace_Entry_Lock * lock) {
+	;
+}

+ 50 - 57
src/ua_namespace.h

@@ -9,47 +9,24 @@
 #define _XOPEN_SOURCE 500
 #define __USE_UNIX98
 #include <pthread.h>
-typedef struct pthread_rwlock_t Namespace_Lock;
-#else
-typedef void Namespace_Lock;
 #endif
 
-static inline void Namespace_Lock_release(Namespace_Lock * lock) {
-#ifdef MULTITHREADING
-	pthread_rwlock_unlock((pthread_rwlock_t *) lock);
-#endif
-}
+struct Namespace;
+typedef struct Namespace Namespace;
 
-/* Poor-man's transactions: If we need multiple locks and at least one of them is a writelock
-   ("transaction"), a deadlock can be introduced in conjunction with a second thread.
+struct Namespace_Entry_Lock;
+typedef struct Namespace_Entry_Lock Namespace_Entry_Lock;
+void Namespace_Entry_Lock_release(Namespace_Entry_Lock * lock);
 
-   Convention: All nodes in a transaction (read and write) must be locked before the first write.
-   If one write-lock cannot be acquired immediately, bail out and restart the transaction. A
-   Namespace_TransactionContext is currently only a linked list of the acquired locks. More advanced
-   transaction mechanisms will be established once the runtime behavior can be observed. */
-typedef UA_list_List Namespace_TransactionContext;
-UA_Int32 Namespace_TransactionContext_init(Namespace_TransactionContext * tc);
+struct Namespace_Transaction;
+typedef struct Namespace_Transaction Namespace_Transaction;
 
-/* Each namespace is a hash-map of NodeIds to Nodes. Every entry in the hashmap consists of a
-   pointer to a read-write lock and a pointer to the Node. */
-typedef struct Namespace_Entry {
-#ifdef MULTITHREADING
-	Namespace_Lock *lock;	/* locks are heap-allocated */
-#endif
-	UA_Node *node;
-} Namespace_Entry;
-
-typedef struct Namespace {
-	UA_Int32 namespaceId;
-	UA_String namespaceUri;
-	Namespace_Entry *entries;
-	UA_UInt32 size;
-	UA_UInt32 count;
-	UA_UInt32 sizePrimeIndex;	/* Current size, as an index into the table of primes.  */
-} Namespace;
+/*************/
+/* Namespace */
+/*************/
 
 /** @brief Create a new namespace */
-UA_Int32 Namespace_create(Namespace ** result, UA_UInt32 size);
+UA_Int32 Namespace_new(Namespace ** result, UA_UInt32 size, UA_UInt32 namespaceId);
 
 /** @brief Delete all nodes in the namespace */
 void Namespace_empty(Namespace * ns);
@@ -57,42 +34,58 @@ void Namespace_empty(Namespace * ns);
 /** @brief Delete the namespace and all nodes in it */
 void Namespace_delete(Namespace * ns);
 
-/** @brief Insert a new node into the namespace */
-UA_Int32 Namespace_insert(Namespace * ns, UA_Node * node);
+/** @brief Insert a new node into the namespace. Abort an entry with the same
+	NodeId is already present */
+UA_Int32 Namespace_insert(Namespace * ns, const UA_Node * node);
 
-/** @brief Find an unused (numeric) NodeId in the namespace, create a slot under
- * the NodeId and return both */
-UA_Int32 Namespace_insertUnique(Namespace * ns, UA_Node * node, UA_NodeId * new_nodeid); 
+/** @brief Insert a new node or replace an existing node if an entry has the same NodeId. */
+UA_Int32 Namespace_insertOrReplace(Namespace * ns, const UA_Node * node);
+
+/** @brief Find an unused (numeric) NodeId in the namespace and insert the node.
+	The node is modified to contain the new nodeid after insertion. */
+UA_Int32 Namespace_insertUnique(Namespace * ns, UA_Node * node);
 
 /** @brief Remove a node from the namespace */
-void Namespace_remove(Namespace * ns, UA_NodeId * nodeid);
+UA_Int32 Namespace_remove(Namespace * ns, const UA_NodeId * nodeid);
 
 /** @brief Tests whether the namespace contains an entry for a given NodeId */
-UA_Int32 Namespace_contains(Namespace * ns, UA_NodeId * nodeid);
+UA_Int32 Namespace_contains(const Namespace * ns, const UA_NodeId * nodeid);
 
 /** @brief Retrieve a node (read-only) from the namespace. Nodes are identified
 	by their NodeId. After the Node is no longer used, the lock needs to be
 	released. */
-UA_Int32 Namespace_get(Namespace const *ns, const UA_NodeId * nodeid, UA_Node const **result, Namespace_Lock ** lock);
-
-/** @brief Retrieve a node (read and write) from the namespace. Nodes are
-	identified by their NodeId. After the Node is no longer used, the lock needs
-	to be released. */
-UA_Int32 Namespace_getWritable(Namespace const *ns, const UA_NodeId * nodeid, UA_Node ** result, Namespace_Lock ** lock);
-
-/** @brief Retrieve a node (read-only) as part of a transaction. If multiples
-	nodes are to be retrieved as part of a transaction, the transaction context
-	needs to be specified. */
-UA_Int32 Namespace_transactionGet(Namespace * ns, Namespace_TransactionContext * tc, const UA_NodeId * nodeid, UA_Node ** const result, Namespace_Lock ** lock);
-
-/** @brief Retrieve a node (read and write) as part of a transaction. If
-	multiples nodes are to be retrieved as part of a transaction, the
-	transaction context needs to be specified. */
-UA_Int32 Namespace_transactionGetWritable(Namespace * ns, Namespace_TransactionContext * tc, const UA_NodeId * nodeid, UA_Node ** result, Namespace_Lock ** lock);
+UA_Int32 Namespace_get(Namespace const *ns, const UA_NodeId * nodeid, UA_Node const **result,
+					   Namespace_Entry_Lock ** lock);
 
 typedef void (*Namespace_nodeVisitor) (UA_Node const *node);
 
 /** @brief Iterate over all nodes in a namespace */
 UA_Int32 Namespace_iterate(const Namespace * ns, Namespace_nodeVisitor visitor);
 
+/****************/
+/* Transactions */
+/****************/
+
+/** @brief Create a transaction that operates on a single namespace */
+UA_Int32 Namespace_Transaction_new(Namespace * ns, Namespace_Transaction ** result);
+
+/** @brief Insert a new node into the namespace as part of a transaction */
+UA_Int32 Namespace_Transaction_enqueueInsert(Namespace_Transaction * t, const UA_Node * node);
+
+/** @brief Insert a new node or replace an existing node as part of a transaction */
+UA_Int32 Namespace_Transaction_enqueueInsertOrReplace(Namespace_Transaction * t, const UA_Node * node);
+
+/** @brief Find an unused (numeric) NodeId in the namespace and insert the node as
+	part of a transaction */
+UA_Int32 Namespace_Transaction_enqueueInsertUnique(Namespace_Transaction * t, UA_Node * node);
+
+/** @brief Remove a node from the namespace as part of a transaction */
+UA_Int32 Namespace_Transaction_enqueueRemove(Namespace_Transaction * t, const UA_NodeId * nodeid);
+
+/** @brief Executes a transaction and returns the status */
+UA_Int32 Namespace_Transaction_commit(Namespace_Transaction * t);
+
+/** @brief Frees the transaction and deletes all the member objects */
+UA_Int32 Namespace_Transaction_delete(Namespace_Transaction * t);
+
 #endif /* __NAMESPACE_H */

+ 86 - 0
src/ua_namespace_transactions.c

@@ -0,0 +1,86 @@
+#include "ua_namespace.h"
+#include "ua_list.h"
+
+/**************************/
+/* Internal Functionality */
+/**************************/
+
+enum NAMESPACE_TRANSACTION_ACTIONTYPE_enum {
+	NAMESPACE_TRANSACTION_ACTIONTYPE_INSERT = 0x00,
+	NAMESPACE_TRANSACTION_ACTIONTYPE_INSERTREPLACE = 0x01,
+	NAMESPACE_TRANSACTION_ACTIONTYPE_INSERTUNIQUE = 0x02,
+	NAMESPACE_TRANSACTION_ACTIONTYPE_REMOVE = 0x03,
+};
+
+typedef struct Namespace_Transaction_Action {
+	UA_SLIST_ENTRY(Namespace_Transaction_Action) next;	// next entry in the list
+	union {
+		UA_Node *insert;
+		const UA_NodeId *remove;
+	} obj;
+	UA_Byte action;	// from NAMESPACE_TRANSACTION_ACTIONTYPE_enum
+} Namespace_Transaction_Action;
+
+struct Namespace_Transaction {
+	UA_SLIST_HEAD(Namespace_Transaction_Actions, Namespace_Transaction_Action) actions;
+	Namespace *ns;
+};
+
+/**********************/
+/* Exported Functions */
+/**********************/
+
+UA_Int32 Namespace_Transaction_new(Namespace * ns, Namespace_Transaction ** result) {
+	Namespace_Transaction *new_ts;
+	if(UA_alloc((void **)&new_ts, sizeof(Namespace_Transaction)) != UA_SUCCESS)
+		return UA_ERROR;
+
+	new_ts->ns = ns;
+	UA_SLIST_INIT(&new_ts->actions);
+	*result = new_ts;
+	return UA_SUCCESS;
+}
+
+UA_Int32 Namespace_Transaction_enqueueInsert(Namespace_Transaction * t, const UA_Node * node) {
+	Namespace_Transaction_Action *new_action;
+	UA_alloc((void **)&new_action, sizeof(Namespace_Transaction_Action));
+	new_action->obj.insert = (UA_Node *)node;
+	new_action->action = NAMESPACE_TRANSACTION_ACTIONTYPE_INSERT;
+	UA_SLIST_INSERT_HEAD(&t->actions, new_action, next);
+	return UA_SUCCESS;
+}
+
+UA_Int32 Namespace_Transaction_enqueueInsertOrReplace(Namespace_Transaction * t, const UA_Node * node) {
+	Namespace_Transaction_Action *new_action;
+	UA_alloc((void **)&new_action, sizeof(Namespace_Transaction_Action));
+	new_action->obj.insert = (UA_Node *)node;
+	new_action->action = NAMESPACE_TRANSACTION_ACTIONTYPE_INSERTREPLACE;
+	UA_SLIST_INSERT_HEAD(&t->actions, new_action, next);
+	return UA_SUCCESS;
+}
+
+UA_Int32 Namespace_Transaction_enqueueInsertUnique(Namespace_Transaction * t, UA_Node * node) {
+	Namespace_Transaction_Action *new_action;
+	UA_alloc((void **)&new_action, sizeof(Namespace_Transaction_Action));
+	new_action->obj.insert = node;
+	new_action->action = NAMESPACE_TRANSACTION_ACTIONTYPE_INSERTUNIQUE;
+	UA_SLIST_INSERT_HEAD(&t->actions, new_action, next);
+	return UA_SUCCESS;
+}
+
+UA_Int32 Namespace_Transaction_enqueueRemove(Namespace_Transaction * t, const UA_NodeId * nodeid) {
+	Namespace_Transaction_Action *new_action;
+	UA_alloc((void **)&new_action, sizeof(Namespace_Transaction_Action));
+	new_action->obj.remove = nodeid;
+	new_action->action = NAMESPACE_TRANSACTION_ACTIONTYPE_REMOVE;
+	UA_SLIST_INSERT_HEAD(&t->actions, new_action, next);
+	return UA_SUCCESS;
+}
+
+UA_Int32 Namespace_Transaction_commit(Namespace_Transaction * t) {
+	return UA_ERROR; // TODO not yet implemented
+}
+
+UA_Int32 Namespace_Transaction_delete(Namespace_Transaction * t) {
+	return UA_SUCCESS; // TODO
+}

+ 2 - 2
src/ua_services_attribute.c

@@ -50,7 +50,7 @@ static UA_DataValue *service_read_node(Application * app, const UA_ReadValueId *
 	DBG_VERBOSE(UA_String_printf(",namespaceUri=", &(ns->namespaceUri)));
 
 	UA_Node const *node = UA_NULL;
-	Namespace_Lock *lock = UA_NULL;
+	Namespace_Entry_Lock *lock = UA_NULL;
 
 	DBG_VERBOSE(UA_NodeId_printf("service_read_node - search for ", &(id->nodeId)));
 	UA_Int32 result = Namespace_get(ns, &(id->nodeId), &node, &lock);
@@ -175,7 +175,7 @@ static UA_DataValue *service_read_node(Application * app, const UA_ReadValueId *
 		break;
 	}
 
-	Namespace_Lock_release(lock);
+	Namespace_Entry_Lock_release(lock);
 
 	if(retval != UA_SUCCESS) {
 		v->encodingMask = UA_DATAVALUE_ENCODINGMASK_STATUSCODE;

+ 5 - 5
src/ua_services_nodemanagement.c

@@ -26,16 +26,16 @@ static UA_AddNodesResult * addSingleNode(Application *app, UA_AddNodesItem *item
 	if(nodeid_isnull) ns = parent_ns;
 	else ns = UA_indexedList_findValue(app->namespaces, item->requestedNewNodeId.nodeId.namespace);
 
-	if(ns == UA_NULL || ns->namespaceId == 0) {
+	if(ns == UA_NULL || item->requestedNewNodeId.nodeId.namespace == 0) {
 		result->statusCode = UA_STATUSCODE_BADNODEIDREJECTED;
 		return result;
 	}
 
 	UA_Int32 status = UA_SUCCESS;
-	UA_Node *parent;
-	Namespace_Lock *parent_lock = UA_NULL;
+	const UA_Node *parent;
+	Namespace_Entry_Lock *parent_lock = UA_NULL;
 
-	CHECKED_ACTION(Namespace_getWritable(parent_ns, &item->parentNodeId.nodeId, &parent, &parent_lock),
+	CHECKED_ACTION(Namespace_get(parent_ns, &item->parentNodeId.nodeId, &parent, &parent_lock),
 				   result->statusCode = UA_STATUSCODE_BADPARENTNODEIDINVALID, ret);
 
 	if(!nodeid_isnull && Namespace_contains(ns, &item->requestedNewNodeId.nodeId)) {
@@ -62,7 +62,7 @@ static UA_AddNodesResult * addSingleNode(Application *app, UA_AddNodesItem *item
 	 */
 
  ret:
-	Namespace_Lock_release(parent_lock);
+	Namespace_Entry_Lock_release(parent_lock);
 	return result;
 }
 

+ 5 - 5
tests/check_namespace.c

@@ -7,7 +7,7 @@
 
 START_TEST(test_Namespace) {
 	Namespace *ns = UA_NULL;
-	Namespace_create(&ns, 512);
+	Namespace_new(&ns, 512, 99);
 	Namespace_delete(ns);
 }
 END_TEST
@@ -23,10 +23,10 @@ UA_Int32 createNode(UA_Node** p, UA_Int16 nsid, UA_Int32 id) {
 START_TEST(findNodeInNamespaceWithSingleEntry) {
 	// given
 	Namespace *ns;
-	Namespace_create(&ns, 512);
+	Namespace_new(&ns, 512, 99);
 	UA_Node* n1; createNode(&n1,0,2253); Namespace_insert(ns,n1);
 	const UA_Node* nr = UA_NULL;
-	Namespace_Lock* nl = UA_NULL;
+	Namespace_Entry_Lock* nl = UA_NULL;
 	UA_Int32 retval;
 	// when
 	retval = Namespace_get(ns,&(n1->nodeId),&nr,&nl);
@@ -41,12 +41,12 @@ END_TEST
 START_TEST(findNodeInNamespaceWithTwoEntries) {
 	// given
 	Namespace *ns;
-	Namespace_create(&ns, 512);
+	Namespace_new(&ns, 512, 99);
 	UA_Node* n1; createNode(&n1,0,2253); Namespace_insert(ns,n1);
 	UA_Node* n2; createNode(&n2,0,2255); Namespace_insert(ns,n2);
 
 	const UA_Node* nr = UA_NULL;
-	Namespace_Lock* nl = UA_NULL;
+	Namespace_Entry_Lock* nl = UA_NULL;
 	UA_Int32 retval;
 	// when
 	retval = Namespace_get(ns,&(n2->nodeId),&nr,&nl);