Description:
The MySQL documentation on CommandTimeout (https://dev.mysql.com/doc/connector-net/en/connector-net-programming-mysqlcommand.html) refers users to the Microsoft documentation: https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.commandtimeout.a..., which states:
> For example, with a 30 second time out, if Read requires two network packets, then it has 30 seconds to read both network packets. If you call Read again, it will have another 30 seconds to read any data that it requires.
This implies that the CommandTimeout is "reset" back to its initial value on public API calls, e.g., that the elapsed time for the user's first call to "Read" doesn't count against the second call. I would expect "NextResult" and other methods to be treated similarly.
However, in MySql.Data, it appears that the elapsed time for I/O is cumulative for all I/O performed by the MySqlCommand. Furthermore, the implementation can throw ArgumentOutOfRangeException (indicating incorrect use of the underlying Stream APIs) rather than a TimeoutException.
How to repeat:
Run the following C# program:
using (var connection = new MySqlConnection("..."))
{
connection.Open();
using (var cmd = connection.CreateCommand())
{
cmd.CommandTimeout = 2;
int repeats = 5;
cmd.CommandText = "";
for (int i = 0; i < repeats; i++)
cmd.CommandText += "SELECT SLEEP(1); ";
using (var reader = cmd.ExecuteReader())
{
for (int i = 0; i < repeats; i++)
{
reader.Read(); // returns true
reader.GetInt32(0); // returns 0
reader.Read(); // returns false
reader.NextResult(); // should return true until last loop iteration; instead, throws exception on second iteration
}
}
}
}
Suggested fix:
The second call to NextResult throws this exception:
System.ArgumentOutOfRangeException: Timeout can be only be set to 'System.Threading.Timeout.Infinite' or a value > 0.
Parameter name: value
at System.Net.Sockets.NetworkStream.set_ReadTimeout(Int32 value)
at MySql.Data.MySqlClient.TimedStream.StartTimer(IOKind op)
at MySql.Data.MySqlClient.TimedStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.BufferedStream.Read(Byte[] array, Int32 offset, Int32 count)
at MySql.Data.MySqlClient.MySqlStream.ReadFully(Stream stream, Byte[] buffer, Int32 offset, Int32 count)
at MySql.Data.MySqlClient.MySqlStream.LoadPacket()
at MySql.Data.MySqlClient.MySqlStream.ReadPacket()
at MySql.Data.MySqlClient.NativeDriver.GetResult(Int32& affectedRow, Int64& insertedId)
at MySql.Data.MySqlClient.Driver.NextResult(Int32 statementId, Boolean force)
at MySql.Data.MySqlClient.MySqlDataReader.NextResult()
at Main()
Infrequently (perhaps if the timing is "just right"), a TimeoutException is thrown instead.
I would expect the entire loop to complete without timing out, because no single call to Read/NextResult takes longer than two seconds (i.e., CommandTimeout).