Skip to content

Commit 71850be

Browse files
committed
Fix hang when disposing a reader. Fixes #299
Update the internal state to 'NoMoreData' if an exception is thrown reading a payload. This prevents a hang when disposing MySqlDataReader (or MySqlConnection) if an error payload was sent and no more data is coming.
1 parent cd3d7cd commit 71850be

File tree

4 files changed

+70
-8
lines changed

4 files changed

+70
-8
lines changed

src/MySqlConnector/MySqlClient/CommandExecutors/StoredProcedureCommandExecutor.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Data;
34
using System.Data.Common;
45
using System.Threading;
@@ -76,10 +77,17 @@ public override async Task<DbDataReader> ExecuteReaderAsync(string commandText,
7677
}
7778

7879
var reader = (MySqlDataReader) await base.ExecuteReaderAsync(commandText, inParams, behavior, ioBehavior, cancellationToken).ConfigureAwait(false);
79-
if (returnParam != null && await reader.ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false))
80-
returnParam.Value = reader.GetValue(0);
81-
82-
return reader;
80+
try
81+
{
82+
if (returnParam != null && await reader.ReadAsync(ioBehavior, cancellationToken).ConfigureAwait(false))
83+
returnParam.Value = reader.GetValue(0);
84+
return reader;
85+
}
86+
catch (Exception)
87+
{
88+
reader.Dispose();
89+
throw;
90+
}
8391
}
8492

8593
internal void SetParams()

src/MySqlConnector/MySqlClient/Results/ResultSet.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,16 +172,19 @@ private ValueTask<Row> ScanRowAsync(IOBehavior ioBehavior, Row row, Cancellation
172172

173173
async Task<Row> ScanRowAsyncAwaited(Task<PayloadData> payloadTask, CancellationToken token)
174174
{
175+
PayloadData payloadData;
175176
try
176177
{
177-
return ScanRowAsyncRemainder(await payloadTask.ConfigureAwait(false));
178+
payloadData = await payloadTask.ConfigureAwait(false);
178179
}
179-
catch (MySqlException ex) when (ex.Number == (int) MySqlErrorCode.QueryInterrupted)
180+
catch (MySqlException ex)
180181
{
181182
BufferState = State = ResultSetState.NoMoreData;
182-
token.ThrowIfCancellationRequested();
183+
if (ex.Number == (int) MySqlErrorCode.QueryInterrupted)
184+
token.ThrowIfCancellationRequested();
183185
throw;
184186
}
187+
return ScanRowAsyncRemainder(payloadData);
185188
}
186189

187190
Row ScanRowAsyncRemainder(PayloadData payload)

tests/SideBySide/StoredProcedureFixture.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ name VARCHAR(63)
1414
BEGIN
1515
RETURN name;
1616
END");
17+
Connection.Execute(@"DROP FUNCTION IF EXISTS failing_function;
18+
CREATE FUNCTION failing_function()
19+
RETURNS INT
20+
BEGIN
21+
DECLARE v1 INT;
22+
SELECT c1 FROM table_that_does_not_exist INTO v1;
23+
RETURN v1;
24+
END");
1725
Connection.Execute(@"DROP PROCEDURE IF EXISTS echop;
1826
CREATE PROCEDURE echop(
1927
IN name VARCHAR(63)

tests/SideBySide/StoredProcedureTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,49 @@ public async Task StoredProcedureEcho(string procedureType, string executorType)
5555
}
5656
}
5757

58+
[Fact]
59+
public void CallFailingFunction()
60+
{
61+
using (var connection = new MySqlConnection(AppConfig.ConnectionString))
62+
using (var command = connection.CreateCommand())
63+
{
64+
connection.Open();
65+
66+
command.CommandType = CommandType.StoredProcedure;
67+
command.CommandText = "failing_function";
68+
69+
var returnParameter = command.CreateParameter();
70+
returnParameter.DbType = DbType.Int32;
71+
returnParameter.Direction = ParameterDirection.ReturnValue;
72+
command.Parameters.Add(returnParameter);
73+
74+
Assert.Throws<MySqlException>(() => command.ExecuteNonQuery());
75+
}
76+
}
77+
78+
[Fact]
79+
public void CallFailingFunctionInTransaction()
80+
{
81+
using (var connection = new MySqlConnection(AppConfig.ConnectionString))
82+
{
83+
connection.Open();
84+
using (var transaction = connection.BeginTransaction())
85+
using (var command = connection.CreateCommand())
86+
{
87+
command.CommandType = CommandType.StoredProcedure;
88+
command.CommandText = "failing_function";
89+
90+
var returnParameter = command.CreateParameter();
91+
returnParameter.DbType = DbType.Int32;
92+
returnParameter.Direction = ParameterDirection.ReturnValue;
93+
command.Parameters.Add(returnParameter);
94+
95+
Assert.Throws<MySqlException>(() => command.ExecuteNonQuery());
96+
transaction.Commit();
97+
}
98+
}
99+
}
100+
58101
[RequiresFeatureTheory(ServerFeatures.StoredProcedures)]
59102
[InlineData("FUNCTION")]
60103
[InlineData("PROCEDURE")]

0 commit comments

Comments
 (0)