Search code examples
c#.netstack-traceoutofrangeexceptionsqlhelper

System.IndexOutOfRangeException mysteriously thrown from System.Collections.Generic.List.Add(T item)


I have been struggling to figure out the cause of an exception with the following stack trace (deliberately shortened):

System.IndexOutOfRangeException: Index was outside the bounds of the array. at System.Collections.Generic.List1.Add(T item) at PPM.Dal.Entities.Code.SqlHelper.ExecuteEntityArray[TEntity](CommandType commandType, String commandText, String connectionString, Nullable1 commandTimeout, DynamicParameters parameters, Boolean useTransaction)

The PPM.Dal.Entities.Code.SqlHelper.ExecuteEntityArray method looks like this:

private static TEntity[] ExecuteEntityArray<TEntity>(CommandType commandType, string commandText, string connectionString, int? commandTimeout, DynamicParameters parameters, bool useTransaction) where TEntity : class, new()
{
    var sqlAction = StartLog(commandType, commandText, null, parameters);

    try
    {
        TEntity[] result;

        var transaction = CurrentTransaction;
        if (useTransaction && transaction != null && (connectionString == null || transaction.ConnectionString == connectionString))
        {
            // Use the existing transaction to execute the query
            result = transaction.SqlConnection.Query<TEntity>(sql: commandText, param: parameters, transaction: transaction.SqlTransaction, commandType: commandType, commandTimeout: commandTimeout).ToArray();
        }
        else
        {
            if (connectionString == null)
            {
                connectionString = ConnectionStringData;
            }

            using (var sqlConnection = new SqlConnection(connectionString))
            {
                result = sqlConnection.Query<TEntity>(sql: commandText, param: parameters, commandType: commandType, commandTimeout: commandTimeout).ToArray();
            }
        }

        EndLog(sqlAction, null);
        return result;
    }
    catch (SqlException ex)
    {
        EndLog(sqlAction, ex);
        HandleSqlExceptions(ex, null);
        throw;
    }
    catch (Exception ex)
    {
        EndLog(sqlAction, ex);
        throw;
    }
}

It is quite peculiar to me how this method could throw that exception. I am not even calling List.Add() anywhere. I entertained the possibility that the stack trace could be wrong, because the exception is caught in this method and rethrown, but that would not be possible unless 'throw ex' was used. Here we have a naked 'throw', which should maintain the original stack trace of the exception.

Does anyone have any idea how this method could throw that exception?

P.S.: I don't know if this has any obvious relevance, but the issue happens quite rarely and only on our production servers, and it is thus quite hard to reproduce locally. I have not succeeded yet, but I have a way of reproducing it on a production server.


Solution

  • I finally figured this out. It turns out that the issue was in EndLog(). For some reason it was skipped in the stack trace. Perhaps when a Release build is done, the function EndLog() was inlined for some reason (which I am still not sure why). But there was a list that was accessed by 2 threads at once. I added a lock to the 'add' operation and it fixed the issue.

    Thanks for all answers!