Merge pull request #1405 from capnproto/jlee/fix-concurrency-limiter-stack-use

HTTP concurrency limiter: Avoid unnecessary recursion
diff --git a/c++/src/kj/table-test.c++ b/c++/src/kj/table-test.c++
index 57b09b2..77f6d9c 100644
--- a/c++/src/kj/table-test.c++
+++ b/c++/src/kj/table-test.c++
@@ -972,6 +972,27 @@
   }
 }
 
+KJ_TEST("TreeIndex clear() leaves tree in valid state") {
+  // A test which ensures that calling clear() does not break the internal state of a TreeIndex.
+  // It used to be the case that clearing a non-empty tree would leave it thinking that it had room
+  // for one more node than it really did, causing it to write and read beyond the end of its
+  // internal array of nodes.
+  Table<uint, TreeIndex<UintCompare>> table;
+
+  // Insert at least one value to allocate an initial set of tree nodes.
+  table.upsert(1, [](auto&&, auto&&) {});
+  KJ_EXPECT(table.find(1) != nullptr);
+  table.clear();
+
+  // Insert enough values to force writes/reads beyond the end of the tree's internal node array.
+  for (uint i = 0; i < 29; ++i) {
+    table.upsert(i, [](auto&&, auto&&) {});
+  }
+  for (uint i = 0; i < 29; ++i) {
+    KJ_EXPECT(table.find(i) != nullptr);
+  }
+}
+
 KJ_TEST("benchmark: kj::Table<uint, TreeIndex>") {
   constexpr uint SOME_PRIME = BIG_PRIME;
   constexpr uint STEP[] = {1, 2, 4, 7, 43, 127};
diff --git a/c++/src/kj/table.c++ b/c++/src/kj/table.c++
index 070d2ba..4b0e028 100644
--- a/c++/src/kj/table.c++
+++ b/c++/src/kj/table.c++
@@ -335,7 +335,7 @@
     azero(tree, treeCapacity);
     height = 0;
     freelistHead = 1;
-    freelistSize = treeCapacity;
+    freelistSize = treeCapacity - 1;  // subtract one to account for the root node
     beginLeaf = 0;
     endLeaf = 0;
   }