顺序执行多个Xunit测试时Postgres报57P01管理员命令终止连接错误
问题描述
我在Xunit测试项目中使用FluentDocker搭建Docker环境运行Postgres数据库,且已将Xunit配置为串行执行测试模式。
运行单个测试时所有逻辑执行正常,但复制第一个测试新增第二个测试用例后,使用dotnet test命令一次性运行两个测试,第二个测试始终抛出错误57P01: terminating connection due to administrator command。
已尝试在每次测试运行结束后主动关闭数据库连接、停止容器等操作,但错误仍未解决,且异常始终触发在同一代码行。
测试代码
[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
问题根因
- 连接字符串开启了
POOLING = True,第一个测试停止Postgres容器时,Npgsql连接池中的空闲失效连接没有被清理,第二个测试启动新容器后,Npgsql会复用这些旧连接,新容器的Postgres进程不识别旧连接会主动断开,触发57P01错误。 - 资源释放顺序错误:
DocumentStore用using声明,Dispose逻辑会在测试方法末尾才执行,此时容器已经停止,Dispose过程中的残留连接操作会触发异常;仅靠WaitForPort只能检测端口是否开放,无法确认Postgres已经完成初始化可正常接受连接,容器刚启动端口放开时发起连接会被正在启动的Postgres主动断开。 - FluentDocker的
Stop()方法默认不会等待容器完全退出、释放端口,第二个测试启动新容器时可能碰到旧容器进程未完全退出的端口冲突问题。
修复方案
- 停止容器前主动清空Npgsql连接池,清除所有残留失效连接:
NpgsqlConnection.ClearAllPools();
- 调整资源释放顺序,确保所有数据库相关资源释放完成后再操作容器:
await session.SaveChangesAsync(); var user = session.Load<User>(id); // 先释放会话 await session.DisposeAsync(); // 再释放DocumentStore store.Dispose(); // 清空连接池 NpgsqlConnection.ClearAllPools(); // 停止并销毁容器 service.Stop(); service.Dispose(); Assert.Equal(username, user?.Username);
- 替换原有的
WaitForPort逻辑,增加Postgres可用性检测,直到数据库能正常响应查询再继续测试:
service.Start(); var retryCount = 0; while (retryCount < 30) { try { await using var conn = new NpgsqlConnection(PgTestConnectionString); await conn.OpenAsync(); await using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT 1;"; await cmd.ExecuteScalarAsync(); break; } catch { retryCount++; await Task.Delay(1000); } }
- (可选)测试场景下可以直接在连接字符串中配置
Pooling=false,从根源避免连接池残留问题,对测试执行速度的影响可以忽略。
内容的提问来源于stack exchange,提问作者Alexander Zeitler
相关产品推荐
相关产品推荐

