System.Data.SQLite
View Ticket
Not logged in
Ticket UUID: ae5267b8638371181c3d4f9a394e250da61b74fb
Title: AccessViolationException during object disposal
Status: Closed Type: Incident
Severity: Critical Priority: Blocker
Subsystem: Integration_Via_PInvoke Resolution: Unable_To_Reproduce
Last Modified: 2012-12-24 00:14:22
Version Found In: 1.0.82.0
User Comments:
anonymous added on 2012-12-17 14:39:22:
Dear SQLite,

I've been hunting this bug for quite a while, but now have a reproducable scenario which raises the AccessViolationException in 90% of the runs.
Sometimes in the second or third loop, sometimes after 7 runs.

The AccessViolationException happens when one forgets to Dispose a SQLiteCommand.
In my test scenario I've deliberatly removed the command.Dispose() (and did not put the SQLiteCommand in a using clause).
The difficulty in this scenario is that it sometimes (10% of my cases) runs fine.

In 90% of my test runs however, it raises an exception on the Finalizer Thread.

System.AccessViolationException was unhandled
  HResult=-2147467261
  Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
  Source=System.Data.SQLite
  StackTrace:
       at System.Data.SQLite.UnsafeNativeMethods.sqlite3_finalize_interop(IntPtr stmt)
       at System.Data.SQLite.SQLiteBase.FinalizeStatement(SQLiteConnectionHandle hdl, IntPtr stmt)
       at System.Data.SQLite.SQLiteStatementHandle.ReleaseHandle()
       at System.Runtime.InteropServices.CriticalHandle.Cleanup()
       at System.Runtime.InteropServices.CriticalHandle.Dispose(Boolean disposing)
       at System.Runtime.InteropServices.CriticalHandle.Finalize()


Regards,

Patrick van Dijk
Spatial Eye




using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

using System.Data.SQLite;

namespace SQLiteBug
{
  static class Program
  {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
      String filename = "test.db";

      try
      {
        System.IO.File.Delete(filename);
      }
      catch
      { }

      for (int i = 1; i <= 1000; i++)
      {
        Test(filename, i);

        // We must do the following, otherwise the File.Delete() does not work
        System.GC.Collect(2, GCCollectionMode.Forced);
        System.GC.WaitForFullGCComplete();
        System.GC.WaitForPendingFinalizers();

        System.IO.File.Delete(filename);
      }
    }

    static void Test(String filename, int testCount)
    {
      var builder = new SQLiteConnectionStringBuilder();
      builder.DataSource = filename;
      builder.FailIfMissing = false;
      builder.Pooling = false;
      builder.DefaultIsolationLevel = System.Data.IsolationLevel.Serializable;

      var connectionString = builder.ToString();

      using (var connection = new SQLiteConnection(connectionString))
      {
        connection.Open();

        using (var command = connection.CreateCommand())
        {
          command.CommandText = "CREATE TABLE MYTEST (__rowid INTEGER PRIMARY KEY AUTOINCREMENT, MYFIELD STRING)";
          command.ExecuteNonQuery();

          command.CommandText = "CREATE VIRTUAL TABLE MYTREE USING RTREE (ID, MINX, MAXX, MINY, MAXY)";
          command.ExecuteNonQuery();
        }

        using (var command = connection.CreateCommand())
        {
          using (var transaction = connection.BeginTransaction())
          {
            for (int i = 0; i < 10; i++)
            {
              command.CommandText = String.Format("INSERT INTO MYTEST VALUES({0}, \"Value {0}\")", i);
              command.ExecuteNonQuery();

              command.CommandText = String.Format("INSERT INTO MYTREE VALUES({0}, {0}, {0}, {0}, {0})", i);
              command.ExecuteNonQuery();
            }
            transaction.Commit();
          }
        }

        String countCommand = "SELECT COUNT(*) FROM MYTEST T1, MYTREE T2 WHERE  T1.ROWID = T2.ID AND (T2.MINX <= :xmax AND T2.MINY <= :ymax AND T2.MAXX >= :xmin AND T2.MAXY >= :ymin)";
        for (int i = 0; i < 10000; i++)
        {
          Console.Write("\r{0} {1}    ", testCount, i);

          var command = connection.CreateCommand();   // Oops forgot to put the command in a using block (I forgot to Dispose it properly)
          command.CommandText = countCommand;
          command.Parameters.Add(new SQLiteParameter(":xmin", 2));
          command.Parameters.Add(new SQLiteParameter(":ymin", 2));
          command.Parameters.Add(new SQLiteParameter(":xmax", 8));
          command.Parameters.Add(new SQLiteParameter(":ymax", 8));
          using (var reader = command.ExecuteReader())
          {
            while (reader.Read())
            { }
          }
        }

        connection.Close();
      }

    }
  }
}

mistachkin added on 2012-12-17 22:24:10:
I believe this issue has already been fixed in trunk, by check-in [e702fa192f].
The original ticket for this was [6c6ecccc5f].

Could you please test this code against the latest trunk and see if that clears the issue you are seeing?

mistachkin added on 2012-12-17 23:14:59:
Local testing seems to confirm that this issue has already been fixed in trunk.
I've added a test case for this ticket on a branch, here: [3f4c3359f3].

mistachkin added on 2012-12-17 23:16:20:
For reference, the original test case covering this issue is [4960a565c2].

mistachkin added on 2012-12-17 23:19:10:
This ticket is currently a release blocker.  However, the underlying issue appears to already be solved.  Therefore, it will be closed by Thursday, December 20th, 2012 unless further information is received before then.

mistachkin added on 2012-12-20 02:31:09:
The more specific test case for this has been merged to trunk, see check-in [c83f8f3852].