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



我有一个Postgres DB运行在Docker环境中,在Xunit测试中使用FluentDocker设置。

Xunit配置为运行串行测试。

运行单个测试时,一切正常。

但是,当添加另一个测试(实际上只是复制第一个测试)并使用dotnet test在一个调用中运行两个测试时,第二个测试总是使用57P01: terminating connection due to administrator command失败。

我试图关闭DB连接,每次测试运行后停止容器等,但错误仍然存在,它总是发生在同一行代码。

这是测试的代码:

[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:Devxunitxunitsrcxunit.executionSdkFrameworksRunnersTestInvoker.cs:line 264
--- End of stack trace from previous location ---
at Xunit.Sdk.ExecutionTimer.AggregateAsync(Func`1 asyncAction) in C:Devxunitxunitsrcxunit.executionSdkFrameworksExecutionTimer.cs:line 48
at Xunit.Sdk.ExceptionAggregator.RunAsync(Func`1 code) in C:Devxunitxunitsrcxunit.coreSdkExceptionAggregator.cs:line 90

我创建了一个repro,可以在GitHub上找到。

您的连接字符串中有生成连接池的选项:POOLING = True; MINPOOLSIZE = 1; MAXPOOLSIZE = 100;

在您生成这个池并运行您的测试逻辑之后,您只在调用CloseAsync时关闭池中的当前连接。

<年代>

由于池的最小大小为1,因此在关闭当前连接后,池将立即添加一个新连接。看起来关闭你的数据库会触发新连接上的错误,因为这个连接是错误的。因此,要解决这个问题并销毁所有与连接相关的资源,您需要使用DisposeAsync

await session.Connection?.DisposeAsync();

您可能还应该设置POOLING = False,因为您的测试似乎只需要一个连接。

session似乎通过using正确地关闭/处置了连接,因为连接是由Marten创建/管理的。这里的问题是,这将不会正确地清理生成的连接池,而只是将连接返回到将保持空闲的连接池。因此,只要PgTestConnectionStringShouldProjectUserRegistrationShouldProjectUserRegistrationSecond中相同,池将尝试提供在ShouldProjectUserRegistration中使用的相同连接。问题是,当连接在池中处于空闲状态时,您终止了它所连接的数据库。因此,当您从池中获取此连接并尝试对其执行操作时,您将得到您描述的错误消息。

这种情况可以通过几种不同的方法来防止:

  • 在两个测试方法中设置POOLING = False;,因为您不需要连接池,因为您将在测试之间破坏数据库。
  • 如果你想保留POOLING = True;,你可以销毁NpgsqlConnection.ClearPool(session.Connection);NpgsqlConnection.ClearAllPools();
  • 在每个Fact末尾的池