From 35cae2693250a3c42896523daa41f25ee30ebb5c Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 8 Aug 2025 12:42:01 +0300 Subject: [PATCH 1/3] ADO.NET: `YdbConnection.OpenAsync` throws OperationCancelException when the `CancellationToken` is cancelled. --- src/Ydb.Sdk/CHANGELOG.md | 1 + .../src/Ado/Session/PoolingSessionSource.cs | 9 ++++++--- src/Ydb.Sdk/src/Ado/YdbConnection.cs | 14 ++------------ .../YdbConnectionTests.cs | 9 --------- .../Session/PoolingSessionSourceMockTests.cs | 7 ++++++- .../test/Ydb.Sdk.Ado.Tests/YdbConnectionTests.cs | 4 +--- 6 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/Ydb.Sdk/CHANGELOG.md b/src/Ydb.Sdk/CHANGELOG.md index aa22338b..af780e08 100644 --- a/src/Ydb.Sdk/CHANGELOG.md +++ b/src/Ydb.Sdk/CHANGELOG.md @@ -1,3 +1,4 @@ +- ADO.NET: `YdbConnection.OpenAsync` throws OperationCancelException when the `CancellationToken` is cancelled. - Feat ADO.NET: decimal type with arbitrary precision/scale ([#498](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/498)). - Fixed bug: interval value parsing in microseconds and double instead of ticks ([#497](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/497)). - ADO.NET: Changed `IBulkUpsertImporter.AddRowAsync` signature: `object?[] row` → `params object[]`. diff --git a/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs b/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs index d3a815e5..6e002668 100644 --- a/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs +++ b/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs @@ -126,9 +126,12 @@ private async ValueTask RentAsync(CancellationToken cancellationToken) continue; } - await using var _ = finalToken.Register( - () => waiterTcs.TrySetCanceled(), - useSynchronizationContext: false + await using var _ = finalToken.Register(() => waiterTcs.TrySetException( + new YdbException(StatusCode.ClientTransportTimeout, + $"The connection pool has been exhausted, either raise 'MaxSessionPool' " + + $"(currently {_maxSessionSize}) or 'CreateSessionTimeout' " + + $"(currently {_createSessionTimeout} seconds) in your connection string.") + ), useSynchronizationContext: false ); await using var disposeRegistration = _disposeCts.Token.Register( () => waiterTcs.TrySetException(new YdbException("The session source has been shut down.")), diff --git a/src/Ydb.Sdk/src/Ado/YdbConnection.cs b/src/Ydb.Sdk/src/Ado/YdbConnection.cs index de1e3c4d..fe69763f 100644 --- a/src/Ydb.Sdk/src/Ado/YdbConnection.cs +++ b/src/Ydb.Sdk/src/Ado/YdbConnection.cs @@ -110,18 +110,8 @@ public override void ChangeDatabase(string databaseName) public override async Task OpenAsync(CancellationToken cancellationToken) { ThrowIfConnectionOpen(); - try - { - Session = await PoolManager.GetSession(ConnectionStringBuilder, cancellationToken); - } - catch (OperationCanceledException e) - { - throw new YdbException(StatusCode.ClientTransportTimeout, - $"The connection pool has been exhausted, either raise 'MaxSessionPool' " + - $"(currently {ConnectionStringBuilder.MaxSessionPool}) or 'CreateSessionTimeout' " + - $"(currently {ConnectionStringBuilder.CreateSessionTimeout} seconds) in your connection string.", e - ); - } + + Session = await PoolManager.GetSession(ConnectionStringBuilder, cancellationToken); OnStateChange(ClosedToOpenEventArgs); diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Specification.Tests/YdbConnectionTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Specification.Tests/YdbConnectionTests.cs index 7eb6b09e..a7ead9ce 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Specification.Tests/YdbConnectionTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Specification.Tests/YdbConnectionTests.cs @@ -28,13 +28,4 @@ public override void ServerVersion_returns_value() { base.ServerVersion_returns_value(); } - - public override async Task OpenAsync_is_canceled() - { - await using var connection = CreateConnection(); - connection.ConnectionString = ConnectionString; - var task = connection.OpenAsync(CanceledToken); - await Assert.ThrowsAnyAsync(() => task); - Assert.True(task.IsFaulted); - } } diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Session/PoolingSessionSourceMockTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Session/PoolingSessionSourceMockTests.cs index 9ad917c6..240aaf3b 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Session/PoolingSessionSourceMockTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/Session/PoolingSessionSourceMockTests.cs @@ -295,6 +295,9 @@ public async Task StressTest_HighContention_OpenClose() catch (OperationCanceledException) { } + catch (YdbException) + { + } }, cts.Token)); } @@ -316,7 +319,9 @@ public async Task Get_Session_From_Exhausted_Pool() var cts = new CancellationTokenSource(); cts.CancelAfter(500); - await Assert.ThrowsAsync(async () => await sessionSource.OpenSession(cts.Token)); + Assert.Equal("The connection pool has been exhausted, either raise 'MaxSessionPool' (currently 1) " + + "or 'CreateSessionTimeout' (currently 5 seconds) in your connection string.", + (await Assert.ThrowsAsync(async () => await sessionSource.OpenSession(cts.Token))).Message); session.Close(); Assert.Equal(1, mockFactory.NumSession); diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionTests.cs index 3001816a..95b50c7b 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionTests.cs @@ -201,9 +201,7 @@ public async Task OpenAsync_WhenCancelTokenIsCanceled_ThrowYdbException() connection.ConnectionString = ConnectionString + ";MinSessionPool=1"; using var cts = new CancellationTokenSource(); cts.Cancel(); - Assert.Equal("The connection pool has been exhausted, either raise 'MaxSessionPool' (currently 10) " + - "or 'CreateSessionTimeout' (currently 5 seconds) in your connection string.", - (await Assert.ThrowsAsync(async () => await connection.OpenAsync(cts.Token))).Message); + await Assert.ThrowsAsync(async () => await connection.OpenAsync(cts.Token)); Assert.Equal(ConnectionState.Closed, connection.State); } From 092c340d86b8a700067cbab6f9e95970f35d5dec Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 8 Aug 2025 12:49:31 +0300 Subject: [PATCH 2/3] delete StatusCode.ClientTransportTimeout status code on pool has been exhausted --- src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs b/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs index 6e002668..755612bb 100644 --- a/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs +++ b/src/Ydb.Sdk/src/Ado/Session/PoolingSessionSource.cs @@ -127,10 +127,9 @@ private async ValueTask RentAsync(CancellationToken cancellationToken) } await using var _ = finalToken.Register(() => waiterTcs.TrySetException( - new YdbException(StatusCode.ClientTransportTimeout, - $"The connection pool has been exhausted, either raise 'MaxSessionPool' " + - $"(currently {_maxSessionSize}) or 'CreateSessionTimeout' " + - $"(currently {_createSessionTimeout} seconds) in your connection string.") + new YdbException($"The connection pool has been exhausted, either raise 'MaxSessionPool' " + + $"(currently {_maxSessionSize}) or 'CreateSessionTimeout' " + + $"(currently {_createSessionTimeout} seconds) in your connection string.") ), useSynchronizationContext: false ); await using var disposeRegistration = _disposeCts.Token.Register( From 73cf2404f2ee1cf297deb90143ebb8ea5fdb82ea Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Fri, 8 Aug 2025 13:11:15 +0300 Subject: [PATCH 3/3] fix test --- src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionTests.cs b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionTests.cs index 95b50c7b..d0806390 100644 --- a/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionTests.cs +++ b/src/Ydb.Sdk/test/Ydb.Sdk.Ado.Tests/YdbConnectionTests.cs @@ -201,7 +201,7 @@ public async Task OpenAsync_WhenCancelTokenIsCanceled_ThrowYdbException() connection.ConnectionString = ConnectionString + ";MinSessionPool=1"; using var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsAsync(async () => await connection.OpenAsync(cts.Token)); + await Assert.ThrowsAnyAsync(async () => await connection.OpenAsync(cts.Token)); Assert.Equal(ConnectionState.Closed, connection.State); }