Skip to content

[Enhancement]: JdbcDatabaseContainer - Improve waitUntilContainerStarted #10360

@AB-xdev

Description

@AB-xdev

Module

Core

Proposal

Currently

protected void waitUntilContainerStarted() {
logger()
.info(
"Waiting for database connection to become available at {} using query '{}'",
getJdbcUrl(),
getTestQueryString()
);
// Repeatedly try and open a connection to the DB and execute a test query
long start = System.nanoTime();
Exception lastConnectionException = null;
while ((System.nanoTime() - start) < TimeUnit.SECONDS.toNanos(startupTimeoutSeconds)) {
if (!isRunning()) {
Thread.sleep(100L);
} else {
try (Connection connection = createConnection(""); Statement statement = connection.createStatement()) {
boolean testQuerySucceeded = statement.execute(this.getTestQueryString());
if (testQuerySucceeded) {
return;
}
} catch (NoDriverFoundException e) {
// we explicitly want this exception to fail fast without retries
throw e;
} catch (Exception e) {
lastConnectionException = e;
// ignore so that we can try again
logger().debug("Failure when trying test query", e);
Thread.sleep(100L);
}
}
}
throw new IllegalStateException(
String.format(
"Container is started, but cannot be accessed by (JDBC URL: %s), please check container logs",
this.getJdbcUrl()
),
lastConnectionException
);
}
behaves very weird and this also impacts performance:

  • It doesn't utilize WaitStrategy and completely ignores/overrides it
  • It constantly tries to query if the container is running or builds database connections (these are very costly in terms of CPU usage)
    • These attempts use a hardcoded throttling value of 100ms and completely ignore the RateLimiter of WaitStrategy

I would propose that you use - as in all other containers - the WaitStrategy.

  • Remove the override/method JdbcDatabaseContainer#waitUntilContainerStarted
  • Create a custom WaitStrategy called JDBCWaitStrategy. It could look like this:
    class JDBCWaitStrategy extends AbstractWaitStrategy
    {
    	@Override
    	protected void waitUntilReady()
    	{
    		if(!(this.waitStrategyTarget instanceof final JdbcDatabaseContainer<?> container))
    		{
    			throw new IllegalArgumentException(
    				"Container must implement JdbcDatabaseContainer");
    		}
    		
    		try
    		{
    			Unreliables.retryUntilTrue(
    				(int)this.startupTimeout.getSeconds(),
    				TimeUnit.SECONDS,
    				() -> this.getRateLimiter().getWhenReady(() -> {
    					try(final Connection connection = container.createConnection("");
    						final Statement statement = connection.createStatement())
    					{
    						return statement.execute(container.getTestQueryString());
    					}
    				})
    			);
    		}
    		catch(final TimeoutException e)
    		{
    			throw new ContainerLaunchException(
    				"JDBCContainer cannot be accessed by (JDBC URL: "
    					+ container.getJdbcUrl()
    					+ "), please check container logs");
    		}
    	}
    }
  • Use the following default WaitStrategy for JDBCDatabaseContainer:
    new WaitAllStrategy()
    	.withStrategy(Wait.defaultWaitStrategy())
    	.withStrategy(new JDBCWaitStrategy())

Full example implementation:

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions