Description:
In an environment of massive multi threading, where query threads are aborted and database connection are reused, it may happen, that a query returns the result of another (before aborted?) query.
Exception in that case will be something like that:
System.ArgumentException: Column 'TelType' does not belong to table .
at System.Data.DataRow.GetDataColumn(String columnName)
at System.Data.DataRow.get_Item(String columnName)
at MySQLAbortTest.SqlAbortTest_Thread.DoWork() in d:\prj\MySQLAbortTest\Program.cs:line 81
The field must be in the result, but the returned result belongs to another query that did not had that field.
Other possible exceptions in that case are:
System.ArgumentException: Thread was being aborted.Couldn't store <19.11.2014 08:43:24> in ReceiveTS Column. Expected type is DateTime. ---> System.Threading.ThreadAbortException: Thread was being aborted.
at System.Data.Common.DateTimeStorage.Set(Int32 record, Object value)
at System.Data.DataColumn.set_Item(Int32 record, Object value)
--- End of inner exception stack trace ---
at System.Data.DataColumn.set_Item(Int32 record, Object value)
at System.Data.DataTable.NewRecordFromArray(Object[] value)
at System.Data.DataTable.LoadDataRow(Object[] values, Boolean fAcceptChanges)
at System.Data.ProviderBase.SchemaMapping.LoadDataRow()
at System.Data.Common.DataAdapter.FillLoadDataRow(SchemaMapping mapping)
at System.Data.Common.DataAdapter.FillFromReader(DataSet dataset, DataTable datatable, String srcTable, DataReaderContainer dataReader, Int32 startRecord, Int32 maxRecords, DataColumn parentChapterColumn, Object parentChapterValue)
at System.Data.Common.DataAdapter.Fill(DataTable[] dataTables, IDataReader dataReader, Int32 startRecord, Int32 maxRecords)
at System.Data.Common.DbDataAdapter.FillInternal(DataSet dataset, DataTable[] datatables, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior)
at System.Data.Common.DbDataAdapter.Fill(DataTable[] dataTables, Int32 startRecord, Int32 maxRecords, IDbCommand command, CommandBehavior behavior)
at System.Data.Common.DbDataAdapter.Fill(DataTable dataTable)
at MySQLAbortTest.SqlAbortTest_Thread.DoWork() in d:\prj\MySQLAbortTest\Program.cs:line 77
Same behaviour with Connector 6.9.5.
How to repeat:
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MySQLAbortTest
{
class SqlAbortTest_Query
{
public string Query { get; set; }
public string Field { get; set; }
public SqlAbortTest_Query(string query, string field)
{
Query = query;
Field = field;
}
}
class SqlAbortTest_Thread
{
MySqlConnection con;
Thread thread;
object conLock;
object abortLock;
SqlAbortTest_Query nextQuery;
public bool State { get; protected set; }
public long AbortAt { get; protected set; }
public SqlAbortTest_Thread()
{
string connStr = String.Format("server={0};user={1};database={2};port={3};password={4};", "localhost", "root", "<DATABASE>", 3306, "<PASSWORD>");
con = new MySqlConnection(connStr);
con.Open();
conLock = new object();
abortLock = new object();
}
public void Start(SqlAbortTest_Query nextQuery, long abortAt)
{
AbortAt = abortAt;
this.nextQuery = nextQuery;
thread = new Thread(new ThreadStart(DoWork));
thread.Start();
State = true;
}
public void Abort()
{
// Just lock to be sure that Exception is written to disk
lock (abortLock)
{
if (thread.ThreadState != System.Threading.ThreadState.Stopped)
thread.Abort();
State = false;
}
}
void DoWork()
{
try
{
StringBuilder sb = new StringBuilder();
DataTable table = new DataTable();
MySqlCommand cmd = new MySqlCommand(nextQuery.Query, con);
MySqlDataAdapter adapter = new MySqlDataAdapter(cmd);
lock (conLock)
{
adapter.Fill(table);
}
foreach (DataRow row in table.Rows)
sb.Append(row[nextQuery.Field].ToString());
}
catch (ThreadAbortException) { }
catch (Exception ex)
{
lock (abortLock)
{
using (StreamWriter writer = File.AppendText("exception.log"))
{
writer.WriteLine(ex.ToString());
}
}
}
}
}
class SqlAbortTest
{
List<SqlAbortTest_Query> sqlAbortTests = new List<SqlAbortTest_Query>()
{
<MULTIPLE SELECTS ON MULTIPLE BIG TABLES>
new SqlAbortTest_Query("select * from joint where ... ", "<Field>"),
new SqlAbortTest_Query("select * from joint where ... ", "<Field>"),
new SqlAbortTest_Query("select * from joint where ... ", "<Field>"),
new SqlAbortTest_Query("select * from joint where ... ", "<Field>"),
};
List<SqlAbortTest_Thread> threadList;
public SqlAbortTest()
{
threadList = new List<SqlAbortTest_Thread>();
for (int i = 0; i < 20; i++)
threadList.Add(new SqlAbortTest_Thread());
}
public void DoTest()
{
Random random = new Random();
while (true)
{
// Start & Abort new threads
foreach (var th in threadList)
{
if (!th.State)
th.Start(sqlAbortTests[(int)Math.Round(random.NextDouble() * (sqlAbortTests.Count - 1), 0)], Environment.TickCount + (int)random.NextDouble() * 5);
else if (Environment.TickCount > th.AbortAt)
th.Abort();
}
}
}
}
class Program
{
static void Main(string[] args)
{
SqlAbortTest at = new SqlAbortTest();
at.DoTest();
}
}
}