testcontainers / testcontainers/testcontainers-java

ConcurrentModificationException using new Startable.dependsOn(startable)

Open
#1,722 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

resolution/acknowledged type/bug
Dominant language
Java
Stars
8.7k
Forks
1.9k
Avg merge
2d 17h
Merged PRs (30d)
9

Description

I ran into an error in one of my TravisCI builds using the new dependsOn mechanism:

    13780 ERROR 🐳 [postgres:9.6.12]  - Could not start container
    java.util.ConcurrentModificationException
    	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1660)
    	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:550)
    	at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
    	at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:517)
    	at org.testcontainers.containers.GenericContainer.applyConfiguration(GenericContainer.java:510)
    	at org.testcontainers.containers.GenericContainer.tryStart(GenericContainer.java:301)
    	at org.testcontainers.containers.GenericContainer.lambda$doStart$0(GenericContainer.java:285)
    	at org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess(Unreliables.java:81)
    	at org.testcontainers.containers.GenericContainer.doStart(GenericContainer.java:283)
    	at org.testcontainers.containers.GenericContainer.start(GenericContainer.java:272)
    	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
    	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654)
    	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    	at java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290)
    	at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:746)
    	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
    	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
    	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
    	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)

My containers are defined like this:

	@Container
	public static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>()
					.withNetworkAliases("testpostgres")
					.withDatabaseName("testdb");
	
    @Container
    public static MicroProfileApplication<?> app = new MicroProfileApplication<>()
                    .withEnv("POSTGRES_HOSTNAME", "testpostgres")
                    .withEnv("POSTGRES_PORT", "5432")
                    .withAppContextRoot("/myservice")
                    .dependsOn(postgres);

Using the latest version of testcontainers (1.12.0), the exception lines up with this bit of code:

    private void applyConfiguration(CreateContainerCmd createCommand) {
        HostConfig hostConfig = buildHostConfig();
        createCommand.withHostConfig(hostConfig);

        // Set up exposed ports (where there are no host port bindings defined)
        ExposedPort[] portArray = exposedPorts.stream()
                .map(ExposedPort::new)
                .toArray(ExposedPort[]::new);  // << --- CONCURRENT MOD EX HERE

It should also be noted that I am starting my containers in parallel like this:

containersToStart.parallelStream().forEach(GenericContainer::start);

which should be OK, since I believe the main value of dependsOn is that it allows us to do parallel container start without needing to worry about dependencies.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in GenericContainer.applyConfiguration at the exposedPorts stream, then trace the dependsOn and parallel GenericContainer.start paths shown in the report. Reproduce the PostgreSQL/application setup with containersToStart.parallelStream().forEach(GenericContainer::start), and consider the issue complete when dependent containers start without ConcurrentModificationException.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, java
Domain
devops, testing
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.