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 Side-by-Side Diffs Ignore Whitespace Patch

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

   289    289       {
   290    290         SQLiteConnectionPool.ClearPool(_fileName);
   291    291       }
   292    292   
   293    293       internal override int CountPool()
   294    294       {
   295    295           Dictionary<string, int> counts = null;
          296  +        int openCount = 0;
          297  +        int closeCount = 0;
   296    298           int totalCount = 0;
   297    299   
   298    300           SQLiteConnectionPool.GetCounts(_fileName,
   299         -            ref counts, ref totalCount);
          301  +            ref counts, ref openCount, ref closeCount,
          302  +            ref totalCount);
   300    303   
   301    304           return totalCount;
   302    305       }
   303    306   
   304    307       internal override void SetTimeout(int nTimeoutMS)
   305    308       {
   306    309         int n = UnsafeNativeMethods.sqlite3_busy_timeout(_sql, nTimeoutMS);

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

   392    392               if (n > 0) throw new SQLiteException(n, GetLastError(hdl, db));
   393    393           }
   394    394       }
   395    395   
   396    396       internal static void ResetConnection(SQLiteConnectionHandle hdl, IntPtr db)
   397    397       {
   398    398           if ((hdl == null) || (db == IntPtr.Zero)) return;
          399  +        if (hdl.IsClosed || hdl.IsInvalid) return;
   399    400           lock (hdl)
   400    401           {
   401    402               IntPtr stmt = IntPtr.Zero;
   402    403               int n;
   403    404               do
   404    405               {
   405    406                   stmt = UnsafeNativeMethods.sqlite3_next_stmt(db, stmt);
................................................................................
   415    416   
   416    417               if (IsAutocommit(db) == false) // a transaction is pending on the connection
   417    418               {
   418    419                   n = UnsafeNativeMethods.sqlite3_exec(db, ToUTF8("ROLLBACK"), IntPtr.Zero, IntPtr.Zero, out stmt);
   419    420                   if (n > 0) throw new SQLiteException(n, GetLastError(hdl, db));
   420    421               }
   421    422           }
          423  +        GC.KeepAlive(hdl);
   422    424       }
   423    425   
   424    426       internal static bool IsAutocommit(IntPtr db)
   425    427       {
   426    428         if (db == IntPtr.Zero) return false;
   427    429         return (UnsafeNativeMethods.sqlite3_get_autocommit(db) == 1);
   428    430       }

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

     5      5    * Released to the public domain, use at your own risk!
     6      6    ********************************************************/
     7      7   
     8      8   namespace System.Data.SQLite
     9      9   {
    10     10     using System;
    11     11     using System.Collections.Generic;
           12  +  using System.Threading;
    12     13   
    13     14     internal static class SQLiteConnectionPool
    14     15     {
    15     16       /// <summary>
    16     17       /// Keeps track of connections made on a specified file.  The PoolVersion dictates whether old objects get
    17     18       /// returned to the pool or discarded when no longer in use.
    18     19       /// </summary>
................................................................................
    35     36       private static SortedList<string, Pool> _connections = new SortedList<string, Pool>(StringComparer.OrdinalIgnoreCase);
    36     37   
    37     38       /// <summary>
    38     39       /// The default version number new pools will get
    39     40       /// </summary>
    40     41       private static int _poolVersion = 1;
    41     42   
           43  +    /// <summary>
           44  +    /// The number of connections successfully opened from any pool.
           45  +    /// This value is incremented by the Remove method.
           46  +    /// </summary>
           47  +    private static int _poolOpened = 0;
           48  +
           49  +    /// <summary>
           50  +    /// The number of connections successfully closed from any pool.
           51  +    /// This value is incremented by the Add method.
           52  +    /// </summary>
           53  +    private static int _poolClosed = 0;
           54  +
    42     55       /// <summary>
    43     56       /// Counts the number of pool entries matching the specified file name.
    44     57       /// </summary>
    45     58       /// <param name="fileName">The file name to match or null to match all files.</param>
    46     59       /// <param name="counts">The pool entry counts for each matching file.</param>
           60  +    /// <param name="openCount">The total number of connections successfully opened from any pool.</param>
           61  +    /// <param name="closeCount">The total number of connections successfully closed from any pool.</param>
    47     62       /// <param name="totalCount">The total number of pool entries for all matching files.</param>
    48     63       internal static void GetCounts(
    49     64           string fileName,
    50     65           ref Dictionary<string, int> counts,
           66  +        ref int openCount,
           67  +        ref int closeCount,
    51     68           ref int totalCount
    52     69           )
    53     70       {
    54     71           lock (_connections)
    55     72           {
           73  +            openCount = _poolOpened;
           74  +            closeCount = _poolClosed;
           75  +
    56     76               if (counts == null)
    57     77               {
    58     78                   counts = new Dictionary<string, int>(
    59     79                       StringComparer.OrdinalIgnoreCase);
    60     80               }
    61     81   
    62     82               if (fileName != null)
................................................................................
   128    148   
   129    149           while (poolQueue.Count > 0)
   130    150           {
   131    151             WeakReference cnn = poolQueue.Dequeue();
   132    152             SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
   133    153             if ((hdl != null) && !hdl.IsClosed && !hdl.IsInvalid)
   134    154             {
          155  +            Interlocked.Increment(ref _poolOpened);
   135    156               return hdl;
   136    157             }
   137    158             GC.KeepAlive(hdl);
   138    159           }
   139    160           return null;
   140    161         }
   141    162       }
................................................................................
   146    167       /// </summary>
   147    168       internal static void ClearAllPools()
   148    169       {
   149    170         lock (_connections)
   150    171         {
   151    172           foreach (KeyValuePair<string, Pool> pair in _connections)
   152    173           {
   153         -          while (pair.Value.Queue.Count > 0)
          174  +          if (pair.Value == null)
          175  +            continue;
          176  +
          177  +          Queue<WeakReference> poolQueue = pair.Value.Queue;
          178  +
          179  +          while (poolQueue.Count > 0)
   154    180             {
   155         -            WeakReference cnn = pair.Value.Queue.Dequeue();
          181  +            WeakReference cnn = poolQueue.Dequeue();
   156    182               SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
   157    183               if (hdl != null)
   158    184               {
   159    185                 hdl.Dispose();
   160    186               }
   161    187               GC.KeepAlive(hdl);
   162    188             }
................................................................................
   223    249           {
   224    250             ResizePool(queue, true);
   225    251   
   226    252             Queue<WeakReference> poolQueue = queue.Queue;
   227    253             if (poolQueue == null) return;
   228    254   
   229    255             poolQueue.Enqueue(new WeakReference(hdl, false));
          256  +          Interlocked.Increment(ref _poolClosed);
   230    257             GC.KeepAlive(hdl);
   231    258           }
   232    259           else
   233    260           {
   234    261             hdl.Close();
   235    262           }
   236    263         }

Changes to Tests/tkt-996d13cd87.eagle.

    52     52             int errors = 0;
    53     53   
    54     54             //
    55     55             // NOTE: This is the total number of test threads to create.
    56     56             //
    57     57             int count = 100;
    58     58   
           59  +          //
           60  +          // NOTE: This is the total number of times we should force a full
           61  +          //       garbage collection.
           62  +          //
           63  +          int gcCount = 2;
           64  +
    59     65             //
    60     66             // NOTE: Create a random number generator suitable for waiting a
    61     67             //       random number of milliseconds between each attempt to
    62     68             //       cause the race condition on a given thread.
    63     69             //
    64     70             Random random = new Random();
    65     71   
................................................................................
   129    135                   //
   130    136                   goEvent.WaitOne();
   131    137   
   132    138                   //
   133    139                   // NOTE: Wait a random number of milliseconds before forcing a
   134    140                   //       full garbage collection.
   135    141                   //
   136         -                Thread.Sleep(random.Next(0, 1000));
   137         -                GC.GetTotalMemory(true);
          142  +                for (int index = 0; index < gcCount; index++)
          143  +                {
          144  +                  Thread.Sleep(random.Next(0, 1000));
          145  +                  GC.GetTotalMemory(true);
          146  +                }
   138    147                 }
   139    148                 catch (Exception e)
   140    149                 {
   141    150                   Interlocked.Increment(ref errors);
   142    151                   Trace.WriteLine(e);
   143    152                 }
   144    153               };
................................................................................
   208    217       }
   209    218     }] true true true results errors System.Data.SQLite.dll]
   210    219   
   211    220     list $code $results \
   212    221         [expr {[info exists errors] ? $errors : ""}] \
   213    222         [expr {$code eq "Ok" ? [catch {
   214    223           object invoke _Dynamic${id}.Test${id} Main
   215         -      } result] : [set result ""]}] $result
          224  +      } result] : [set result ""]}] $result \
          225  +      [expr {[object invoke -flags +NonPublic \
          226  +          System.Data.SQLite.SQLiteConnectionPool _poolOpened] > 0}] \
          227  +      [expr {[object invoke -flags +NonPublic \
          228  +          System.Data.SQLite.SQLiteConnectionPool _poolClosed] > 0}]
   216    229   } -cleanup {
   217    230     object invoke System.Data.SQLite.SQLiteConnection ClearAllPools
   218    231     object invoke GC GetTotalMemory true
   219    232   
   220    233     cleanupDb $fileName
   221    234   
   222    235     unset -nocomplain result results errors code sql dataSource id db fileName
   223    236   } -constraints \
   224    237   {eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -match \
   225         -regexp -result {^Ok System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 \d+$}}
          238  +regexp -result {^Ok System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 \d+ True\
          239  +True$}}
   226    240   
   227    241   ###############################################################################
   228    242   
   229    243   runSQLiteTestEpilogue
   230    244   runTestEpilogue