private NodeMutexes lockAndGetAll(Collection<Node> nodes, Optional<Duration> timeout, boolean required) {
        TimeBudget budget = TimeBudget.fromNow(clock, timeout.orElse(Duration.ofMinutes(2)));
        Comparator<Node> universalOrder = (a, b) -> {
            Optional<ApplicationId> idA = applicationIdForLock(a);
            Optional<ApplicationId> idB = applicationIdForLock(b);
            if (idA.isPresent() != idB.isPresent()) return idA.isPresent() ? -1 : 1;    // Allocated nodes first.
            if (a.type() != b.type()) return a.type().compareTo(b.type());              // Tenant nodes first among those.
            if ( ! idA.equals(idB)) return idA.get().compareTo(idB.get());              // Sort primarily by tenant owner id.
            return a.hostname().compareTo(b.hostname());                                // Sort secondarily by hostname.
        };
        NavigableSet<NodeMutex> locked = new TreeSet<>(comparing(NodeMutex::node, universalOrder));
        NavigableSet<Node> unlocked = new TreeSet<>(universalOrder);
        unlocked.addAll(nodes);
        try {
            int attempts = 10; // We'll accept getting the wrong lock at most this many times before giving up.
            for (int attempt = 0; attempt < attempts; ) {
                if (unlocked.isEmpty()) {
                    NodeMutexes mutexes = new NodeMutexes(List.copyOf(locked));
                    locked.clear();
                    return mutexes;
                }

                // If the first node is now earlier in lock order than some other locks we have, we need to close those and re-acquire them.
                Node next = unlocked.pollFirst();
                Set<NodeMutex> outOfOrder = locked.tailSet(new NodeMutex(next, () -> { }), false);
                NodeMutexes.close(outOfOrder);
                for (NodeMutex node : outOfOrder) unlocked.add(node.node());
                outOfOrder.clear();

                boolean nextLockSameAsPrevious = ! locked.isEmpty() && applicationIdForLock(locked.last().node()).equals(applicationIdForLock(next));
                Mutex lock = nextLockSameAsPrevious ? () -> { } : lock(next, budget.timeLeftOrThrow());
                try {
                    Optional<Node> fresh = node(next.hostname());
                    if (fresh.isEmpty()) {
                        if (required) throw new NoSuchNodeException("No node with hostname '" + next.hostname() + "'");
                        continue; // Node is gone; skip to close lock.
                    }

                    if (applicationIdForLock(fresh.get()).equals(applicationIdForLock(next))) {
                        // We held the right lock, so this node is ours now.
                        locked.add(new NodeMutex(fresh.get(), lock));
                        lock = null;
                    }
                    else {
                        // We held the wrong lock, and need to try again.
                        ++attempt;
                        unlocked.add(fresh.get());
                    }
                }
                finally {
                    // If we didn't hold the right lock, we must close the wrong one before we continue.
                    if (lock != null) lock.close();
                }
            }
            throw new IllegalStateException("giving up (after " + attempts + " extra attempts) to lock nodes: " +
                                            nodes.stream().map(Node::hostname).collect(joining(", ")));
        }
        finally {
            // If we didn't manage to lock all nodes, we must close the ones we did lock before we throw.
            NodeMutexes.close(locked);
        }
    }
Locks all nodes in the given list, in a universal order, and returns the locks and nodes.
node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java