System.Data.SQLite
Check-in [a27556f61d]
Not logged in

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

Overview
Comment:Support asynchronous completion of distributed transactions, fix for [5cee5409f8]. Add experimental WaitForEnlistmentReset method to the SQLiteConnection class, pursuant to [7e1dd697dc].
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: a27556f61daf7e5470556c9a843899c529643733
User & Date: mistachkin 2018-01-28 05:28:38
Context
2018-01-28
05:38
Update the VsWhere tool in externals to the 2.3.2 release. check-in: 1e2a7d22d2 user: mistachkin tags: trunk
05:28
Support asynchronous completion of distributed transactions, fix for [5cee5409f8]. Add experimental WaitForEnlistmentReset method to the SQLiteConnection class, pursuant to [7e1dd697dc]. check-in: a27556f61d user: mistachkin tags: trunk
05:27
Update version history docs. Closed-Leaf check-in: 81d4c53f90 user: mistachkin tags: tkt-5cee5409f8
2018-01-26
02:22
Further debugging enhancements. check-in: 0b0bd83ddd user: mistachkin tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to Doc/Extra/Provider/version.html.

    43     43       <div id="mainSection">
    44     44       <div id="mainBody">
    45     45       <h1 class="heading">Version History</h1>
    46     46       <p><b>1.0.107.0 - January XX, 2018 <font color="red">(release scheduled)</font></b></p>
    47     47       <ul>
    48     48         <li>Updated to <a href="https://www.sqlite.org/draft/releaselog/3_22_0.html">SQLite 3.22.0</a>.</li>
    49     49         <li>Improve performance of type name lookups by removing superfluous locking and string creation.</li>
           50  +      <li>Support asynchronous completion of distributed transactions. Fix for <a href="https://system.data.sqlite.org/index.html/info/5cee5409f8">[5cee5409f8]</a>.</li>
           51  +      <li>Add experimental WaitForEnlistmentReset method to the SQLiteConnection class. Pursuant to <a href="https://system.data.sqlite.org/index.html/info/7e1dd697dc">[7e1dd697dc]</a>.</li>
    50     52         <li>Fix some internal memory accounting present only in the debug build.</li>
    51     53         <li>Make sure inbound native delegates are unhooked before adding a connection to the pool. Fix for <a href="https://system.data.sqlite.org/index.html/info/0e48e80333">[0e48e80333]</a>.</li>
    52     54         <li>Add preliminary support for the .NET Framework 4.7.1.</li>
    53     55         <li>Updates to internal DbType mapping related lookup tables. Pursuant to <a href="https://system.data.sqlite.org/index.html/info/a799e3978f">[a799e3978f]</a>.</li>
    54     56       </ul>
    55     57       <p><b>1.0.106.0 - November 2, 2017</b></p>
    56     58       <ul>

Changes to Setup/data/verify.lst.

   861    861     Tests/tkt-5251bd0878.eagle
   862    862     Tests/tkt-53633bbe39.eagle
   863    863     Tests/tkt-544dba0a2f.eagle
   864    864     Tests/tkt-5535448538.eagle
   865    865     Tests/tkt-56b42d99c1.eagle
   866    866     Tests/tkt-58ed318f2f.eagle
   867    867     Tests/tkt-59edc1018b.eagle
          868  +  Tests/tkt-5cee5409f8.eagle
   868    869     Tests/tkt-6434e23a0f.eagle
   869    870     Tests/tkt-647d282d11.eagle
   870    871     Tests/tkt-69cf6e5dc8.eagle
   871    872     Tests/tkt-6c6ecccc5f.eagle
   872    873     Tests/tkt-71bedaca19.eagle
   873    874     Tests/tkt-72905c9a77.eagle
   874    875     Tests/tkt-74542e702e.eagle

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

    14     14     using System.Collections.Generic;
    15     15     using System.Globalization;
    16     16     using System.ComponentModel;
    17     17     using System.Reflection;
    18     18     using System.Runtime.InteropServices;
    19     19     using System.IO;
    20     20     using System.Text;
           21  +  using System.Threading;
    21     22   
    22     23     /////////////////////////////////////////////////////////////////////////////////////////////////
    23     24   
    24     25     /// <summary>
    25     26     /// This class represents a single value to be returned
    26     27     /// from the <see cref="SQLiteDataReader" /> class via
    27     28     /// its <see cref="SQLiteDataReader.GetBlob" />,
................................................................................
  1476   1477   
  1477   1478       /// <summary>
  1478   1479       /// The default isolation level for new transactions
  1479   1480       /// </summary>
  1480   1481       private IsolationLevel _defaultIsolation;
  1481   1482   
  1482   1483   #if !PLATFORM_COMPACTFRAMEWORK
         1484  +    /// <summary>
         1485  +    /// This object is used with lock statements to synchronize access to the
         1486  +    /// <see cref="_enlistment" /> field, below.
         1487  +    /// </summary>
         1488  +    internal readonly object _enlistmentSyncRoot = new object();
         1489  +
  1483   1490       /// <summary>
  1484   1491       /// Whether or not the connection is enlisted in a distrubuted transaction
  1485   1492       /// </summary>
  1486   1493       internal SQLiteEnlistment _enlistment;
  1487   1494   #endif
  1488   1495   
  1489   1496       /// <summary>
................................................................................
  2029   2036                   // NOTE: If we need to retry the previous operation, wait for
  2030   2037                   //       the number of milliseconds specified by our caller
  2031   2038                   //       unless the caller used a negative number, in that case
  2032   2039                   //       skip sleeping at all because we do not want to block
  2033   2040                   //       this thread forever.
  2034   2041                   //
  2035   2042                   if (retry && (retryMilliseconds >= 0))
  2036         -                    System.Threading.Thread.Sleep(retryMilliseconds);
         2043  +                    Thread.Sleep(retryMilliseconds);
  2037   2044   
  2038   2045                   //
  2039   2046                   // NOTE: There is no point in calling the native API to copy
  2040   2047                   //       zero pages as it does nothing; therefore, stop now.
  2041   2048                   //
  2042   2049                   if (pages == 0)
  2043   2050                       break;
................................................................................
  2916   2923         OnChanged(this, new ConnectionEventArgs(
  2917   2924             SQLiteConnectionEventType.Closing, null, null, null, null, null,
  2918   2925             null, null));
  2919   2926   
  2920   2927         if (_sql != null)
  2921   2928         {
  2922   2929   #if !PLATFORM_COMPACTFRAMEWORK
  2923         -        if (_enlistment != null)
         2930  +        lock (_enlistmentSyncRoot) /* TRANSACTIONAL */
  2924   2931           {
  2925         -          // If the connection is enlisted in a transaction scope and the scope is still active,
  2926         -          // we cannot truly shut down this connection until the scope has completed.  Therefore make a
  2927         -          // hidden connection temporarily to hold open the connection until the scope has completed.
  2928         -          SQLiteConnection cnn = new SQLiteConnection();
         2932  +          SQLiteEnlistment enlistment = _enlistment;
         2933  +          _enlistment = null;
         2934  +
         2935  +          if (enlistment != null)
         2936  +          {
         2937  +            // If the connection is enlisted in a transaction scope and the scope is still active,
         2938  +            // we cannot truly shut down this connection until the scope has completed.  Therefore make a
         2939  +            // hidden connection temporarily to hold open the connection until the scope has completed.
         2940  +            SQLiteConnection cnn = new SQLiteConnection();
  2929   2941   
  2930   2942   #if DEBUG
  2931         -          cnn._debugString = HelperMethods.StringFormat(
  2932         -              CultureInfo.InvariantCulture,
  2933         -              "closeThreadId = {0}, {1}{2}{2}{3}",
  2934         -              HelperMethods.GetThreadId(), _sql,
  2935         -              Environment.NewLine, _debugString);
  2936         -#endif
  2937         -
  2938         -          cnn._sql = _sql;
  2939         -          cnn._transactionLevel = _transactionLevel;
  2940         -          cnn._transactionSequence = _transactionSequence;
  2941         -          cnn._enlistment = _enlistment;
  2942         -          cnn._connectionState = _connectionState;
  2943         -          cnn._version = _version;
  2944         -
  2945         -          cnn._enlistment._transaction._cnn = cnn;
  2946         -          cnn._enlistment._disposeConnection = true;
  2947         -
  2948         -          _sql = null;
  2949         -          _enlistment = null;
         2943  +            cnn._debugString = HelperMethods.StringFormat(
         2944  +                CultureInfo.InvariantCulture,
         2945  +                "closeThreadId = {0}, {1}{2}{2}{3}",
         2946  +                HelperMethods.GetThreadId(), _sql,
         2947  +                Environment.NewLine, _debugString);
         2948  +#endif
         2949  +
         2950  +            cnn._sql = _sql;
         2951  +            cnn._transactionLevel = _transactionLevel;
         2952  +            cnn._transactionSequence = _transactionSequence;
         2953  +            cnn._enlistment = enlistment;
         2954  +            cnn._connectionState = _connectionState;
         2955  +            cnn._version = _version;
         2956  +
         2957  +            SQLiteTransaction transaction = enlistment._transaction;
         2958  +
         2959  +            if (transaction != null)
         2960  +                transaction._cnn = cnn;
         2961  +
         2962  +            enlistment._disposeConnection = true;
         2963  +
         2964  +            _sql = null;
         2965  +          }
  2950   2966           }
  2951   2967   #endif
  2952   2968           if (_sql != null)
  2953   2969           {
  2954   2970             _sql.Close(_disposing);
  2955   2971             _sql = null;
  2956   2972           }
................................................................................
  3393   3409   #if !PLATFORM_COMPACTFRAMEWORK
  3394   3410       /// <summary>
  3395   3411       /// Manual distributed transaction enlistment support
  3396   3412       /// </summary>
  3397   3413       /// <param name="transaction">The distributed transaction to enlist in</param>
  3398   3414       public override void EnlistTransaction(System.Transactions.Transaction transaction)
  3399   3415       {
  3400         -      CheckDisposed();
  3401         -
  3402         -      if (_enlistment != null && transaction == _enlistment._scope)
  3403         -        return;
  3404         -      else if (_enlistment != null)
  3405         -        throw new ArgumentException("Already enlisted in a transaction");
  3406         -
  3407         -      if (_transactionLevel > 0 && transaction != null)
  3408         -        throw new ArgumentException("Unable to enlist in transaction, a local transaction already exists");
  3409         -      else if (transaction == null)
  3410         -        throw new ArgumentNullException("Unable to enlist in transaction, it is null");
  3411         -
  3412         -      bool strictEnlistment = ((_flags & SQLiteConnectionFlags.StrictEnlistment) ==
  3413         -          SQLiteConnectionFlags.StrictEnlistment);
  3414         -
  3415         -      _enlistment = new SQLiteEnlistment(this, transaction,
  3416         -          GetFallbackDefaultIsolationLevel(), strictEnlistment,
  3417         -          strictEnlistment);
  3418         -
  3419         -      OnChanged(this, new ConnectionEventArgs(
  3420         -          SQLiteConnectionEventType.EnlistTransaction, null, null, null, null,
  3421         -          null, null, new object[] { _enlistment }));
         3416  +        CheckDisposed();
         3417  +
         3418  +        lock (_enlistmentSyncRoot) /* TRANSACTIONAL */
         3419  +        {
         3420  +            if (_enlistment != null && transaction == _enlistment._scope)
         3421  +                return;
         3422  +            else if (_enlistment != null)
         3423  +                throw new ArgumentException("Already enlisted in a transaction");
         3424  +
         3425  +            if (_transactionLevel > 0 && transaction != null)
         3426  +                throw new ArgumentException("Unable to enlist in transaction, a local transaction already exists");
         3427  +            else if (transaction == null)
         3428  +                throw new ArgumentNullException("Unable to enlist in transaction, it is null");
         3429  +
         3430  +            bool strictEnlistment = ((_flags & SQLiteConnectionFlags.StrictEnlistment) ==
         3431  +                SQLiteConnectionFlags.StrictEnlistment);
         3432  +
         3433  +            _enlistment = new SQLiteEnlistment(this, transaction,
         3434  +                GetFallbackDefaultIsolationLevel(), strictEnlistment,
         3435  +                strictEnlistment);
         3436  +
         3437  +            OnChanged(this, new ConnectionEventArgs(
         3438  +                SQLiteConnectionEventType.EnlistTransaction, null, null, null, null,
         3439  +                null, null, new object[] { _enlistment }));
         3440  +        }
         3441  +    }
         3442  +
         3443  +    /// <summary>
         3444  +    /// <![CDATA[<b>]]>EXPERIMENTAL<![CDATA[</b>]]>
         3445  +    /// Waits for the enlistment associated with this connection to be reset.
         3446  +    /// </summary>
         3447  +    /// <param name="timeoutMilliseconds">
         3448  +    /// The approximate maximum number of milliseconds to wait before timing
         3449  +    /// out the wait operation.
         3450  +    /// </param>
         3451  +    /// <returns>
         3452  +    /// Non-zero if the enlistment assciated with this connection was reset;
         3453  +    /// otherwise, zero.  It should be noted that this method returning a
         3454  +    /// non-zero value does not necessarily guarantee that the connection
         3455  +    /// can enlist in a new transaction (i.e. due to potentical race with
         3456  +    /// other threads); therefore, callers should generally use try/catch
         3457  +    /// when calling the <see cref="EnlistTransaction" /> method.
         3458  +    /// </returns>
         3459  +    public bool WaitForEnlistmentReset(
         3460  +        int timeoutMilliseconds
         3461  +        )
         3462  +    {
         3463  +        CheckDisposed();
         3464  +
         3465  +        if (timeoutMilliseconds < 0)
         3466  +            throw new ArgumentException("timeout cannot be negative");
         3467  +
         3468  +        const int defaultMilliseconds = 100;
         3469  +        int sleepMilliseconds;
         3470  +
         3471  +        if (timeoutMilliseconds == 0)
         3472  +        {
         3473  +            sleepMilliseconds = 0;
         3474  +        }
         3475  +        else
         3476  +        {
         3477  +            sleepMilliseconds = Math.Min(
         3478  +                timeoutMilliseconds / 10, defaultMilliseconds);
         3479  +
         3480  +            if (sleepMilliseconds == 0)
         3481  +                sleepMilliseconds = defaultMilliseconds;
         3482  +        }
         3483  +
         3484  +        DateTime start = DateTime.UtcNow;
         3485  +
         3486  +        while (true)
         3487  +        {
         3488  +            //
         3489  +            // NOTE: Attempt to acquire the necessary lock without blocking.
         3490  +            //       This method will treat a failure to obtain the lock the
         3491  +            //       same as the enlistment not being reset yet.  Both will
         3492  +            //       advance toward the timeout.
         3493  +            //
         3494  +            bool locked = Monitor.TryEnter(_enlistmentSyncRoot);
         3495  +
         3496  +            try
         3497  +            {
         3498  +                if (locked)
         3499  +                {
         3500  +                    //
         3501  +                    // NOTE: Is there still an enlistment?  If not, we are
         3502  +                    //       done.  There is a potential race condition in
         3503  +                    //       the caller if another thread is able to setup
         3504  +                    //       a new enlistment at any point prior to our
         3505  +                    //       caller fully dealing with the result of this
         3506  +                    //       method.  However, that should generally never
         3507  +                    //       happen because this class is not intended to
         3508  +                    //       be used by multiple concurrent threads, with
         3509  +                    //       the notable exception of an active enlistment
         3510  +                    //       being asynchronously committed or rolled back
         3511  +                    //       by the .NET Framework.
         3512  +                    //
         3513  +                    if (_enlistment == null)
         3514  +                        return true;
         3515  +                }
         3516  +            }
         3517  +            finally
         3518  +            {
         3519  +                if (locked)
         3520  +                {
         3521  +                    Monitor.Exit(_enlistmentSyncRoot);
         3522  +                    locked = false;
         3523  +                }
         3524  +            }
         3525  +
         3526  +            //
         3527  +            // NOTE: A timeout value of zero is special.  It means never
         3528  +            //       sleep.
         3529  +            //
         3530  +            if (sleepMilliseconds == 0)
         3531  +                return false;
         3532  +
         3533  +            //
         3534  +            // NOTE: How much time has elapsed since we first starting
         3535  +            //       waiting?
         3536  +            //
         3537  +            DateTime now = DateTime.UtcNow;
         3538  +            TimeSpan elapsed = now.Subtract(start);
         3539  +
         3540  +            //
         3541  +            // NOTE: Are we done wait?
         3542  +            //
         3543  +            double totalMilliseconds = elapsed.TotalMilliseconds;
         3544  +
         3545  +            if ((totalMilliseconds < 0) || /* Time went backward? */
         3546  +                (totalMilliseconds >= (double)timeoutMilliseconds))
         3547  +            {
         3548  +                return false;
         3549  +            }
         3550  +
         3551  +            //
         3552  +            // NOTE: Sleep for a bit and then try again.
         3553  +            //
         3554  +            Thread.Sleep(sleepMilliseconds);
         3555  +        }
  3422   3556       }
  3423   3557   #endif
  3424   3558   
  3425   3559       /// <summary>
  3426   3560       /// Looks for a key in the array of key/values of the parameter string.  If not found, return the specified default value
  3427   3561       /// </summary>
  3428   3562       /// <param name="items">The list to look in</param>

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

   102    102           return defaultIsolationLevel;
   103    103       }
   104    104   
   105    105       ///////////////////////////////////////////////////////////////////////////
   106    106   
   107    107       private void Cleanup(SQLiteConnection cnn)
   108    108       {
   109         -        if (_disposeConnection)
          109  +        if (_disposeConnection && (cnn != null))
   110    110               cnn.Dispose();
   111    111   
   112    112           _transaction = null;
   113    113           _scope = null;
   114    114       }
   115    115       #endregion
   116    116   
................................................................................
   180    180       #endregion
   181    181   
   182    182       ///////////////////////////////////////////////////////////////////////////
   183    183   
   184    184       #region IEnlistmentNotification Members
   185    185       public void Commit(Enlistment enlistment)
   186    186       {
   187         -      CheckDisposed();
   188         -
   189         -      SQLiteConnection cnn = _transaction.Connection;
   190         -      cnn._enlistment = null;
   191         -
   192         -      try
   193         -      {
   194         -        _transaction.IsValid(true);
   195         -        _transaction.Connection._transactionLevel = 1;
   196         -        _transaction.Commit();
   197         -
   198         -        enlistment.Done();
   199         -      }
   200         -      finally
   201         -      {
   202         -        Cleanup(cnn);
   203         -      }
          187  +        CheckDisposed();
          188  +
          189  +        SQLiteConnection cnn = null;
          190  +
          191  +        try
          192  +        {
          193  +            while (true)
          194  +            {
          195  +                cnn = _transaction.Connection;
          196  +
          197  +                if (cnn == null)
          198  +                    break;
          199  +
          200  +                lock (cnn._enlistmentSyncRoot) /* TRANSACTIONAL */
          201  +                {
          202  +                    //
          203  +                    // NOTE: This check is necessary to detect the case where
          204  +                    //       the SQLiteConnection.Close() method changes the
          205  +                    //       connection associated with our transaction (i.e.
          206  +                    //       to avoid a race (condition) between grabbing the
          207  +                    //       Connection property and locking its enlistment).
          208  +                    //
          209  +                    if (!Object.ReferenceEquals(cnn, _transaction.Connection))
          210  +                        continue;
          211  +
          212  +                    cnn._enlistment = null;
          213  +
          214  +                    _transaction.IsValid(true); /* throw */
          215  +                    cnn._transactionLevel = 1;
          216  +                    _transaction.Commit();
          217  +
          218  +                    break;
          219  +                }
          220  +            }
          221  +
          222  +            enlistment.Done();
          223  +        }
          224  +        finally
          225  +        {
          226  +            Cleanup(cnn);
          227  +        }
   204    228       }
   205    229   
   206    230       ///////////////////////////////////////////////////////////////////////////
   207    231   
   208    232       public void InDoubt(Enlistment enlistment)
   209    233       {
   210    234         CheckDisposed();
................................................................................
   223    247           preparingEnlistment.Prepared();
   224    248       }
   225    249   
   226    250       ///////////////////////////////////////////////////////////////////////////
   227    251   
   228    252       public void Rollback(Enlistment enlistment)
   229    253       {
   230         -      CheckDisposed();
          254  +        CheckDisposed();
          255  +
          256  +        SQLiteConnection cnn = null;
          257  +
          258  +        try
          259  +        {
          260  +            while (true)
          261  +            {
          262  +                cnn = _transaction.Connection;
          263  +
          264  +                if (cnn == null)
          265  +                    break;
          266  +
          267  +                lock (cnn._enlistmentSyncRoot) /* TRANSACTIONAL */
          268  +                {
          269  +                    //
          270  +                    // NOTE: This check is necessary to detect the case where
          271  +                    //       the SQLiteConnection.Close() method changes the
          272  +                    //       connection associated with our transaction (i.e.
          273  +                    //       to avoid a race (condition) between grabbing the
          274  +                    //       Connection property and locking its enlistment).
          275  +                    //
          276  +                    if (!Object.ReferenceEquals(cnn, _transaction.Connection))
          277  +                        continue;
          278  +
          279  +                    cnn._enlistment = null;
          280  +
          281  +                    _transaction.Rollback();
   231    282   
   232         -      SQLiteConnection cnn = _transaction.Connection;
   233         -      cnn._enlistment = null;
          283  +                    break;
          284  +                }
          285  +            }
   234    286   
   235         -      try
   236         -      {
   237         -        _transaction.Rollback();
   238         -        enlistment.Done();
   239         -      }
   240         -      finally
   241         -      {
   242         -        Cleanup(cnn);
   243         -      }
          287  +            enlistment.Done();
          288  +        }
          289  +        finally
          290  +        {
          291  +            Cleanup(cnn);
          292  +        }
   244    293       }
   245    294       #endregion
   246    295     }
   247    296   }
   248    297   #endif // !PLATFORM_COMPACT_FRAMEWORK

Added Tests/tkt-5cee5409f8.eagle.

            1  +###############################################################################
            2  +#
            3  +# tkt-5cee5409f8.eagle --
            4  +#
            5  +# Written by Joe Mistachkin.
            6  +# Released to the public domain, use at your own risk!
            7  +#
            8  +###############################################################################
            9  +
           10  +package require Eagle
           11  +package require Eagle.Library
           12  +package require Eagle.Test
           13  +
           14  +runTestPrologue
           15  +
           16  +###############################################################################
           17  +
           18  +package require System.Data.SQLite.Test
           19  +runSQLiteTestPrologue
           20  +
           21  +###############################################################################
           22  +
           23  +runTest {test tkt-5cee5409f8-1.1 {asynchronous transaction handling} -setup {
           24  +  setupDb [set fileName tkt-5cee5409f8-1.1.db]
           25  +} -body {
           26  +  sql execute $db "CREATE TABLE t1(x INTEGER);"
           27  +
           28  +  set id [object invoke Interpreter.GetActive NextId]
           29  +  set dataSource [file join [getDatabaseDirectory] $fileName]
           30  +
           31  +  unset -nocomplain results errors
           32  +
           33  +  set code [compileCSharpWith [subst {
           34  +    using System;
           35  +    using System.Data.SQLite;
           36  +    using System.Threading;
           37  +    using System.Transactions;
           38  +
           39  +    namespace _Dynamic${id}
           40  +    {
           41  +      public static class Test${id}
           42  +      {
           43  +        #region Private EnlistmentNotification Class
           44  +        private sealed class EnlistmentNotification : IEnlistmentNotification
           45  +        {
           46  +          #region Private Data
           47  +          private bool forceRollback;
           48  +          #endregion
           49  +
           50  +          /////////////////////////////////////////////////////////////////////
           51  +
           52  +          #region Private Constructors
           53  +          private EnlistmentNotification(bool forceRollback)
           54  +          {
           55  +            this.forceRollback = forceRollback;
           56  +          }
           57  +          #endregion
           58  +
           59  +          /////////////////////////////////////////////////////////////////////
           60  +
           61  +          #region IEnlistmentNotification Members
           62  +          public void Commit(Enlistment enlistment)
           63  +          {
           64  +            enlistment.Done();
           65  +          }
           66  +
           67  +          /////////////////////////////////////////////////////////////////////
           68  +
           69  +          public void InDoubt(Enlistment enlistment)
           70  +          {
           71  +            enlistment.Done();
           72  +          }
           73  +
           74  +          /////////////////////////////////////////////////////////////////////
           75  +
           76  +          public void Prepare(PreparingEnlistment preparingEnlistment)
           77  +          {
           78  +            if (forceRollback)
           79  +              preparingEnlistment.ForceRollback();
           80  +            else
           81  +              preparingEnlistment.Prepared();
           82  +          }
           83  +
           84  +          /////////////////////////////////////////////////////////////////////
           85  +
           86  +          public void Rollback(Enlistment enlistment)
           87  +          {
           88  +            enlistment.Done();
           89  +          }
           90  +          #endregion
           91  +
           92  +          /////////////////////////////////////////////////////////////////////
           93  +
           94  +          #region Public Static Methods
           95  +          public static void UseDistributedTransaction(bool forceRollback)
           96  +          {
           97  +            Transaction.Current.EnlistDurable(
           98  +                Guid.NewGuid(), new EnlistmentNotification(forceRollback),
           99  +                EnlistmentOptions.None);
          100  +          }
          101  +          #endregion
          102  +        }
          103  +        #endregion
          104  +
          105  +        ///////////////////////////////////////////////////////////////////////
          106  +
          107  +        #region Private Static Data
          108  +        private static int resetCount;
          109  +        private static int timeoutCount;
          110  +        #endregion
          111  +
          112  +        ///////////////////////////////////////////////////////////////////////
          113  +
          114  +        #region Private Static Methods
          115  +        private static void DoTransactions(SQLiteConnection connection)
          116  +        {
          117  +          Random random = new Random();
          118  +
          119  +          for (int iteration = 0; iteration < 10000; iteration++)
          120  +          {
          121  +            using (TransactionScope transactionScope = new TransactionScope())
          122  +            {
          123  +              EnlistmentNotification.UseDistributedTransaction(false);
          124  +
          125  +              TransactionInformation transactionInformation =
          126  +                  Transaction.Current.TransactionInformation;
          127  +
          128  +              if (transactionInformation.DistributedIdentifier.Equals(
          129  +                  Guid.Empty))
          130  +              {
          131  +                throw new Exception("distributed identifier is empty");
          132  +              }
          133  +
          134  +              connection.EnlistTransaction(Transaction.Current);
          135  +
          136  +              using (SQLiteCommand command = connection.CreateCommand())
          137  +              {
          138  +                command.CommandText = "INSERT INTO t1(x) VALUES(?);";
          139  +                command.Parameters.Add(new SQLiteParameter("", iteration));
          140  +                command.ExecuteNonQuery();
          141  +              }
          142  +
          143  +              transactionScope.Complete();
          144  +            }
          145  +
          146  +            Thread.Sleep(random.Next(10));
          147  +          }
          148  +        }
          149  +
          150  +        ///////////////////////////////////////////////////////////////////////
          151  +
          152  +        private static void WaitOnEnlistments(object state)
          153  +        {
          154  +          SQLiteConnection connection = (SQLiteConnection)state;
          155  +          Random random = new Random();
          156  +
          157  +          for (int iteration = 0; iteration < 1000; iteration++)
          158  +          {
          159  +            if (connection.WaitForEnlistmentReset(1))
          160  +              Interlocked.Increment(ref resetCount);
          161  +            else
          162  +              Interlocked.Increment(ref timeoutCount);
          163  +
          164  +            Thread.Sleep(random.Next(100));
          165  +          }
          166  +        }
          167  +        #endregion
          168  +
          169  +        ///////////////////////////////////////////////////////////////////////
          170  +
          171  +        #region Public Static Methods
          172  +        public static string DoTest()
          173  +        {
          174  +          using (SQLiteConnection connection = new SQLiteConnection(
          175  +              "Data Source=${dataSource};[getTestProperties]"))
          176  +          {
          177  +            ThreadPool.QueueUserWorkItem(WaitOnEnlistments, connection);
          178  +
          179  +            connection.Open();
          180  +
          181  +            DoTransactions(connection);
          182  +          }
          183  +
          184  +          int count1 = Interlocked.CompareExchange(ref resetCount, 0, 0);
          185  +          int count2 = Interlocked.CompareExchange(ref timeoutCount, 0, 0);
          186  +
          187  +          return String.Format("{0}, {1}", count1, count2);
          188  +        }
          189  +
          190  +        ///////////////////////////////////////////////////////////////////////
          191  +
          192  +        public static void Main()
          193  +        {
          194  +          // do nothing.
          195  +        }
          196  +        #endregion
          197  +      }
          198  +    }
          199  +  }] true true true results errors System.Data.SQLite.dll]
          200  +
          201  +  list $code $results \
          202  +      [expr {[info exists errors] ? $errors : ""}] \
          203  +      [expr {$code eq "Ok" ? [catch {
          204  +        object invoke _Dynamic${id}.Test${id} DoTest
          205  +      } result] : [set result ""]}] \
          206  +      [expr {[lindex $result 0] > 0}] \
          207  +      [expr {[lindex $result 1] > 0}]
          208  +} -cleanup {
          209  +  cleanupDb $fileName
          210  +
          211  +  unset -nocomplain result results errors code dataSource id db fileName
          212  +} -constraints {eagle command.object monoBug211 monoBug54 command.sql\
          213  +compile.DATA SQLite System.Data.SQLite compileCSharp} -match regexp -result \
          214  +{^Ok System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 True True$}}
          215  +
          216  +###############################################################################
          217  +
          218  +runSQLiteTestEpilogue
          219  +runTestEpilogue

Changes to readme.htm.

   209    209   
   210    210   <p>
   211    211       <b>1.0.107.0 - January XX, 2018 <font color="red">(release scheduled)</font></b>
   212    212   </p>
   213    213   <ul>
   214    214       <li>Updated to <a href="https://www.sqlite.org/draft/releaselog/3_22_0.html">SQLite 3.22.0</a>.</li>
   215    215       <li>Improve performance of type name lookups by removing superfluous locking and string creation.</li>
          216  +    <li>Support asynchronous completion of distributed transactions. Fix for [5cee5409f8].</li>
          217  +    <li>Add experimental WaitForEnlistmentReset method to the SQLiteConnection class. Pursuant to [7e1dd697dc].</li>
   216    218       <li>Fix some internal memory accounting present only in the debug build.</li>
   217    219       <li>Make sure inbound native delegates are unhooked before adding a connection to the pool. Fix for [0e48e80333].</li>
   218    220       <li>Add preliminary support for the .NET Framework 4.7.1.</li>
   219    221       <li>Updates to internal DbType mapping related lookup tables. Pursuant to [a799e3978f].</li>
   220    222   </ul>
   221    223   <p>
   222    224       <b>1.0.106.0 - November 2, 2017</b>

Changes to www/news.wiki.

    46     46   
    47     47   <p>
    48     48       <b>1.0.107.0 - January XX, 2018 <font color="red">(release scheduled)</font></b>
    49     49   </p>
    50     50   <ul>
    51     51       <li>Updated to [https://www.sqlite.org/draft/releaselog/3_22_0.html|SQLite 3.22.0].</li>
    52     52       <li>Improve performance of type name lookups by removing superfluous locking and string creation.</li>
           53  +    <li>Support asynchronous completion of distributed transactions. Fix for [5cee5409f8].</li>
           54  +    <li>Add experimental WaitForEnlistmentReset method to the SQLiteConnection class. Pursuant to [7e1dd697dc].</li>
    53     55       <li>Fix some internal memory accounting present only in the debug build.</li>
    54     56       <li>Make sure inbound native delegates are unhooked before adding a connection to the pool. Fix for [0e48e80333].</li>
    55     57       <li>Add preliminary support for the .NET Framework 4.7.1.</li>
    56     58       <li>Updates to internal DbType mapping related lookup tables. Pursuant to [a799e3978f].</li>
    57     59   </ul>
    58     60   <p>
    59     61       <b>1.0.106.0 - November 2, 2017</b>