Skip to content

Commit

Permalink
fix(#595): Implement TestcontainersContainer.DisposeAsync thread safe
Browse files Browse the repository at this point in the history
  • Loading branch information
HofmeisterAn committed Sep 30, 2022
1 parent 9baa4fd commit 31b492e
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
54 changes: 36 additions & 18 deletions src/Testcontainers/Containers/TestcontainersContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace DotNet.Testcontainers.Containers
/// <inheritdoc cref="ITestcontainersContainer" />
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" };

Expand All @@ -28,6 +28,8 @@ public class TestcontainersContainer : ITestcontainersContainer

private readonly ITestcontainersConfiguration configuration;

private int disposed;

[NotNull]
private ContainerInspectResponse container = new ContainerInspectResponse();

Expand Down Expand Up @@ -228,18 +230,18 @@ public Task<ExecResult> ExecAsync(IList<string> command, CancellationToken ct =
}

/// <summary>
/// Removes the Testcontainer.
/// Removes the Testcontainers.
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>A task that represents the asynchronous clean up operation of a Testcontainer.</returns>
/// <returns>A task that represents the asynchronous clean up operation of a Testcontainers.</returns>
public async Task CleanUpAsync(CancellationToken ct = default)
{
await this.semaphoreSlim.WaitAsync(ct)
.ConfigureAwait(false);

try
{
this.container = await this.CleanUp(this.Id, ct)
this.container = await this.CleanUp(ct)
.ConfigureAwait(false);
}
finally
Expand All @@ -251,31 +253,41 @@ await this.semaphoreSlim.WaitAsync(ct)
/// <inheritdoc />
public async ValueTask DisposeAsync()
{
if (!ContainerHasBeenCreatedStates.Contains(this.State))
await this.DisposeAsyncCore()
.ConfigureAwait(false);

GC.SuppressFinalize(this);
}

/// <summary>
/// Releases any resources associated with the instance of <see cref="TestcontainersContainer" />.
/// </summary>
/// <returns>Value task that completes when any resources associated with the instance have been released.</returns>
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<ContainerInspectResponse> Create(CancellationToken ct = default)
{
if (ContainerHasBeenCreatedStates.Contains(this.State))
if (ContainerHasBeenCreatedStates.HasFlag(this.State))
{
return this.container;
}
Expand Down Expand Up @@ -336,24 +348,30 @@ await this.client.StopAsync(id, ct)
.ConfigureAwait(false);
}

private async Task<ContainerInspectResponse> CleanUp(string id, CancellationToken ct = default)
private async Task<ContainerInspectResponse> CleanUp(CancellationToken ct = default)
{
await this.client.RemoveAsync(id, ct)
.ConfigureAwait(false);
if (ContainerHasBeenCreatedStates.HasFlag(this.State))
{
await this.client.RemoveAsync(this.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.");
return;
}

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";
}
Expand Down
18 changes: 10 additions & 8 deletions src/Testcontainers/Containers/TestcontainersState.cs
Original file line number Diff line number Diff line change
@@ -1,53 +1,55 @@
namespace DotNet.Testcontainers.Containers
{
using System;
using JetBrains.Annotations;

/// <summary>
/// Docker container states.
/// </summary>
[PublicAPI]
[Flags]
public enum TestcontainersState
{
/// <summary>
/// Docker container was not created.
/// Docker container has not been created.
/// </summary>
[PublicAPI]
Undefined,
Undefined = -1,

/// <summary>
/// Docker container is created.
/// </summary>
[PublicAPI]
Created,
Created = 1,

/// <summary>
/// Docker container is restarting.
/// </summary>
[PublicAPI]
Restarting,
Restarting = 2,

/// <summary>
/// Docker container is running.
/// </summary>
[PublicAPI]
Running,
Running = 4,

/// <summary>
/// Docker container is paused.
/// </summary>
[PublicAPI]
Paused,
Paused = 8,

/// <summary>
/// Docker container is exited.
/// </summary>
[PublicAPI]
Exited,
Exited = 16,

/// <summary>
/// Docker container is dead.
/// </summary>
[PublicAPI]
Dead,
Dead = 32,
}
}

0 comments on commit 31b492e

Please sign in to comment.