Bug: in Couchbase.Extensions.DependencyInjection - AddCouchbaseBucket<>() - multiple injection not possible

I am using the lastest Couchbase SDK in .Net (3.4.2) with an ASP.Net Core in .Net 7

And for ASP.Net Core integration tests, the injection is executed multiple times, but the Couchbase.Extensions.DependencyInjection seems to not support being called multiple times.
Integration tests in ASP.NET Core | Microsoft Learn

The problematic method is AddCouchbaseBucket() (same for AddCouchbaseBucket())

It triggers a Duplicate Type within an assembly

It seems those method did not account for the possibility to be called multiple times like it’s the case for integration tests

Emited types should have distinct names. As injection can be called multiple times AND you cannot delete an emited type, you might either need to remember that the type already exist, or test it’s existence before trying to emit it, or use unique type names (random suffix). (Or I totally misunderstood the issue :slight_smile: )

It seems to be an issue similar to

but I’m unsure.

The last messages I found suggested the solution might be a signing issue or a compilation in .net 7 not done (current target is .Net 5 & .Net 6, but not yet .Net 7)

Thank you

@piartt

Can you provide a code example and the full exception with stack trace that you are encountering?

Here is the trace:

Message: 
System.ArgumentException : Duplicate type name within an assembly.

  Stack Trace: 
TypeBuilder.ctor(String fullname, TypeAttributes attr, Type parent, Type[] interfaces, ModuleBuilder module, PackingSize iPackingSize, Int32 iTypeSize, TypeBuilder enclosingType)
ModuleBuilder.DefineType(String name, TypeAttributes attr, Type parent, Type[] interfaces)
ModuleBuilder.DefineType(String name, TypeAttributes attr, Type parent)
NamedBucketProxyGenerator.CreateProxyType(Type interfaceType, String bucketName)
NamedBucketProxyGenerator.GetProxy(Type bucketProviderInterface, String bucketName)
ServiceCollectionExtensions.AddCouchbaseBucket[T](IServiceCollection services, String bucketName, Action`1 buildAction)
ServiceCollectionExtensions.AddCouchbaseBucket[T](IServiceCollection services, String bucketName)
Program.<Main>$(String[] args) line 8
RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
--- End of stack trace from previous location ---
HostingListener.CreateHost()
<>c__DisplayClass10_0.<ResolveHostFactory>b__0(String[] args)
DeferredHostBuilder.Build()
WebApplicationFactory`1.CreateHost(IHostBuilder builder)
WebApplicationFactory`1.ConfigureHostBuilder(IHostBuilder hostBuilder)
WebApplicationFactory`1.EnsureServer()
WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers)
WebApplicationFactory`1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers)
WebApplicationFactory`1.CreateClient(WebApplicationFactoryClientOptions options)
WebApplicationFactory`1.CreateClient()
BasicTests.Get_EndpointsReturnSuccessAndCorrectContentType2(String url) line 80
--- End of stack trace from previous location ---

As for the source code, I cannot upload it, but it’s a blank ASP.Net Core 7
Here is the Program.cs:

using Couchbase.Extensions.DependencyInjection;

using WebApplication2;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCouchbase(builder.Configuration.GetSection("Couchbase:ClusterOptions"));

builder.Services.AddCouchbaseBucket<IMyCouchbaseBucketProvider>("MyBucket");

var app = builder.Build();

app.UseRouting();

app.Map("ping", () => "pong");

app.Run();

public partial class Program { }

the IMyCouchbaseBucketProvider.cs is just:

public interface IMyCouchbaseBucketProvider : INamedBucketProvider {}

And the test is just a dummy:

public class BasicTests2
: IClassFixture<WebApplicationFactory>
{
private readonly WebApplicationFactory _factory;

public BasicTests2(WebApplicationFactory<Program> factory)
{
    _factory = factory;
}

[Theory]
[InlineData("/ping")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
    // Arrange
    var client = _factory.CreateClient();

    // Act
    var response = await client.GetAsync(url);

    // Assert
    response.EnsureSuccessStatusCode(); // Status Code 200-299
    Assert.Equal("text/plain; charset=utf-8",
        response.Content.Headers.ContentType.ToString());
}

[Theory]
[InlineData("/ping")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType2(string url)
{
    // Arrange
    var client = _factory.CreateClient();

    // Act
    var response = await client.GetAsync(url);

    // Assert
    response.EnsureSuccessStatusCode(); // Status Code 200-299
    Assert.Equal("text/plain; charset=utf-8",
        response.Content.Headers.ContentType.ToString());
}

}

public class BasicTests
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public BasicTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/ping")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/plain; charset=utf-8",
            response.Content.Headers.ContentType.ToString());
    }

    [Theory]
    [InlineData("/ping")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType2(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/plain; charset=utf-8",
            response.Content.Headers.ContentType.ToString());
    }
}

If you run tests one by one, it’s ok, but if you run them all at once, it’s not :frowning:

@piarrt

I’ve looked into this further, and the problem isn’t necessarily registering more than once. I believe this is related to test parallelization. It’s the fact that you’re registering it more than once simultaneously on multiple threads and creating a race condition. There is a cache designed to reuse the same generated class, but that cache isn’t thread-safe.

I’m going to work on improving this logic to be thread-safe, but in the meantime you can adjust parallelization on your tests.

https://issues.couchbase.com/browse/NCBC-3329

@piartt

Also, here is the proposed fix if you want to build and test it yourself: https://review.couchbase.org/c/couchbase-net-client/+/185567

1 Like