All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.testcontainers.lifecycle.Startables Maven / Gradle / Ivy

There is a newer version: 1.20.3
Show newest version
package org.testcontainers.lifecycle;

import lombok.experimental.UtilityClass;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

@UtilityClass
public class Startables {

    private static final Executor EXECUTOR = Executors.newCachedThreadPool(
        new ThreadFactory() {
            private final AtomicLong COUNTER = new AtomicLong(0);

            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "testcontainers-lifecycle-" + COUNTER.getAndIncrement());
                thread.setDaemon(true);
                return thread;
            }
        }
    );

    /**
     * @see #deepStart(Stream)
     */
    public CompletableFuture deepStart(Collection startables) {
        return deepStart((Iterable) startables);
    }

    /**
     * @see #deepStart(Stream)
     */
    public CompletableFuture deepStart(Iterable startables) {
        return deepStart(StreamSupport.stream(startables.spliterator(), false));
    }

    /**
     * @see #deepStart(Stream)
     */
    public CompletableFuture deepStart(Startable... startables) {
        return deepStart(Arrays.stream(startables));
    }

    /**
     * Start every {@link Startable} recursively and asynchronously and join on the result.
     *
     * Performance note:
     * The method uses and returns {@link CompletableFuture}s to resolve as many {@link Startable}s at once as possible.
     * This way, for the following graph:
     *   / b \
     * a      e
     *     c /
     *     d /
     * "a", "c" and "d" will resolve in parallel, then "b".
     *
     * If we would call blocking {@link Startable#start()}, "e" would wait for "b", "b" for "a", and only then "c", and then "d".
     * But, since "c" and "d" are independent from "a", there is no point in waiting for "a" to be resolved first.
     *
     * @param startables a {@link Stream} of {@link Startable}s to start and scan for transitive dependencies.
     * @return a {@link CompletableFuture} that resolves once all {@link Startable}s have started.
     */
    public CompletableFuture deepStart(Stream startables) {
        return deepStart(new HashMap<>(), startables);
    }

    /**
     *
     * @param started an intermediate storage for already started {@link Startable}s to prevent multiple starts.
     * @param startables a {@link Stream} of {@link Startable}s to start and scan for transitive dependencies.
     */
    private CompletableFuture deepStart(
        Map> started,
        Stream startables
    ) {
        CompletableFuture[] futures = startables
            .sequential()
            .map(it -> {
                // avoid a recursive update in `computeIfAbsent`
                Map> subStarted = new HashMap<>(started);
                CompletableFuture future = started.computeIfAbsent(
                    it,
                    startable -> {
                        return deepStart(subStarted, startable.getDependencies().stream())
                            .thenRunAsync(startable::start, EXECUTOR);
                    }
                );
                started.putAll(subStarted);
                return future;
            })
            .toArray(CompletableFuture[]::new);

        return allOfFailfast(futures);
    }

    private static  CompletableFuture allOfFailfast(CompletableFuture[] futures) {
        CompletableFuture result = CompletableFuture.allOf(futures);
        for (CompletableFuture future : futures) {
            future.whenComplete((t, ex) -> {
                if (ex != null) {
                    result.completeExceptionally(ex);
                }
            });
        }

        return result;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy