views:

789

answers:

4

I found this in an error log and am trying to work out how it's possible. It's not every day that a NullReferenceException turns up deep within the .net base classes!

1) Exception Information
*********************************************
Exception Type: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Data: System.Collections.ListDictionaryInternal
TargetSite: Void Bind(System.Data.SqlClient.TdsParserStateObject)
HelpLink: NULL
Source: System.Data

StackTrace Information
*********************************************
   at System.Data.SqlClient.SqlDataReader.Bind(TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult esult)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
   at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader(CommandBehavior behavior)
   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(DataSet dataSet, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior)
   at System.Data.Common.DbDataAdapter.Fill(DataSet dataSet)
   at MyCode.Shared.Data.DataSocket.GetTable(String SPString)
   at <rest of stack trace>

The only thing I could come up with is there's a (slim) chance there were two threads executing the same method at the same time, and one cleared or modified the DataSet that is being passed to Fill(). So my question is really:

  • how could this exception get thrown
  • Could a multi-thread scenario cause this exception
  • How can I be sure, eg is there a way i can step through the System.Data methods to replicate the problem?

Incidentally, I've found a couple of other cases of this problem, one in this thread and another in this google cache of someone's page. Neither seems to be of any help though.

The GetTable() method of mine that is being executed looks like this:

public DataTable GetTable(string SPString)
{
    //Setup the data objects by calling helper
    prepareDataAdaptor(SPString,CommandType.Text);

    dataAdaptor.Fill(dataSet);
    dataAdaptor.SelectCommand.Connection.Close();
    DataTable dt;
    //ensure we dispose okay
    using(dataSet)
    {   

        if(dataSet.Tables.Count==0)
        {
            dataSet.Tables.Add(new DataTable("EmptyTable"));
        }

        dt=dataSet.Tables[0];
        //because we are disposing we need to remove the table from the dataset
        dataSet.Tables.Clear();

    }
    return dt;
}

private void prepareDataAdaptor(string SPString,CommandType Type)
{
    checkForConnection();
    dataSet=new DataSet();
    dbCommand.CommandText=SPString;
    dbCommand.CommandTimeout = MySettings.CommandTimeout;
    dataAdaptor.SelectCommand=dbCommand;
    dataAdaptor.SelectCommand.CommandType=Type;
    dataAdaptor.SelectCommand.Connection=dbConnection;
    dataAdaptor.SelectCommand.Parameters.Clear();
}

dataAdaptor (sic) is an instance variable declared as an IDbDataAdapter populated with a SqlDataAdapter. dataSet is an instance variable of type DataSet.

My theory goes that thread A runs through and gets partway into the SqlDataAdapter.Fill() method. Meanwhile thread B is also executing and does something that messes up thread A, like this line:

dataAdaptor.SelectCommand.Connection.Close();

I can see that this code of mine isn't thread-safe but how can I be sure this is the problem that caused the above exception?

many thanks for any suggestions!

Rory

EDIT: fixed case of lousy spelling. Didn't update it in the code as that's how it is.

UPDATE: I agree there's several things wrong with this code that should be fixed, but my main interest is whether there's any way to verify that it is this threading issue that would have caused this error. Given my application it's slightly far-fetched but the only thing I can think of. Before I modify the code to generally make it better I'd like to be sure that I have found the cause of the exception so that I can be sure I've fixed it.

Is there any way to step into the .net code for instance? I'm using VS 2005 / .net 2.0 but I think in VS 2008 you can view the .NET framework source? If that's the case could I create a 2-thread scenario and step through to recreate this problem? Or is there a way that doesn't require me installing VS 2008?

A: 

I'm guessing that disposing the dataset invalidates the table that it contains!

BTW you don't need the dataset, you can fill a datatable directly - or call the other data adapter function which creates a datatable for you (Get, i think)

and it's spelled "adapter" not "adaptor" ;-)

Steven A. Lowe
Damn legacy codors who couldn't spell! got me all confused...So any ideas on how to be sure that this multi-threaded access and then dispose is actually what happened, eg to replicate the scenario?
Rory
A: 

I would add some logging code right around dataAdaptor.Fill(dataSet) that shows the ThreadID and other information. You could use Console.Writeline, but I highly recommend log4net. Also, make your code thread-safe. Each thread should get its own DataSet and DataAdapter, or use mutex's.

+1  A: 

Your dataset shouldn't be a class level variable. It is probably being disposed or accessed by the other thread in between these two calls or during the fill operation.

prepareDataAdaptor(SPString,CommandType.Text);

dataAdaptor.Fill(dataSet);

dotjoe
+1  A: 

Could a multi-thread scenario cause this exception

Instance methods of ADO.NET classes (e.g. SqlCommand) are generally not thread-safe. So if you are using such instances from multiple threads, you can expect problems such as the one you describe.

Joe