|
22 | 22 | import java.io.IOException; |
23 | 23 | import java.io.PrintWriter; |
24 | 24 | import java.nio.ByteBuffer; |
| 25 | +import java.util.ArrayDeque; |
25 | 26 | import java.util.ArrayList; |
26 | 27 | import java.util.Collection; |
27 | 28 | import java.util.Collections; |
| 29 | +import java.util.Deque; |
28 | 30 | import java.util.HashMap; |
29 | 31 | import java.util.HashSet; |
30 | 32 | import java.util.LinkedList; |
@@ -1340,32 +1342,45 @@ private void setupQuota() { |
1340 | 1342 | * @throws IOException |
1341 | 1343 | */ |
1342 | 1344 | void serializeNode(OutputArchive oa, StringBuilder path) throws IOException { |
1343 | | - String pathString = path.toString(); |
1344 | | - DataNode node = getNode(pathString); |
1345 | | - if (node == null) { |
1346 | | - return; |
1347 | | - } |
1348 | | - String[] children = null; |
1349 | | - DataNode nodeCopy; |
1350 | | - synchronized (node) { |
1351 | | - StatPersisted statCopy = new StatPersisted(); |
1352 | | - copyStatPersisted(node.stat, statCopy); |
1353 | | - //we do not need to make a copy of node.data because the contents |
1354 | | - //are never changed |
1355 | | - nodeCopy = new DataNode(node.data, node.acl, statCopy); |
1356 | | - Set<String> childs = node.getChildren(); |
1357 | | - children = childs.toArray(new String[childs.size()]); |
1358 | | - } |
1359 | | - serializeNodeData(oa, pathString, nodeCopy); |
1360 | | - path.append('/'); |
1361 | | - int off = path.length(); |
1362 | | - for (String child : children) { |
1363 | | - // since this is single buffer being resused |
1364 | | - // we need |
1365 | | - // to truncate the previous bytes of string. |
1366 | | - path.delete(off, Integer.MAX_VALUE); |
1367 | | - path.append(child); |
1368 | | - serializeNode(oa, path); |
| 1345 | + // Iterative, explicit-stack pre-order traversal of the subtree rooted |
| 1346 | + // at `path`. This was previously written as recursion with one frame |
| 1347 | + // per znode level, which does not scale to very deep trees; walking the |
| 1348 | + // tree with a heap-allocated stack removes the dependency on tree depth. |
| 1349 | + // |
| 1350 | + // The traversal order (pre-order, children in the same order as |
| 1351 | + // before), the per-node locking, and the emitted records are unchanged, |
| 1352 | + // so the produced snapshot bytes are identical and the on-disk format is |
| 1353 | + // unchanged. The explicit stack holds at most the unvisited frontier of |
| 1354 | + // the tree (a single entry for a chain), all of which the in-memory |
| 1355 | + // DataTree already holds. |
| 1356 | + Deque<String> pathStack = new ArrayDeque<>(); |
| 1357 | + pathStack.push(path.toString()); |
| 1358 | + while (!pathStack.isEmpty()) { |
| 1359 | + String pathString = pathStack.pop(); |
| 1360 | + DataNode node = getNode(pathString); |
| 1361 | + if (node == null) { |
| 1362 | + continue; |
| 1363 | + } |
| 1364 | + String[] children = null; |
| 1365 | + DataNode nodeCopy; |
| 1366 | + synchronized (node) { |
| 1367 | + StatPersisted statCopy = new StatPersisted(); |
| 1368 | + copyStatPersisted(node.stat, statCopy); |
| 1369 | + //we do not need to make a copy of node.data because the contents |
| 1370 | + //are never changed |
| 1371 | + nodeCopy = new DataNode(node.data, node.acl, statCopy); |
| 1372 | + Set<String> childs = node.getChildren(); |
| 1373 | + children = childs.toArray(new String[childs.size()]); |
| 1374 | + } |
| 1375 | + // Called outside the node lock: writeRecord can block and holding |
| 1376 | + // the lock would stall writers (ZOOKEEPER-2201). |
| 1377 | + serializeNodeData(oa, pathString, nodeCopy); |
| 1378 | + // Push children in reverse so that, popped from this LIFO stack, |
| 1379 | + // they are visited in the same order as the previous recursion. |
| 1380 | + String childPathPrefix = pathString + "/"; |
| 1381 | + for (int i = children.length - 1; i >= 0; i--) { |
| 1382 | + pathStack.push(childPathPrefix + children[i]); |
| 1383 | + } |
1369 | 1384 | } |
1370 | 1385 | } |
1371 | 1386 |
|
|
0 commit comments