###############################################################################
#
# tkt-996d13cd87.eagle --
#
# Written by Joe Mistachkin.
# Released to the public domain, use at your own risk!
#
###############################################################################

package require Eagle
package require Eagle.Library
package require Eagle.Test

runTestPrologue

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

package require System.Data.SQLite.Test
runSQLiteTestPrologue

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

for {set i 1} {$i < 11} {incr i} {
  set pooling [expr {$i % 2 == 0 ? True : False}]
  set count [expr {$i <= 2 ? 100 : int(rand() * 100 + 1)}]

  runTest {test [appendArgs tkt-996d13cd87-1. $i] {SQLiteConnection stress} \
      -setup {
    set fileName [appendArgs tkt-996d13cd87-1. $i .db]

    tputs $test_channel [appendArgs \
        "---- using a total of " $count " threads...\n"]

    if {![haveConstraint runtime.noPoolCounts] && [catch {
        object invoke -flags +NonPublic \
            System.Data.SQLite.SQLiteConnectionPool _poolOpened 0

        object invoke -flags +NonPublic \
            System.Data.SQLite.SQLiteConnectionPool _poolClosed 0
    }] == 0} then {
      set havePoolCounts true
    } else {
      set havePoolCounts false

      tputs $test_channel \
          "==== WARNING: connection pool counts are not available\n"
    }

    proc getPoolCounts {} {
      #
      # NOTE: If we have the ability to determine the opened/closed pool
      #       counts, fetch them now; otherwise, just set them to zero.
      #
      if {$::havePoolCounts} then {
        set ::poolCounts(opened) [object invoke -flags +NonPublic \
            System.Data.SQLite.SQLiteConnectionPool _poolOpened]

        set ::poolCounts(closed) [object invoke -flags +NonPublic \
            System.Data.SQLite.SQLiteConnectionPool _poolClosed]

        tputs $::test_channel [appendArgs \
            "---- opened " $::poolCounts(opened) " connections from the pool\n"]

        tputs $::test_channel [appendArgs \
            "---- closed " $::poolCounts(closed) " connections to the pool\n"]
      } else {
        set ::poolCounts(opened) 0
        set ::poolCounts(closed) 0
      }

      return ""
    }
  } -body {
    set id [object invoke Interpreter.GetActive NextId]
    set dataSource [file join [getDatabaseDirectory] $fileName]

    set sql { \
      CREATE TABLE t1(x TEXT); \
      INSERT INTO t1 (x) VALUES(RANDOMBLOB(1000)); \
    }

    unset -nocomplain results errors

    set code [compileCSharpWith [subst {
      using System;
      using System.Data.SQLite;
      using System.Diagnostics;
      using System.Threading;

      namespace _Dynamic${id}
      {
        public static class Test${id}
        {
          public static int Main()
          {
            //
            // NOTE: This is the total number of exceptions caught by all the
            //       test threads.
            //
            int errors = 0;

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

            //
            // 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();

            //
            // NOTE: Create the event that will be used to synchronize all the
            //       created threads so that they start doing their actual test
            //       "work" at approximately the same time.
            //
            using (ManualResetEvent goEvent = new ManualResetEvent(false))
            {
              //
              // NOTE: Create a (reusable) delegate that will contain the code
              //       that most of the created test threads are going to
              //       execute.  The code in this delegate will create, open,
              //       use, and close a single database connection with (or
              //       without) pooling enabled.
              //
              ThreadStart threadStart1 = delegate()
              {
                try
                {
                  //
                  // NOTE: Wait forever for the "GO" signal so that all threads
                  //       can start working at approximately the same time.
                  //
                  goEvent.WaitOne();

                  //
                  // NOTE: Try to create, open, and close a database connection
                  //       and then wait a random number of milliseconds before
                  //       doing it again.
                  //
                  Thread.Sleep(random.Next(0, 500));

                  SQLiteConnection connection = new SQLiteConnection(
                      "Data Source=${dataSource};Pooling=${pooling};" +
                      "[getFlagsProperty]");

                  connection.Open();

                  using (SQLiteCommand command = new SQLiteCommand("${sql}",
                      connection))
                  {
                    command.ExecuteNonQuery();
                  }

                  connection.Close();
                  connection = null;
                }
                catch (Exception e)
                {
                  Interlocked.Increment(ref errors);
                  Trace.WriteLine(e);
                }
              };

              //
              // NOTE: Create a (reusable) delegate that will contain the code
              //       that the garbage collection thread is to execute.  The
              //       code in this delegate will attempt to force a full round
              //       of garbage collection several times.
              //
              ThreadStart threadStart2 = delegate()
              {
                try
                {
                  //
                  // NOTE: Wait forever for the "GO" signal so that all threads
                  //       can start working at approximately the same time.
                  //
                  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);
                }
              };

              //
              // NOTE: Create the array of thread objects.
              //
              Thread\[\] thread = new Thread\[count\];

              //
              // NOTE: Create each of the test threads with a suitable stack
              //       size.  We must specify a stack size here because the
              //       default one for the process would be the same as the
              //       parent executable (the Eagle shell), which is 16MB,
              //       too large to be useful.
              //
              for (int index = 0; index < count; index++)
              {
                //
                // NOTE: Figure out what kind of thread to create (i.e. one
                //       that uses a connection or one that calls the GC).
                //
                ThreadStart threadStart;

                if (index == 0)
                  threadStart = threadStart2;
                else
                  threadStart = threadStart1;

                thread\[index\] = new Thread(threadStart, 1048576);

                //
                // NOTE: Name each thread for a better debugging experience.
                //
                thread\[index\].Name = String.Format(
                    "[file rootname ${fileName}] #{0}", index);
              }

              //
              // NOTE: Start all the threads now.  They should not actually do
              //       any of the test "work" until we signal the event.
              //
              for (int index = 0; index < count; index++)
                thread\[index\].Start();

              //
              // NOTE: Send the signal that all threads should start doing
              //       their test "work" now.
              //
              goEvent.Set(); /* GO */

              //
              // NOTE: Wait forever for each thread to finish its test "work"
              //       and then die.
              //
              for (int index = 0; index < count; index++)
                thread\[index\].Join();
            }

            //
            // NOTE: Return the total number of exceptions caught by the test
            //       threads.
            //
            return errors;
          }
        }
      }
    }] 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 [getPoolCounts] \
        [expr {$havePoolCounts ? $pooling ? $poolCounts(opened) > 0 : \
            $poolCounts(opened) == 0} : True] \
        [expr {$havePoolCounts ? $pooling ? $poolCounts(closed) > 0 : \
            $poolCounts(closed) == 0} : True]
  } -cleanup {
    object invoke System.Data.SQLite.SQLiteConnection ClearAllPools
    collectGarbage $test_channel

    cleanupDb $fileName

    unset -nocomplain result results errors code sql dataSource id \
        poolCounts havePoolCounts fileName

    rename getPoolCounts ""
  } -constraints {eagle monoBug28 command.sql compile.DATA\
SQLite System.Data.SQLite compileCSharp} -match regexp -result {^Ok\
System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 \d+ \{\} True True$}}
}

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

unset -nocomplain count pooling i

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

runSQLiteTestEpilogue
runTestEpilogue
