Bug #88124 CommandTimeout is not reset by NextResult, throws ArgumentOutOfRangeException
Submitted: 17 Oct 2017 17:08 Modified: 19 Oct 2017 7:16
Reporter: Bradley Grainger (OCA) Email Updates:
Status: Verified Impact on me:
None 
Category:Connector / NET Severity:S3 (Non-critical)
Version:6.9.9 OS:Microsoft Windows (10.0.15063 x64)
Assigned to: CPU Architecture:Any

[17 Oct 2017 17:08] Bradley Grainger
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).
[19 Oct 2017 7:16] Chiranjeevi Battula
Hello Bradley Grainger,

Thank you for the bug report and test case.
Verified this behavior on Visual Studio 2013 (C#.Net) and Connector/NET 6.9.9 version.

Thanks,
Chiranjeevi.