docker 57P01:按顺序运行多个测试时,由于管理员命令而终止连接

ttygqcqt  于 2023-05-28  发布在  Docker
关注(0)|答案(1)|浏览(175)

我有一个运行在Docker环境中的Postgres DB,它是在Xunit测试中使用FluentDocker设置的。
Xunit被配置为串行运行测试。
当运行单个测试时,一切都正常。
但是,当添加另一个测试(实际上只是复制第一个测试)并使用dotnet test在一个调用中运行两个测试时,第二个测试总是会失败。
我试着关闭数据库连接,在每次测试运行后停止容器等。但是错误仍然存在,并且它总是发生在同一行代码中。
这是测试的代码:

[Fact]
public async Task ShouldProjectUserRegistration()
{
  var file = Path.Combine(
    Directory.GetCurrentDirectory(),
    (TemplateString)"Resources/docker-compose.yml"
  );

  var service = new Builder()
    .UseContainer()
    .UseCompose()
    .FromFile(file)
    .RemoveOrphans()
    .ForceRecreate()
    .WaitForPort(
      "database",
      "5432/tcp",
      30000 /*30s*/
    )
    .Build();
  var container = service.Start();

  var PgTestConnectionString =
    "PORT = 5432; HOST = localhost; TIMEOUT = 15; POOLING = True; MINPOOLSIZE = 1; MAXPOOLSIZE = 100; COMMANDTIMEOUT = 20; DATABASE = 'marten'; PASSWORD = '123456'; USER ID = 'marten'";
  using var store = DocumentStore.For(
    options =>
    {
      options.Connection(PgTestConnectionString);
      options.AutoCreateSchemaObjects = AutoCreate.All;
      options.Projections.SelfAggregate<User>(ProjectionLifecycle.Inline);
    }
  );

  var id = Guid.NewGuid();
  var username = "jane.doe@acme.inc";
  var userRegistered = new UserRegistered(
    id,
    username
  );

  await using var session = store.OpenSession();
  session.Events.StartStream(
    id,
    userRegistered
  );

  await session.SaveChangesAsync();

  var user = session.Load<User>(id);
  await session.Connection?.CloseAsync();
  service.Stop();
  Assert.Equal(
    username,
    user?.Username
  );

这是docker-compose.yml

version: "3"

services:
  database:
    image: library/postgres:14
    environment:
      POSTGRES_USER: 'marten'
      POSTGRES_PASSWORD: '123456'
      POSTGRES_DB: 'marten'
    ports:
      - "5432:5432"

例外情况:

Npgsql.PostgresException
57P01: terminating connection due to administrator command
   at Npgsql.Internal.NpgsqlConnector.<ReadMessage>g__ReadMessageLong|213_0(NpgsqlConnector connector, Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
   at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
   at Weasel.Core.CommandBuilderBase`6.ExecuteReaderAsync(TConnection conn, CancellationToken cancellation, TTransaction tx)
   at Weasel.Core.SchemaMigration.Determine(DbConnection conn, ISchemaObject[] schemaObjects)
   at Weasel.Core.Migrations.DatabaseBase`1.executeMigration(ISchemaObject[] schemaObjects, CancellationToken token)
   at Weasel.Core.Migrations.DatabaseBase`1.executeMigration(ISchemaObject[] schemaObjects, CancellationToken token)
   at Weasel.Core.Migrations.DatabaseBase`1.generateOrUpdateFeature(Type featureType, IFeatureSchema feature, CancellationToken token)
   at Weasel.Core.Migrations.DatabaseBase`1.ensureStorageExists(IList`1 types, Type featureType, CancellationToken token)
   at Weasel.Core.Migrations.DatabaseBase`1.ensureStorageExists(IList`1 types, Type featureType, CancellationToken token)
   at Weasel.Core.Migrations.DatabaseBase`1.ensureStorageExists(IList`1 types, Type featureType, CancellationToken token)
   at Marten.Events.EventGraph.ProcessEventsAsync(DocumentSessionBase session, CancellationToken token)
   at Marten.Internal.Sessions.DocumentSessionBase.SaveChangesAsync(CancellationToken token)
   at Marten.Internal.Sessions.DocumentSessionBase.SaveChangesAsync(CancellationToken token)
   at MartenFluentDockerNpsql57P01Repro.Tests.UserProjectionTests.ShouldProjectUserRegistrationSecond() in /Users/alexzeitler/src/MartenFluentDockerNpsql57P01Repro/MartenFluentDockerNpsql57P01Repro.Tests/UserProjectionTests.cs:line 120
   at MartenFluentDockerNpsql57P01Repro.Tests.UserProjectionTests.ShouldProjectUserRegistrationSecond() in /Users/alexzeitler/src/MartenFluentDockerNpsql57P01Repro/MartenFluentDockerNpsql57P01Repro.Tests/UserProjectionTests.cs:line 126
   at Xunit.Sdk.TestInvoker`1.<>c__DisplayClass48_1.<<InvokeTestMethodAsync>b__1>d.MoveNext() in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\Runners\TestInvoker.cs:line 264
--- End of stack trace from previous location ---
   at Xunit.Sdk.ExecutionTimer.AggregateAsync(Func`1 asyncAction) in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\ExecutionTimer.cs:line 48
   at Xunit.Sdk.ExceptionAggregator.RunAsync(Func`1 code) in C:\Dev\xunit\xunit\src\xunit.core\Sdk\ExceptionAggregator.cs:line 90

我创建了一个可以找到on GitHub的repro。

erhoui1w

erhoui1w1#

您的连接字符串中有生成连接池的选项:POOLING = True; MINPOOLSIZE = 1; MAXPOOLSIZE = 100;
在生成这个池并运行测试逻辑之后,调用CloseAsync时只是关闭了池中的当前连接。
由于池的最小大小为1,因此在关闭当前连接后,池将立即添加新连接。
看起来关闭数据库会触发新连接上的错误,因为连接。因此,要解决这个问题并销毁所有连接相关资源,您需要使用DisposeAsync

await session.Connection?.DisposeAsync();

您可能还应该设置POOLING = False,因为您的测试似乎只需要一个连接。
由于连接是由Marten创建/管理的,因此session似乎通过using正确关闭/处理了连接。这里的问题是,这将不会正确地清理生成的连接池,而只是将连接返回到它将保持空闲的连接池。因此,只要PgTestConnectionStringShouldProjectUserRegistrationShouldProjectUserRegistrationSecond中相同,池将尝试提供与ShouldProjectUserRegistration中相同的连接。问题是,当连接在池中处于空闲状态时,您终止了它所连接的数据库。因此,当您从池中获取此连接并尝试对其执行操作时,您会收到所描述的错误消息。
这种情况可以通过几种不同的方式来预防:

  • 在两个测试方法中设置POOLING = False;,因为您不需要连接池,因为您在测试之间销毁了数据库。
  • 如果要保留POOLING = True;,可以在每个Fact结束时使用NpgsqlConnection.ClearPool(session.Connection);NpgsqlConnection.ClearAllPools();销毁池

相关问题