diff --git a/src/FirebirdSql.Data.FirebirdClient.Tests/FbConnectionTests.cs b/src/FirebirdSql.Data.FirebirdClient.Tests/FbConnectionTests.cs index eb6a1df1e..d45f2073f 100644 --- a/src/FirebirdSql.Data.FirebirdClient.Tests/FbConnectionTests.cs +++ b/src/FirebirdSql.Data.FirebirdClient.Tests/FbConnectionTests.cs @@ -619,6 +619,40 @@ public async Task ConnectionTimeoutUsingCancellationToken() } } + [Test] + public async Task PooledConnectionUsableAfterCancelledBeginTransaction() + { + if (!EnsureServerType(FbServerType.Default)) + return; + + // The cancellation token reaches the socket read, so the transaction response stays unread. + var csb = BuildConnectionStringBuilder(ServerType, Compression, WireCrypt); + csb.Pooling = true; + csb.MinPoolSize = 0; + csb.MaxPoolSize = 1; + var connectionString = csb.ToString(); + FbConnection.ClearPool(connectionString); + + using (var cancellation = new CancellationTokenSource()) + await using (var connection = new FbConnection(connectionString)) + { + await connection.OpenAsync(); + var beginning = connection.BeginTransactionAsync(cancellation.Token); + cancellation.Cancel(); + Assert.CatchAsync(async () => await beginning); + } + + await using (var connection = new FbConnection(connectionString)) + { + await connection.OpenAsync(); + await using (var command = connection.CreateCommand()) + { + command.CommandText = "select 1 from rdb$database"; + Assert.AreEqual(1, Convert.ToInt32(await command.ExecuteScalarAsync())); + } + } + } + [Test] public async Task SrpWithLeadingZeros() { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs index 17f068a30..a8104cfeb 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs @@ -57,6 +57,7 @@ public FirebirdNetworkHandlingWrapper(IDataProvider dataProvider) public int Read(byte[] buffer, int offset, int count) { + CheckIOState(); if (_inputBuffer.Count < count) { var readBuffer = _readBuffer; @@ -89,6 +90,7 @@ public int Read(byte[] buffer, int offset, int count) } public async ValueTask ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) { + CheckIOState(); if (_inputBuffer.Count < count) { var readBuffer = _readBuffer; @@ -102,6 +104,11 @@ public async ValueTask ReadAsync(byte[] buffer, int offset, int count, Canc IOFailed = true; throw; } + catch (OperationCanceledException) + { + IOFailed = true; + throw; + } if (read != 0) { if (_decryptor != null) @@ -134,6 +141,7 @@ public ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationTo public void Flush() { + CheckIOState(); var buffer = _outputBuffer.ToArray(); _outputBuffer.Clear(); var count = buffer.Length; @@ -159,6 +167,7 @@ public void Flush() } public async ValueTask FlushAsync(CancellationToken cancellationToken = default) { + CheckIOState(); var buffer = _outputBuffer.ToArray(); _outputBuffer.Clear(); var count = buffer.Length; @@ -181,6 +190,11 @@ public async ValueTask FlushAsync(CancellationToken cancellationToken = default) IOFailed = true; throw; } + catch (OperationCanceledException) + { + IOFailed = true; + throw; + } } public void StartCompression() @@ -196,6 +210,12 @@ public void StartEncryption(byte[] key) _decryptor = CreateCipher(key); } + void CheckIOState() + { + if (IOFailed) + throw new IOException("Error reading data from the connection."); + } + int ReadFromInputBuffer(byte[] buffer, int offset, int count) { var read = Math.Min(count, _inputBuffer.Count); diff --git a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbConnection.cs b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbConnection.cs index 0a75039aa..0b6edc58f 100644 --- a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbConnection.cs +++ b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbConnection.cs @@ -753,7 +753,17 @@ public override void Close() } } catch - { } + { + if (_innerConnection != null) + { + if (_options.Pooling) + { + FbConnectionPoolManager.Instance.Release(_innerConnection, returnToAvailable: false); + } + + DisconnectEnlistedHelper(); + } + } finally { OnStateChange(_state, ConnectionState.Closed); @@ -810,7 +820,17 @@ public override async Task CloseAsync() } } catch - { } + { + if (_innerConnection != null) + { + if (_options.Pooling) + { + FbConnectionPoolManager.Instance.Release(_innerConnection, false); + } + + await DisconnectEnlistedHelper().ConfigureAwait(false); + } + } finally { OnStateChange(_state, ConnectionState.Closed); diff --git a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbConnectionInternal.cs b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbConnectionInternal.cs index 370129b3d..e5e23bb41 100644 --- a/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbConnectionInternal.cs +++ b/src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbConnectionInternal.cs @@ -367,6 +367,11 @@ public async Task BeginTransactionAsync(IsolationLevel level, str await DisposeTransactionAsync(cancellationToken).ConfigureAwait(false); throw FbException.Create(ex); } + catch (OperationCanceledException) + { + await DisposeTransactionAsync(CancellationToken.None).ConfigureAwait(false); + throw; + } return _activeTransaction; } @@ -412,6 +417,11 @@ public async Task BeginTransactionAsync(FbTransactionOptions opti await DisposeTransactionAsync(cancellationToken).ConfigureAwait(false); throw FbException.Create(ex); } + catch (OperationCanceledException) + { + await DisposeTransactionAsync(CancellationToken.None).ConfigureAwait(false); + throw; + } return _activeTransaction; }