###############################################################################
#
# 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};" +
"[getTestProperties]");
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 false 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 command.object monoBug28 monoCrash211 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