System.Data.SQLite
Check-in [5c0646db9d]
Not logged in

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

Overview
Comment:Prevent returning a connection handle whose finalizer may be pending on the GC thread. Part of fix for ticket [996d13cd87]. Also, update Eagle in externals to latest trunk.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 5c0646db9d326095304e868e25c0c2d02e1a8bc9
User & Date: mistachkin 2012-05-04 16:04:32
Context
2012-05-04
18:28
Make sure to check for a null connection handle prior to calling the GC.SuppressFinalize method. check-in: 5cc9167d46 user: mistachkin tags: trunk
16:04
Prevent returning a connection handle whose finalizer may be pending on the GC thread. Part of fix for ticket [996d13cd87]. Also, update Eagle in externals to latest trunk. check-in: 5c0646db9d user: mistachkin tags: trunk
2012-05-03
19:21
Check each weak reference object from the queue prior to attempting to fetch its target. check-in: 2a6ee97694 user: mistachkin tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to Externals/Eagle/bin/Eagle.dll.

cannot compute difference between binary files

Changes to Externals/Eagle/bin/EagleShell.exe.

cannot compute difference between binary files

Changes to Externals/Eagle/bin/EagleShell.exe.config.

     1      1   <?xml version="1.0" encoding="UTF-8" ?>
     2      2   <!--
     3      3    *
     4      4    * EagleShell.exe.config -
     5      5    *
     6         - * Copyright (c) 2007-2010 by Joe Mistachkin.  All rights reserved.
            6  + * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
     7      7    *
     8      8    * See the file "license.terms" for information on usage and redistribution of
     9      9    * this file, and for a DISCLAIMER OF ALL WARRANTIES.
    10     10    *
    11     11    * RCS: @(#) $Id: $
    12     12    *
    13     13   -->

Changes to Externals/Eagle/bin/EagleShell.exe.mda.config.

     1      1   <?xml version="1.0" encoding="UTF-8" ?>
     2      2   <!--
     3      3    *
     4      4    * EagleShell.exe.mda.config -
     5      5    *
     6         - * Copyright (c) 2007-2010 by Joe Mistachkin.  All rights reserved.
            6  + * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
     7      7    *
     8      8    * See the file "license.terms" for information on usage and redistribution of
     9      9    * this file, and for a DISCLAIMER OF ALL WARRANTIES.
    10     10    *
    11     11    * RCS: @(#) $Id: $
    12     12    *
    13     13   -->

Changes to Externals/Eagle/lib/Eagle1.0/vendor.eagle.

   109    109               #
   110    110               lappend ::env(EAGLELIBPATH) $dir2
   111    111   
   112    112               #
   113    113               # NOTE: Force Eagle to rebuild the auto-path list for the current
   114    114               #       interpreter right now.
   115    115               #
   116         -            object invoke Utility RefreshAutoPathList
          116  +            object invoke Utility RefreshAutoPathList true
   117    117             }
   118    118   
   119    119             #
   120    120             # NOTE: We are done, return success.
   121    121             #
   122    122             return true
   123    123           }

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

   345    345       // Therefore these functions have to be static, and have to be low-level.
   346    346   
   347    347       internal static string GetLastError(SQLiteConnectionHandle hdl, IntPtr db)
   348    348       {
   349    349           if ((hdl == null) || (db == IntPtr.Zero))
   350    350               return "null connection or database handle";
   351    351   
   352         -        if (hdl.IsClosed || hdl.IsInvalid)
   353         -            return "closed or invalid connection handle";
   354         -
   355    352           lock (hdl)
   356    353           {
          354  +            if (hdl.IsClosed || hdl.IsInvalid)
          355  +                return "closed or invalid connection handle";
          356  +
   357    357   #if !SQLITE_STANDARD
   358    358               int len;
   359    359               return UTF8ToString(UnsafeNativeMethods.sqlite3_errmsg_interop(db, out len), len);
   360    360   #else
   361    361               return UTF8ToString(UnsafeNativeMethods.sqlite3_errmsg(db), -1);
   362    362   #endif
   363    363           }

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

   114    114       /// </summary>
   115    115       /// <param name="fileName">The filename for a desired connection</param>
   116    116       /// <param name="maxPoolSize">The maximum size the connection pool for the filename can be</param>
   117    117       /// <param name="version">The pool version the returned connection will belong to</param>
   118    118       /// <returns>Returns NULL if no connections were available.  Even if none are, the poolversion will still be a valid pool version</returns>
   119    119       internal static SQLiteConnectionHandle Remove(string fileName, int maxPoolSize, out int version)
   120    120       {
   121         -      lock (_connections)
   122         -      {
   123         -        Pool queue;
          121  +        Queue<WeakReference> poolQueue;
   124    122   
   125         -        // Default to the highest pool version
   126         -        version = _poolVersion;
   127         -
   128         -        // If we didn't find a pool for this file, create one even though it will be empty.
   129         -        // We have to do this here because otherwise calling ClearPool() on the file will not work for active connections
   130         -        // that have never seen the pool yet.
   131         -        if (_connections.TryGetValue(fileName, out queue) == false)
          123  +        //
          124  +        // NOTE: This lock cannot be held while checking the queue for available
          125  +        //       connections because other methods of this class are called from
          126  +        //       the GC finalizer thread and we use the WaitForPendingFinalizers
          127  +        //       method (below).  Holding this lock while calling that method
          128  +        //       would therefore result in a deadlock.  This lock is held while
          129  +        //       a temporary copy of the queue is created.
          130  +        //
          131  +        lock (_connections)
   132    132           {
   133         -          queue = new Pool(_poolVersion, maxPoolSize);
   134         -          _connections.Add(fileName, queue);
          133  +            Pool queue;
   135    134   
   136         -          return null;
          135  +            // Default to the highest pool version
          136  +            version = _poolVersion;
          137  +
          138  +            // If we didn't find a pool for this file, create one even though it will be empty.
          139  +            // We have to do this here because otherwise calling ClearPool() on the file will not work for active connections
          140  +            // that have never seen the pool yet.
          141  +            if (_connections.TryGetValue(fileName, out queue) == false)
          142  +            {
          143  +                queue = new Pool(_poolVersion, maxPoolSize);
          144  +                _connections.Add(fileName, queue);
          145  +
          146  +                return null;
          147  +            }
          148  +
          149  +            // We found a pool for this file, so use its version number
          150  +            version = queue.PoolVersion;
          151  +            queue.MaxPoolSize = maxPoolSize;
          152  +
          153  +            ResizePool(queue, false);
          154  +
          155  +            // Try and get a pooled connection from the queue
          156  +            poolQueue = new Queue<WeakReference>(queue.Queue);
          157  +            if (poolQueue == null) return null;
   137    158           }
   138    159   
   139         -        // We found a pool for this file, so use its version number
   140         -        version = queue.PoolVersion;
   141         -        queue.MaxPoolSize = maxPoolSize;
   142         -
   143         -        ResizePool(queue, false);
   144         -
   145         -        // Try and get a pooled connection from the queue
   146         -        Queue<WeakReference> poolQueue = queue.Queue;
   147         -        if (poolQueue == null) return null;
   148         -
   149    160           while (poolQueue.Count > 0)
   150    161           {
   151         -          WeakReference cnn = poolQueue.Dequeue();
   152         -          if (cnn == null) continue;
   153         -          SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
   154         -          if ((hdl != null) && !hdl.IsClosed && !hdl.IsInvalid)
   155         -          {
   156         -            Interlocked.Increment(ref _poolOpened);
   157         -            return hdl;
   158         -          }
   159         -          cnn.Target = null;
   160         -          GC.KeepAlive(hdl);
          162  +            WeakReference cnn = poolQueue.Dequeue();
          163  +            if (cnn == null) continue;
          164  +            SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
          165  +
          166  +            //
          167  +            // BUGFIX: For ticket [996d13cd87], step #1.  After this point,
          168  +            //         make sure that the finalizer for the connection handle
          169  +            //         just obtained from the queue cannot START running (i.e.
          170  +            //         it may still be pending but it will no longer start
          171  +            //         after this point).
          172  +            //
          173  +            GC.SuppressFinalize(hdl);
          174  +
          175  +            try
          176  +            {
          177  +                //
          178  +                // BUGFIX: For ticket [996d13cd87], step #2.  Now, we must wait
          179  +                //         for all pending finalizers which have STARTED running
          180  +                //         and have not yet COMPLETED.  This must be done just
          181  +                //         in case the finalizer for the connection handle just
          182  +                //         obtained from the queue has STARTED running at some
          183  +                //         point before SuppressFinalize was called on it.
          184  +                //
          185  +                //         After this point, checking properties of the
          186  +                //         connection handle (e.g. IsClosed) should work
          187  +                //         reliably without having to worry that they will
          188  +                //         (due to the finalizer) change out from under us.
          189  +                //
          190  +                GC.WaitForPendingFinalizers();
          191  +
          192  +                //
          193  +                // BUGFIX: For ticket [996d13cd87], step #3.  Next, verify that
          194  +                //         the connection handle is actually valid and [still?]
          195  +                //         not closed prior to actually returning it to our
          196  +                //         caller.
          197  +                //
          198  +                if ((hdl != null) && !hdl.IsClosed && !hdl.IsInvalid)
          199  +                {
          200  +                    Interlocked.Increment(ref _poolOpened);
          201  +                    return hdl;
          202  +                }
          203  +            }
          204  +            finally
          205  +            {
          206  +                //
          207  +                // BUGFIX: For ticket [996d13cd87], step #4.  Finally, we must
          208  +                //         re-register the connection handle for finalization
          209  +                //         now that we have a strong reference to it (i.e. the
          210  +                //         finalizer run at least until the connection is
          211  +                //         subsequently closed).
          212  +                //
          213  +                GC.ReRegisterForFinalize(hdl);
          214  +            }
          215  +
          216  +#pragma warning disable 162
          217  +            GC.KeepAlive(hdl); /* NOTE: Unreachable code. */
          218  +#pragma warning restore 162
   161    219           }
          220  +
   162    221           return null;
   163         -      }
   164    222       }
   165    223   
   166    224       /// <summary>
   167    225       /// Clears out all pooled connections and rev's up the default pool version to force all old active objects
   168    226       /// not in the pool to get discarded rather than returned to their pools.
   169    227       /// </summary>
   170    228       internal static void ClearAllPools()
................................................................................
   183    241               WeakReference cnn = poolQueue.Dequeue();
   184    242               if (cnn == null) continue;
   185    243               SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
   186    244               if (hdl != null)
   187    245               {
   188    246                 hdl.Dispose();
   189    247               }
   190         -            cnn.Target = null;
   191    248               GC.KeepAlive(hdl);
   192    249             }
   193    250             
   194    251             // Keep track of the highest revision so we can go one higher when we're finished
   195    252             if (_poolVersion <= pair.Value.PoolVersion)
   196    253               _poolVersion = pair.Value.PoolVersion + 1;
   197    254           }
................................................................................
   225    282               WeakReference cnn = poolQueue.Dequeue();
   226    283               if (cnn == null) continue;
   227    284               SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
   228    285               if (hdl != null)
   229    286               {
   230    287                 hdl.Dispose();
   231    288               }
   232         -            cnn.Target = null;
   233    289               GC.KeepAlive(hdl);
   234    290             }
   235    291           }
   236    292         }
   237    293       }
   238    294   
   239    295       /// <summary>
................................................................................
   290    346           WeakReference cnn = poolQueue.Dequeue();
   291    347           if (cnn == null) continue;
   292    348           SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle;
   293    349           if (hdl != null)
   294    350           {
   295    351             hdl.Dispose();
   296    352           }
   297         -        cnn.Target = null;
   298    353           GC.KeepAlive(hdl);
   299    354         }
   300    355       }
   301    356     }
   302    357   }

Changes to Tests/tkt-996d13cd87.eagle.

    23     23   for {set i 1} {$i < 3} {incr i} {
    24     24     set pooling [expr {$i == 2 ? True : False}]
    25     25   
    26     26     runTest {test [appendArgs tkt-996d13cd87-1. $i] {SQLiteConnection stress} \
    27     27         -setup {
    28     28       set fileName [appendArgs tkt-996d13cd87-1. $i .db]
    29     29   
    30         -    object invoke -flags +NonPublic \
    31         -        System.Data.SQLite.SQLiteConnectionPool _poolOpened 0
           30  +    if {[catch {
           31  +        object invoke -flags +NonPublic \
           32  +            System.Data.SQLite.SQLiteConnectionPool _poolOpened 0
    32     33   
    33         -    object invoke -flags +NonPublic \
    34         -        System.Data.SQLite.SQLiteConnectionPool _poolClosed 0
           34  +        object invoke -flags +NonPublic \
           35  +            System.Data.SQLite.SQLiteConnectionPool _poolClosed 0
           36  +    }] == 0} then {
           37  +      set havePoolCounts true
           38  +    } else {
           39  +      set havePoolCounts false
           40  +
           41  +      tputs $test_channel [appendArgs \
           42  +          "==== WARNING: connection pool counts are not available\n"]
           43  +    }
    35     44     } -body {
    36     45       set id [object invoke Interpreter.GetActive NextId]
    37     46       set dataSource [file join [getDatabaseDirectory] $fileName]
    38     47   
    39     48       set sql { \
    40     49         CREATE TABLE t1(x TEXT); \
    41     50         INSERT INTO t1 (x) VALUES(RANDOMBLOB(1000)); \
................................................................................
   228    237       }] true true true results errors System.Data.SQLite.dll]
   229    238   
   230    239       list $code $results \
   231    240           [expr {[info exists errors] ? $errors : ""}] \
   232    241           [expr {$code eq "Ok" ? [catch {
   233    242             object invoke _Dynamic${id}.Test${id} Main
   234    243           } result] : [set result ""]}] $result \
   235         -        [expr {$pooling ? [object invoke -flags +NonPublic \
          244  +        [expr {$havePoolCounts ? $pooling ? [object invoke -flags +NonPublic \
   236    245               System.Data.SQLite.SQLiteConnectionPool _poolOpened] > 0 : \
   237    246               [object invoke -flags +NonPublic \
   238         -            System.Data.SQLite.SQLiteConnectionPool _poolOpened] == 0}] \
   239         -        [expr {$pooling ? [object invoke -flags +NonPublic \
          247  +            System.Data.SQLite.SQLiteConnectionPool _poolOpened] == 0} : \
          248  +            True] \
          249  +        [expr {$havePoolCounts ? $pooling ? [object invoke -flags +NonPublic \
   240    250               System.Data.SQLite.SQLiteConnectionPool _poolClosed] > 0 : \
   241    251               [object invoke -flags +NonPublic \
   242         -            System.Data.SQLite.SQLiteConnectionPool _poolClosed] == 0}]
          252  +            System.Data.SQLite.SQLiteConnectionPool _poolClosed] == 0} : \
          253  +            True]
   243    254     } -cleanup {
   244    255       object invoke System.Data.SQLite.SQLiteConnection ClearAllPools
   245    256       object invoke GC GetTotalMemory true
   246    257   
   247    258       cleanupDb $fileName
   248    259   
   249         -    unset -nocomplain result results errors code sql dataSource id db fileName
          260  +    unset -nocomplain result results errors code sql dataSource id db \
          261  +        havePoolCounts fileName
   250    262     } -constraints {eagle monoBug28 command.sql compile.DATA\
   251    263   SQLite System.Data.SQLite} -match regexp -result {^Ok\
   252    264   System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 \d+ True True$}}
   253    265   }
   254    266   
   255    267   ###############################################################################
   256    268   
   257    269   unset -nocomplain pooling i
   258    270   
   259    271   ###############################################################################
   260    272   
   261    273   runSQLiteTestEpilogue
   262    274   runTestEpilogue