Module Name:    src
Committed By:   ad
Date:           Fri Apr 10 23:43:05 UTC 2020

Modified Files:
        src/common/lib/libc/gen: radixtree.c

Log Message:
PR kern/54979 (radixtree might misbehave if ENOMEM)

- radix_tree_insert_node(): if the insert failed due to ENOMEM, roll back
  any updates made to the tree.

- radix_tree_grow(): either succeed or fail, never make partial adjustments
  to the tree.

- radix_tree_await_memory(): allocate & free the maximum possible number of
  nodes required by any insertion.


To generate a diff of this commit:
cvs rdiff -u -r1.24 -r1.25 src/common/lib/libc/gen/radixtree.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/common/lib/libc/gen/radixtree.c
diff -u src/common/lib/libc/gen/radixtree.c:1.24 src/common/lib/libc/gen/radixtree.c:1.25
--- src/common/lib/libc/gen/radixtree.c:1.24	Fri Apr 10 21:56:41 2020
+++ src/common/lib/libc/gen/radixtree.c	Fri Apr 10 23:43:05 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: radixtree.c,v 1.24 2020/04/10 21:56:41 ad Exp $	*/
+/*	$NetBSD: radixtree.c,v 1.25 2020/04/10 23:43:05 ad Exp $	*/
 
 /*-
  * Copyright (c)2011,2012,2013 YAMAMOTO Takashi,
@@ -112,7 +112,7 @@
 #include <sys/cdefs.h>
 
 #if defined(_KERNEL) || defined(_STANDALONE)
-__KERNEL_RCSID(0, "$NetBSD: radixtree.c,v 1.24 2020/04/10 21:56:41 ad Exp $");
+__KERNEL_RCSID(0, "$NetBSD: radixtree.c,v 1.25 2020/04/10 23:43:05 ad Exp $");
 #include <sys/param.h>
 #include <sys/errno.h>
 #include <sys/pool.h>
@@ -122,7 +122,7 @@ __KERNEL_RCSID(0, "$NetBSD: radixtree.c,
 #include <lib/libsa/stand.h>
 #endif /* defined(_STANDALONE) */
 #else /* defined(_KERNEL) || defined(_STANDALONE) */
-__RCSID("$NetBSD: radixtree.c,v 1.24 2020/04/10 21:56:41 ad Exp $");
+__RCSID("$NetBSD: radixtree.c,v 1.25 2020/04/10 23:43:05 ad Exp $");
 #include <assert.h>
 #include <errno.h>
 #include <stdbool.h>
@@ -335,16 +335,22 @@ radix_tree_init(void)
  * radix_tree_await_memory:
  *
  * after an insert has failed with ENOMEM, wait for memory to become
- * available, so the caller can retry.
+ * available, so the caller can retry.  this needs to ensure that the
+ * maximum possible required number of nodes is available.
  */
 
 void
 radix_tree_await_memory(void)
 {
-	struct radix_tree_node *n;
+	struct radix_tree_node *nodes[RADIX_TREE_MAX_HEIGHT];
+	int i;
 
-	n = pool_cache_get(radix_tree_node_cache, PR_WAITOK);
-	pool_cache_put(radix_tree_node_cache, n);
+	for (i = 0; i < __arraycount(nodes); i++) {
+		nodes[i] = pool_cache_get(radix_tree_node_cache, PR_WAITOK);
+	}
+	while (--i >= 0) {
+		pool_cache_put(radix_tree_node_cache, nodes[i]);
+	}
 }
 
 #endif /* defined(_KERNEL) */
@@ -360,8 +366,8 @@ static uintptr_t
 radix_tree_node_sum(const struct radix_tree_node *n)
 {
 #if RADIX_TREE_PTR_PER_NODE > 16
+	unsigned int i;
 	uintptr_t sum;
-	unsigned i;
 
 	for (i = 0, sum = 0; i < RADIX_TREE_PTR_PER_NODE; i++) {
 		sum |= (uintptr_t)n->n_ptrs[i];
@@ -449,31 +455,39 @@ radix_tree_free_node(struct radix_tree_n
 #endif
 }
 
-static int
+/*
+ * radix_tree_grow:
+ *
+ * increase the height of the tree.
+ */
+
+static __noinline int
 radix_tree_grow(struct radix_tree *t, unsigned int newheight)
 {
 	const unsigned int tagmask = entry_tagmask(t->t_root);
+	struct radix_tree_node *newnodes[RADIX_TREE_MAX_HEIGHT];
+	void *root;
+	int h;
 
-	KASSERT(newheight <= 64 / RADIX_TREE_BITS_PER_HEIGHT);
-	if (t->t_root == NULL) {
+	KASSERT(newheight <= RADIX_TREE_MAX_HEIGHT);
+	if ((root = t->t_root) == NULL) {
 		t->t_height = newheight;
 		return 0;
 	}
-	while (t->t_height < newheight) {
-		struct radix_tree_node *n;
-
-		n = radix_tree_alloc_node();
-		if (n == NULL) {
-			/*
-			 * don't bother to revert our changes.
-			 * the caller will likely retry.
-			 */
+	for (h = t->t_height; h < newheight; h++) {
+		newnodes[h] = radix_tree_alloc_node();
+		if (__predict_false(newnodes[h] == NULL)) {
+			while (--h >= (int)t->t_height) {
+				newnodes[h]->n_ptrs[0] = NULL;
+				radix_tree_free_node(newnodes[h]);
+			}
 			return ENOMEM;
 		}
-		n->n_ptrs[0] = t->t_root;
-		t->t_root = entry_compose(n, tagmask);
-		t->t_height++;
+		newnodes[h]->n_ptrs[0] = root;
+		root = entry_compose(newnodes[h], tagmask);
 	}
+	t->t_root = root;
+	t->t_height = h;
 	return 0;
 }
 
@@ -583,6 +597,52 @@ radix_tree_lookup_ptr(struct radix_tree 
 }
 
 /*
+ * radix_tree_undo_insert_node:
+ *
+ * Undo the effects of a failed insert.  The conditions that led to the
+ * insert may change and it may not be retried.  If the insert is not
+ * retried, there will be no corresponding radix_tree_remove_node() for
+ * this index in the future.  Therefore any adjustments made to the tree
+ * before memory was exhausted must be reverted.
+ */
+
+static __noinline void
+radix_tree_undo_insert_node(struct radix_tree *t, uint64_t idx)
+{
+	struct radix_tree_path path;
+	int i;
+
+	(void)radix_tree_lookup_ptr(t, idx, &path, false, 0);
+	if (path.p_lastidx == RADIX_TREE_INVALID_HEIGHT) {
+		/*
+		 * no nodes were inserted.
+		 */
+		return;
+	}
+	for (i = path.p_lastidx - 1; i >= 0; i--) {
+		struct radix_tree_node ** const pptr =
+		    (struct radix_tree_node **)path_pptr(t, &path, i);
+		struct radix_tree_node *n;
+
+		KASSERT(pptr != NULL);
+		n = entry_ptr(*pptr);
+		KASSERT(n != NULL);
+		if (radix_tree_node_sum(n) != 0) {
+			break;
+		}
+		radix_tree_free_node(n);
+		*pptr = NULL;
+	}
+	/*
+	 * fix up height
+	 */
+	if (i < 0) {
+		KASSERT(t->t_root == NULL);
+		t->t_height = 0;
+	}
+}
+
+/*
  * radix_tree_insert_node:
  *
  * Insert the node at the given index.
@@ -606,7 +666,8 @@ radix_tree_insert_node(struct radix_tree
 	KASSERT(p != NULL);
 	KASSERT(entry_tagmask(entry_compose(p, 0)) == 0);
 	vpp = radix_tree_lookup_ptr(t, idx, NULL, true, 0);
-	if (vpp == NULL) {
+	if (__predict_false(vpp == NULL)) {
+		radix_tree_undo_insert_node(t, idx);
 		return ENOMEM;
 	}
 	KASSERT(*vpp == NULL);

Reply via email to