public RecursiveNodeMutexes lockAndGetRecursively(String hostname, Optional<Duration> timeout) {
        TimeBudget budget = TimeBudget.fromNow(clock, timeout.orElse(Duration.ofMinutes(2)));
        Set<Node> children = new HashSet<>(list().childrenOf(hostname).asList());
        Optional<Node> node = node(hostname);

        int attempts = 5; // We'll retry locking the whole list of children this many times, in case new children appear.
        for (int attempt = 0; attempt < attempts; attempt++) {
            NodeMutexes mutexes = null;
            Mutex unallocatedLock = null;
            try {
                // First, we lock all the children, and the host; then we take the allocation lock to ensure our snapshot is valid.
                List<Node> nodes = new ArrayList<>(children.size() + 1);
                nodes.addAll(children);
                node.ifPresent(nodes::add);
                mutexes = lockAndGetAll(nodes, budget.timeLeftOrThrow());
                unallocatedLock = db.lockInactive(budget.timeLeftOrThrow().get());
                RecursiveNodeMutexes recursive = new RecursiveNodeMutexes(hostname, mutexes, unallocatedLock);
                Set<Node> freshChildren = list().childrenOf(hostname).asSet();
                Optional<Node> freshNode = recursive.parent.map(NodeMutex::node);
                if (children.equals(freshChildren) && node.equals(freshNode)) {
                    // No new nodes have appeared, and none will now, so we have a consistent snapshot.
                    if (node.isEmpty() && ! children.isEmpty())
                        throw new IllegalStateException("node '" + hostname + "' was not found, but it has children: " + children);

                    mutexes = null;
                    unallocatedLock = null;
                    return recursive;
                }
                else {
                    // New nodes have appeared, so we need to let go of the locks and try again with the new set of nodes.
                    children = freshChildren;
                    node = freshNode;
                }
            }
            finally {
                if (unallocatedLock != null) unallocatedLock.close();
                if (mutexes != null) mutexes.close();
            }
        }
        throw new IllegalStateException("giving up (after " + attempts + " attempts) fetching an up to " +
                                        "date recursive node set under lock for node " + hostname);
    }
Locks the children of the given node, the node itself, and finally takes the unallocated lock.
<br>
When taking multiple locks, it's crucial that we always take them in the same order, to avoid deadlocks.
We want to take the most contended locks last, so that we don't block other operations for longer than necessary.
This method does that, by first taking the locks for any children the given node may have, and then the node itself.
(This is enforced by taking host locks after tenant node locks, in {@link #lockAndGetAll(Collection, Optional)}.)
Finally, the allocation lock is taken, to ensure no new children are added while we hold this snapshot.
Unfortunately, since that lock is taken last, we may detect new nodes after taking it, and then we have to retry.
Closing the returned {@link RecursiveNodeMutexes} will release all the locks, and the locks should not be closed elsewhere.
node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java