System.Data.SQLite
Check-in [7b3fe92dcb]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Do not attempt to reset a connection if it has been closed. Also, prevent any potential GC race while resetting a connection. Keep track of the total number of connections successfully opened and closed from any of the pools. Enhance test for ticket [996d13cd87] to make sure that at least one connection was opened and closed from a connection pool.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 7b3fe92dcb994e0af39ec6937c70abda0f3d7030
User & Date: mistachkin 2012-05-03 13:44:30
Context
2012-05-03
15:11
Fix a couple more potential GC race conditions. check-in: ed16f61cf7 user: mistachkin tags: trunk
13:44
Do not attempt to reset a connection if it has been closed. Also, prevent any potential GC race while resetting a connection. Keep track of the total number of connections successfully opened and closed from any of the pools. Enhance test for ticket [996d13cd87] to make sure that at least one connection was opened and closed from a connection pool. check-in: 7b3fe92dcb user: mistachkin tags: trunk
13:04
Stop creating the CriticalHandle derived classes via implicit operator conversions. Simplify test case for ticket [996d13cd87]. check-in: 29b506224a user: mistachkin tags: trunk
Changes
Hide Diffs Unified Diffs Show Whitespace Changes Patch

Changes to System.Data.SQLite/SQLite3.cs.

289
290
291
292
293
294
295


296
297
298

299
300
301
302
303
304
305
306
    {
      SQLiteConnectionPool.ClearPool(_fileName);
    }

    internal override int CountPool()
    {
        Dictionary<string, int> counts = null;


        int totalCount = 0;

        SQLiteConnectionPool.GetCounts(_fileName,

            ref counts, ref totalCount);

        return totalCount;
    }

    internal override void SetTimeout(int nTimeoutMS)
    {
      int n = UnsafeNativeMethods.sqlite3_busy_timeout(_sql, nTimeoutMS);







>
>



>
|







289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
    {
      SQLiteConnectionPool.ClearPool(_fileName);
    }

    internal override int CountPool()
    {
        Dictionary<string, int> counts = null;
        int openCount = 0;
        int closeCount = 0;
        int totalCount = 0;

        SQLiteConnectionPool.GetCounts(_fileName,
            ref counts, ref openCount, ref closeCount,
            ref totalCount);

        return totalCount;
    }

    internal override void SetTimeout(int nTimeoutMS)
    {
      int n = UnsafeNativeMethods.sqlite3_busy_timeout(_sql, nTimeoutMS);

Changes to System.Data.SQLite/SQLiteBase.cs.

392
393
394
395
396
397
398

399
400
401
402
403
404
405
...
415
416
417
418
419
420
421

422
423
424
425
426
427
428
            if (n > 0) throw new SQLiteException(n, GetLastError(hdl, db));
        }
    }

    internal static void ResetConnection(SQLiteConnectionHandle hdl, IntPtr db)
    {
        if ((hdl == null) || (db == IntPtr.Zero)) return;

        lock (hdl)
        {
            IntPtr stmt = IntPtr.Zero;
            int n;
            do
            {
                stmt = UnsafeNativeMethods.sqlite3_next_stmt(db, stmt);
................................................................................

            if (IsAutocommit(db) == false) // a transaction is pending on the connection
            {
                n = UnsafeNativeMethods.sqlite3_exec(db, ToUTF8("ROLLBACK"), IntPtr.Zero, IntPtr.Zero, out stmt);
                if (n > 0) throw new SQLiteException(n, GetLastError(hdl, db));
            }
        }

    }

    internal static bool IsAutocommit(IntPtr db)
    {
      if (db == IntPtr.Zero) return false;
      return (UnsafeNativeMethods.sqlite3_get_autocommit(db) == 1);
    }







>







 







>







392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
...
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
            if (n > 0) throw new SQLiteException(n, GetLastError(hdl, db));
        }
    }

    internal static void ResetConnection(SQLiteConnectionHandle hdl, IntPtr db)
    {
        if ((hdl == null) || (db == IntPtr.Zero)) return;
        if (hdl.IsClosed || hdl.IsInvalid) return;
        lock (hdl)
        {
            IntPtr stmt = IntPtr.Zero;
            int n;
            do
            {
                stmt = UnsafeNativeMethods.sqlite3_next_stmt(db, stmt);
................................................................................

            if (IsAutocommit(db) == false) // a transaction is pending on the connection
            {
                n = UnsafeNativeMethods.sqlite3_exec(db, ToUTF8("ROLLBACK"), IntPtr.Zero, IntPtr.Zero, out stmt);
                if (n > 0) throw new SQLiteException(n, GetLastError(hdl, db));
            }
        }
        GC.KeepAlive(hdl);
    }

    internal static bool IsAutocommit(IntPtr db)
    {
      if (db == IntPtr.Zero) return false;
      return (UnsafeNativeMethods.sqlite3_get_autocommit(db) == 1);
    }

Changes to System.Data.SQLite/SQLiteConnectionPool.cs.

5
6
7
8
9
10
11

12
13
14
15
16
17
18
..
35
36
37
38
39
40
41












42
43
44
45
46


47
48
49
50


51
52
53
54
55



56
57
58
59
60
61
62
...
128
129
130
131
132
133
134

135
136
137
138
139
140
141
...
146
147
148
149
150
151
152





153
154
155
156
157
158
159
160
161
162
...
223
224
225
226
227
228
229

230
231
232
233
234
235
236
 * Released to the public domain, use at your own risk!
 ********************************************************/

namespace System.Data.SQLite
{
  using System;
  using System.Collections.Generic;


  internal static class SQLiteConnectionPool
  {
    /// <summary>
    /// Keeps track of connections made on a specified file.  The PoolVersion dictates whether old objects get
    /// returned to the pool or discarded when no longer in use.
    /// </summary>
................................................................................
    private static SortedList<string, Pool> _connections = new SortedList<string, Pool>(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    /// The default version number new pools will get
    /// </summary>
    private static int _poolVersion = 1;













    /// <summary>
    /// Counts the number of pool entries matching the specified file name.
    /// </summary>
    /// <param name="fileName">The file name to match or null to match all files.</param>
    /// <param name="counts">The pool entry counts for each matching file.</param>


    /// <param name="totalCount">The total number of pool entries for all matching files.</param>
    internal static void GetCounts(
        string fileName,
        ref Dictionary<string, int> counts,


        ref int totalCount
        )
    {
        lock (_connections)
        {



            if (counts == null)
            {
                counts = new Dictionary<string, int>(
                    StringComparer.OrdinalIgnoreCase);
            }

            if (fileName != null)
................................................................................

        while (poolQueue.Count > 0)
        {
          WeakReference cnn = poolQueue.Dequeue();
          SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
          if ((hdl != null) && !hdl.IsClosed && !hdl.IsInvalid)
          {

            return hdl;
          }
          GC.KeepAlive(hdl);
        }
        return null;
      }
    }
................................................................................
    /// </summary>
    internal static void ClearAllPools()
    {
      lock (_connections)
      {
        foreach (KeyValuePair<string, Pool> pair in _connections)
        {





          while (pair.Value.Queue.Count > 0)
          {
            WeakReference cnn = pair.Value.Queue.Dequeue();
            SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
            if (hdl != null)
            {
              hdl.Dispose();
            }
            GC.KeepAlive(hdl);
          }
................................................................................
        {
          ResizePool(queue, true);

          Queue<WeakReference> poolQueue = queue.Queue;
          if (poolQueue == null) return;

          poolQueue.Enqueue(new WeakReference(hdl, false));

          GC.KeepAlive(hdl);
        }
        else
        {
          hdl.Close();
        }
      }







>







 







>
>
>
>
>
>
>
>
>
>
>
>





>
>




>
>





>
>
>







 







>







 







>
>
>
>
>
|
|
|







 







>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
...
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
...
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
...
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
 * Released to the public domain, use at your own risk!
 ********************************************************/

namespace System.Data.SQLite
{
  using System;
  using System.Collections.Generic;
  using System.Threading;

  internal static class SQLiteConnectionPool
  {
    /// <summary>
    /// Keeps track of connections made on a specified file.  The PoolVersion dictates whether old objects get
    /// returned to the pool or discarded when no longer in use.
    /// </summary>
................................................................................
    private static SortedList<string, Pool> _connections = new SortedList<string, Pool>(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    /// The default version number new pools will get
    /// </summary>
    private static int _poolVersion = 1;

    /// <summary>
    /// The number of connections successfully opened from any pool.
    /// This value is incremented by the Remove method.
    /// </summary>
    private static int _poolOpened = 0;

    /// <summary>
    /// The number of connections successfully closed from any pool.
    /// This value is incremented by the Add method.
    /// </summary>
    private static int _poolClosed = 0;

    /// <summary>
    /// Counts the number of pool entries matching the specified file name.
    /// </summary>
    /// <param name="fileName">The file name to match or null to match all files.</param>
    /// <param name="counts">The pool entry counts for each matching file.</param>
    /// <param name="openCount">The total number of connections successfully opened from any pool.</param>
    /// <param name="closeCount">The total number of connections successfully closed from any pool.</param>
    /// <param name="totalCount">The total number of pool entries for all matching files.</param>
    internal static void GetCounts(
        string fileName,
        ref Dictionary<string, int> counts,
        ref int openCount,
        ref int closeCount,
        ref int totalCount
        )
    {
        lock (_connections)
        {
            openCount = _poolOpened;
            closeCount = _poolClosed;

            if (counts == null)
            {
                counts = new Dictionary<string, int>(
                    StringComparer.OrdinalIgnoreCase);
            }

            if (fileName != null)
................................................................................

        while (poolQueue.Count > 0)
        {
          WeakReference cnn = poolQueue.Dequeue();
          SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
          if ((hdl != null) && !hdl.IsClosed && !hdl.IsInvalid)
          {
            Interlocked.Increment(ref _poolOpened);
            return hdl;
          }
          GC.KeepAlive(hdl);
        }
        return null;
      }
    }
................................................................................
    /// </summary>
    internal static void ClearAllPools()
    {
      lock (_connections)
      {
        foreach (KeyValuePair<string, Pool> pair in _connections)
        {
          if (pair.Value == null)
            continue;

          Queue<WeakReference> poolQueue = pair.Value.Queue;

          while (poolQueue.Count > 0)
          {
            WeakReference cnn = poolQueue.Dequeue();
            SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
            if (hdl != null)
            {
              hdl.Dispose();
            }
            GC.KeepAlive(hdl);
          }
................................................................................
        {
          ResizePool(queue, true);

          Queue<WeakReference> poolQueue = queue.Queue;
          if (poolQueue == null) return;

          poolQueue.Enqueue(new WeakReference(hdl, false));
          Interlocked.Increment(ref _poolClosed);
          GC.KeepAlive(hdl);
        }
        else
        {
          hdl.Close();
        }
      }

Changes to Tests/tkt-996d13cd87.eagle.

52
53
54
55
56
57
58






59
60
61
62
63
64
65
...
129
130
131
132
133
134
135


136
137
138

139
140
141
142
143
144
145
...
208
209
210
211
212
213
214
215




216
217
218
219
220
221
222
223
224
225

226
227
228
229
230
          int errors = 0;

          //
          // NOTE: This is the total number of test threads to create.
          //
          int count = 100;







          //
          // NOTE: Create a random number generator suitable for waiting a
          //       random number of milliseconds between each attempt to
          //       cause the race condition on a given thread.
          //
          Random random = new Random();

................................................................................
                //
                goEvent.WaitOne();

                //
                // NOTE: Wait a random number of milliseconds before forcing a
                //       full garbage collection.
                //


                Thread.Sleep(random.Next(0, 1000));
                GC.GetTotalMemory(true);
              }

              catch (Exception e)
              {
                Interlocked.Increment(ref errors);
                Trace.WriteLine(e);
              }
            };

................................................................................
    }
  }] true true true results errors System.Data.SQLite.dll]

  list $code $results \
      [expr {[info exists errors] ? $errors : ""}] \
      [expr {$code eq "Ok" ? [catch {
        object invoke _Dynamic${id}.Test${id} Main
      } result] : [set result ""]}] $result




} -cleanup {
  object invoke System.Data.SQLite.SQLiteConnection ClearAllPools
  object invoke GC GetTotalMemory true

  cleanupDb $fileName

  unset -nocomplain result results errors code sql dataSource id db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -match \
regexp -result {^Ok System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 \d+$}}


###############################################################################

runSQLiteTestEpilogue
runTestEpilogue







>
>
>
>
>
>







 







>
>



>







 







|
>
>
>
>









|
>





52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
...
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
...
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
          int errors = 0;

          //
          // NOTE: This is the total number of test threads to create.
          //
          int count = 100;

          //
          // NOTE: This is the total number of times we should force a full
          //       garbage collection.
          //
          int gcCount = 2;

          //
          // NOTE: Create a random number generator suitable for waiting a
          //       random number of milliseconds between each attempt to
          //       cause the race condition on a given thread.
          //
          Random random = new Random();

................................................................................
                //
                goEvent.WaitOne();

                //
                // NOTE: Wait a random number of milliseconds before forcing a
                //       full garbage collection.
                //
                for (int index = 0; index < gcCount; index++)
                {
                  Thread.Sleep(random.Next(0, 1000));
                  GC.GetTotalMemory(true);
                }
              }
              catch (Exception e)
              {
                Interlocked.Increment(ref errors);
                Trace.WriteLine(e);
              }
            };

................................................................................
    }
  }] true true true results errors System.Data.SQLite.dll]

  list $code $results \
      [expr {[info exists errors] ? $errors : ""}] \
      [expr {$code eq "Ok" ? [catch {
        object invoke _Dynamic${id}.Test${id} Main
      } result] : [set result ""]}] $result \
      [expr {[object invoke -flags +NonPublic \
          System.Data.SQLite.SQLiteConnectionPool _poolOpened] > 0}] \
      [expr {[object invoke -flags +NonPublic \
          System.Data.SQLite.SQLiteConnectionPool _poolClosed] > 0}]
} -cleanup {
  object invoke System.Data.SQLite.SQLiteConnection ClearAllPools
  object invoke GC GetTotalMemory true

  cleanupDb $fileName

  unset -nocomplain result results errors code sql dataSource id db fileName
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -match \
regexp -result {^Ok System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 \d+ True\
True$}}

###############################################################################

runSQLiteTestEpilogue
runTestEpilogue