diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e58183bf..ec04bb33b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@
### Fixed
- 525 Read ServerURL, Username and Secret field from CredsStore response to pull private Docker images
+- 595 Implement `TestcontainersContainer.DisposeAsync` thread safe
## [2.1.0]
diff --git a/src/Testcontainers/Containers/TestcontainersContainer.cs b/src/Testcontainers/Containers/TestcontainersContainer.cs
index 8fdcb4ca8..626e11256 100644
--- a/src/Testcontainers/Containers/TestcontainersContainer.cs
+++ b/src/Testcontainers/Containers/TestcontainersContainer.cs
@@ -18,7 +18,7 @@ namespace DotNet.Testcontainers.Containers
///
public class TestcontainersContainer : ITestcontainersContainer
{
- private static readonly TestcontainersState[] ContainerHasBeenCreatedStates = { TestcontainersState.Created, TestcontainersState.Running, TestcontainersState.Exited };
+ private const TestcontainersState ContainerHasBeenCreatedStates = TestcontainersState.Created | TestcontainersState.Running | TestcontainersState.Exited;
private static readonly string[] DockerDesktopGateways = { "host.docker.internal", "gateway.docker.internal" };
@@ -28,6 +28,8 @@ public class TestcontainersContainer : ITestcontainersContainer
private readonly ITestcontainersConfiguration configuration;
+ private int disposed;
+
[NotNull]
private ContainerInspectResponse container = new ContainerInspectResponse();
@@ -228,10 +230,10 @@ public Task ExecAsync(IList command, CancellationToken ct =
}
///
- /// Removes the Testcontainer.
+ /// Removes the Testcontainers.
///
/// Cancellation token.
- /// A task that represents the asynchronous clean up operation of a Testcontainer.
+ /// A task that represents the asynchronous clean up operation of a Testcontainers.
public async Task CleanUpAsync(CancellationToken ct = default)
{
await this.semaphoreSlim.WaitAsync(ct)
@@ -251,31 +253,40 @@ await this.semaphoreSlim.WaitAsync(ct)
///
public async ValueTask DisposeAsync()
{
- if (!ContainerHasBeenCreatedStates.Contains(this.State))
+ await this.DisposeAsyncCore()
+ .ConfigureAwait(false);
+
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases any resources associated with the instance of .
+ ///
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+ if (1.Equals(Interlocked.CompareExchange(ref this.disposed, 1, 0)))
{
return;
}
- // If someone calls `DisposeAsync`, we can immediately remove the container. We don't need to wait for the Resource Reaper.
- if (!Guid.Empty.Equals(this.configuration.SessionId))
+ // If someone calls `DisposeAsync`, we can immediately remove the container. We do not need to wait for the Resource Reaper.
+ if (Guid.Empty.Equals(this.configuration.SessionId))
{
- await this.CleanUpAsync()
+ await this.StopAsync()
.ConfigureAwait(false);
}
else
{
- await this.StopAsync()
+ await this.CleanUpAsync()
.ConfigureAwait(false);
}
this.semaphoreSlim.Dispose();
-
- GC.SuppressFinalize(this);
}
private async Task Create(CancellationToken ct = default)
{
- if (ContainerHasBeenCreatedStates.Contains(this.State))
+ if (ContainerHasBeenCreatedStates.HasFlag(this.State))
{
return this.container;
}
@@ -340,20 +351,21 @@ private async Task CleanUp(string id, CancellationToke
{
await this.client.RemoveAsync(id, ct)
.ConfigureAwait(false);
+
return new ContainerInspectResponse();
}
private void ThrowIfContainerHasNotBeenCreated()
{
- if (!ContainerHasBeenCreatedStates.Contains(this.State))
+ if (!ContainerHasBeenCreatedStates.HasFlag(this.State))
{
- throw new InvalidOperationException("Testcontainer has not been created.");
+ throw new InvalidOperationException("Testcontainers has not been created.");
}
}
private string GetContainerGateway()
{
- if (!this.client.IsRunningInsideDocker || !ContainerHasBeenCreatedStates.Contains(this.State))
+ if (!this.client.IsRunningInsideDocker || !ContainerHasBeenCreatedStates.HasFlag(this.State))
{
return "localhost";
}
diff --git a/src/Testcontainers/Containers/TestcontainersState.cs b/src/Testcontainers/Containers/TestcontainersState.cs
index 017614bc8..22dc584c1 100644
--- a/src/Testcontainers/Containers/TestcontainersState.cs
+++ b/src/Testcontainers/Containers/TestcontainersState.cs
@@ -1,53 +1,55 @@
namespace DotNet.Testcontainers.Containers
{
+ using System;
using JetBrains.Annotations;
///
/// Docker container states.
///
[PublicAPI]
+ [Flags]
public enum TestcontainersState
{
///
- /// Docker container was not created.
+ /// Docker container has not been created.
///
[PublicAPI]
- Undefined,
+ Undefined = -1,
///
/// Docker container is created.
///
[PublicAPI]
- Created,
+ Created = 1,
///
/// Docker container is restarting.
///
[PublicAPI]
- Restarting,
+ Restarting = 2,
///
/// Docker container is running.
///
[PublicAPI]
- Running,
+ Running = 4,
///
/// Docker container is paused.
///
[PublicAPI]
- Paused,
+ Paused = 8,
///
/// Docker container is exited.
///
[PublicAPI]
- Exited,
+ Exited = 16,
///
/// Docker container is dead.
///
[PublicAPI]
- Dead,
+ Dead = 32,
}
}