testcontainers-java icon indicating copy to clipboard operation
testcontainers-java copied to clipboard

JUnit5: Start containers concurrently

Open OLibutzki opened this issue 3 years ago • 9 comments

When using Testcontainers in combination with JUnit5 using @Testcontainers and @Container all the containers are started sequentially.

Especially when there are multiple containers involved it could save some time to start them in parallel.

This feature request is about facilitating the concurrent start of containers.

OLibutzki avatar Feb 09 '22 17:02 OLibutzki

Hi @OLibutzki, thanks for the suggestion, this is correct.

Note that you can work around this issue, if you use Testcontainers directly and without the JUnit5 extension, e.g. like:

@BeforeAll
static void setup() {
  Stream.of(container1, container2).parallel().forEach(GenericContainer::start);
}

In most cases, the direct use of Testcontainers without test framework integration is in no way inferior.

kiview avatar Feb 10 '22 08:02 kiview

@kiview Nice idea!

I just have struggled to get it working with DockerComposeContainer. With an instance of that one, or to be precise, mixing GenericContainer and it, blocks forever leaving me without a clue in which state the worker threads are. Here's a minimal repo for reproduction: https://github.com/bountin/testcontainer-parallel-startup (./gradlew test)

Same behavior with a manual Threadpool (newFixedThreadPool, submit, submit, awaitTermination). It times out the await, even though I can step through both start methods completely in the debugger. Maybe there is some resource that keeps the thread from finally returning?

bountin avatar Feb 12 '22 23:02 bountin

Thanks for the reproducer @bountin.

I can confirm that your example hangs. After adding logback and configuring logging, I could see that Testcontainers seems to hang after starting the socat container (which is used internally by DockerComposeContainer). Since we generally use this pattern without problems, this seems to be explicitly a problem with regards to the implementation of DockerComposeContainer (or the internal SocatContainer). It is also very curious to see, that none of our timeouts catch this.

For now, I would simply recommend against the use of DockerComposeContainer in this way, until we figured out the root cause.

Note that you should also add the exposed ports, so Testcontainers can use a meaningful default wait strategy (but this is unrelated to seeing the hanging behavior).

Edit: It also seems to work fine when the startup is moved out of the static block, e.g. into a test method. So what I can observe is something hanging when we start multiple containers in paralell, with one being DockerComposeContainer, in a static block of a JUnit Jupiter test class.

kiview avatar Feb 14 '22 11:02 kiview

It's possible to use both @Container annotation with a parallel approach. You can implement org.testcontainers.lifecycle.Startable interface that would have several containers inside it. Like this:

public class ParallelContainers implements org.testcontainers.lifecycle.Startable {

    private final GenericContainer container1 = ...;
    private final GenericContainer container2 = ...;

    @Override
    public void start() {
        Stream.of(this.container1, this.container2).parallel().forEach(GenericContainer::start);
    }

    @Override
    public void stop() {
        Stream.of(this.container1, this.container2).parallel().forEach(GenericContainer::stop);
    }

}

And then just use @Container ParallelContainers containers in test classes.

40rn05lyv avatar Aug 06 '22 12:08 40rn05lyv

Hey I am a first time contributor and would love to take a crack at this!

cjmartinez217 avatar Oct 01 '22 16:10 cjmartinez217

If there is any insight on how to approach developing this feature rather than just a work around that would be much appreciated

cjmartinez217 avatar Oct 01 '22 17:10 cjmartinez217

@cjmartinez217, If you still wanting to contribute can I somehow help you? Otherwise I can try to work on the implementation idea provided by @40rn05lyv

Olex1313 avatar Dec 05 '22 19:12 Olex1313

Sorry @cjmartinez217 and @Olex1313 for not responding earlier. This is essentially up for grabs but would require some exploration of JUnit5 extension best practices and a possible API.

I imagine it could be a flag on the @Testcontainers extension annotation. The implementation code could like make use of the Stream.of(this.container1, this.container2).parallel().forEach(GenericContainer::stop); example. Not necessary to implement a new Startable class.

kiview avatar Dec 06 '22 12:12 kiview

@kiview Well sorry for long thinking about the problem, maybe it instead could be a flag on @Container annotation, so in the https://github.com/testcontainers/testcontainers-java/blob/main/modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/TestcontainersExtension.java, we might split containers into 2 groups every time we have to start it, and as you said start ones with the flag with sample of code like this

parallelContainers.parallel().forEach(GenericContainer::start);

?

Olex1313 avatar Dec 11 '22 09:12 Olex1313

@kiview I have tried to come up with a solution and would like to get some review, I am more than happy to contribute if you can help with the review and the direction I need to take.

samed-bicer avatar Feb 18 '23 20:02 samed-bicer

Hi I am new contributor here and am an enthusiast to work with you to crack

Kamveno avatar Sep 25 '23 19:09 Kamveno