Testcontainers version
4.11.0 (develop, 1d329ad1)
Using the latest Testcontainers version?
Yes
Host OS
macOS
Host arch
arm64
.NET version
10
Docker version
Docker Desktop, engine 28.x
What happened?
WaitIndicateReadiness in MongoDbBuilder is not safe across a container restart, in both directions.
// src/Testcontainers.MongoDb/MongoDbBuilder.cs
public async Task<bool> UntilAsync(IContainer container)
{
var (stdout, stderr) = await container.GetLogsAsync(since: container.StoppedTime, timestampsEnabled: false)
.ConfigureAwait(false);
return _count.Equals(Array.Empty<string>()
.Concat(stdout.Split(LineEndings, StringSplitOptions.RemoveEmptyEntries))
.Concat(stderr.Split(LineEndings, StringSplitOptions.RemoveEmptyEntries))
.Count(line => line.Contains("Waiting for connections")));
}
_count is 1 or 2 depending on whether credentials are configured, and the comparison is exact equality over the logs since StoppedTime. After a restart that window covers the previous run as well as the new one, so:
-
Too many matches: the start hangs. The count exceeds _count and the equality can never become true. For a replica set container this surfaces inside InitiateReplicaSetAsync, which awaits this readiness check before initiating. The default wait strategy timeout is one hour, so the caller appears to hang rather than fail.
-
Too few matches, at the wrong time: the start returns early. A single stale Waiting for connections line from the previous run can satisfy the check before the new mongod is listening. The next command then fails with MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017.
Steps to reproduce
Add a restart test to MongoDbContainerTest:
[Fact]
public async Task RestartsSuccessfully()
{
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
await _mongoDbContainer.StopAsync(cts.Token);
await _mongoDbContainer.StartAsync(cts.Token);
var execResult = await _mongoDbContainer.ExecScriptAsync("db.adminCommand({ping:1}).ok;", cts.Token);
Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr);
}
Observed across the existing fixtures:
| Fixture |
Result |
MongoDbNoAuthConfiguration |
fails in ~3s, MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017 |
MongoDbReplicaSetDefaultConfiguration |
hangs until the bound, TimeoutException from WaitStrategy.WaitUntilAsync via InitiateReplicaSetAsync |
MongoDbNamedReplicaSetConfiguration |
same as above |
MongoDbV5Configuration |
same as above |
MongoDbDefaultConfiguration, MongoDbV4Configuration |
pass |
Expected result / actual result
Expected: a stopped container that is started again becomes ready, and the readiness check reflects the current run.
Actual: the readiness check counts log lines from the previous run, so a restart either hangs until the wait strategy times out or is reported ready before mongod is accepting connections.
Additional information
Two things worth deciding together:
- Comparing with
>= instead of exact equality would fix the hang but not the early return, since a stale line still counts.
- Scoping the log window to the current run (rather than
StoppedTime, which refers to the previous stop) addresses both, but I am not sure what the intended semantics of StoppedTime are here.
Found while working on #1722; deliberately kept out of that pull request since it is a separate defect. Happy to open a PR once the preferred direction is clear.
Testcontainers version
4.11.0 (
develop,1d329ad1)Using the latest Testcontainers version?
Yes
Host OS
macOS
Host arch
arm64
.NET version
10
Docker version
Docker Desktop, engine 28.x
What happened?
WaitIndicateReadinessinMongoDbBuilderis not safe across a container restart, in both directions._countis 1 or 2 depending on whether credentials are configured, and the comparison is exact equality over the logs sinceStoppedTime. After a restart that window covers the previous run as well as the new one, so:Too many matches: the start hangs. The count exceeds
_countand the equality can never become true. For a replica set container this surfaces insideInitiateReplicaSetAsync, which awaits this readiness check before initiating. The default wait strategy timeout is one hour, so the caller appears to hang rather than fail.Too few matches, at the wrong time: the start returns early. A single stale
Waiting for connectionsline from the previous run can satisfy the check before the newmongodis listening. The next command then fails withMongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017.Steps to reproduce
Add a restart test to
MongoDbContainerTest:Observed across the existing fixtures:
MongoDbNoAuthConfigurationMongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017MongoDbReplicaSetDefaultConfigurationTimeoutExceptionfromWaitStrategy.WaitUntilAsyncviaInitiateReplicaSetAsyncMongoDbNamedReplicaSetConfigurationMongoDbV5ConfigurationMongoDbDefaultConfiguration,MongoDbV4ConfigurationExpected result / actual result
Expected: a stopped container that is started again becomes ready, and the readiness check reflects the current run.
Actual: the readiness check counts log lines from the previous run, so a restart either hangs until the wait strategy times out or is reported ready before
mongodis accepting connections.Additional information
Two things worth deciding together:
>=instead of exact equality would fix the hang but not the early return, since a stale line still counts.StoppedTime, which refers to the previous stop) addresses both, but I am not sure what the intended semantics ofStoppedTimeare here.Found while working on #1722; deliberately kept out of that pull request since it is a separate defect. Happy to open a PR once the preferred direction is clear.