###############################################################################
#
# thread.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
###############################################################################
checkForSQLiteDirectories $test_channel true
set handle_counts [getSQLiteHandleCounts $test_channel]
set memory_used [reportSQLiteResources $test_channel]
###############################################################################
#
# NOTE: How many test threads should be created and used for these tests? This
# value must be at least two. The first test thread (at index 0) is
# always the "master thread" and just repeatedly calls the Thread.Abort()
# method on other random test threads that appear to be alive until all
# test threads appear to have died. All other test threads will attempt
# to open a connection, execute one or more queries against it, and then
# close it.
#
if {![info exists count(1)]} then {
set count(1) 10
}
#
# NOTE: How many milliseconds should the master thread initially wait until it
# starts to abort the other test threads?
#
if {![info exists count(2)]} then {
set count(2) 1000
}
#
# NOTE: How many milliseconds (maximum) should the master thread wait between
# aborting other random test threads?
#
if {![info exists count(3)]} then {
set count(3) 1000
}
#
# NOTE: How many bytes should be used for the five random data blobs contained
# in the test database?
#
if {![info exists count(4)]} then {
set count(4) 1000
}
###############################################################################
#
# NOTE: This test is disabled for .NET Core due to its lack of support for
# the Thread.Abort method (i.e. throws PlatformNotSupportedException
# when called).
#
runTest {test thread-1.1 {Thread.Abort() impact on native resources} -setup {
setupDb [set fileName thread-1.1.db]
tputs $test_channel [appendArgs \
"---- using " $count(1) " test threads (with one master thread)\n"]
tputs $test_channel [appendArgs \
"---- initial wait will be " $count(2) " milliseconds\n"]
tputs $test_channel [appendArgs \
"---- subsequent maximum wait will be " $count(3) " milliseconds\n"]
tputs $test_channel [appendArgs \
"---- random data blob size will be " $count(4) " bytes\n"]
} -body {
sql execute $db [subst {
CREATE TABLE t1(x INTEGER PRIMARY KEY, y BLOB);
INSERT INTO t1 (y) VALUES(RANDOMBLOB(${count(4)}));
INSERT INTO t1 (y) VALUES(RANDOMBLOB(${count(4)}));
INSERT INTO t1 (y) VALUES(RANDOMBLOB(${count(4)}));
INSERT INTO t1 (y) VALUES(RANDOMBLOB(${count(4)}));
INSERT INTO t1 (y) VALUES(RANDOMBLOB(${count(4)}));
}]
#
# NOTE: The data and temporary directories must be reset here (after the
# setupDb call above) because they allocate some SQLite memory and
# this test requires an extremely accurate reading.
#
sql execute $db {
PRAGMA data_store_directory = "";
PRAGMA temp_store_directory = "";
}
#
# NOTE: Close the database now, freeing any native SQLite memory and/or
# resources that it may be using at this point; however, do not
# actually delete the database file because it is needed in the C#
# code for this test.
#
cleanupDb $fileName db true false false; unset -nocomplain db
#
# NOTE: Setup the variables used in the dynamically substituted C# code
# for the main body of this test (below).
#
set id [object invoke Interpreter.GetActive NextId]
set dataSource [file join [getDatabaseDirectory] $fileName]
set sql { \
SELECT x, y FROM t1 ORDER BY x; \
}
unset -nocomplain results errors
set code [compileCSharpWith [subst {
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Diagnostics;
using System.Threading;
using Eagle._Containers.Public;
namespace _Dynamic${id}
{
public static class Test${id}
{
public static LongList RunTestThreads()
{
//
// NOTE: This is the total number of data bytes seen in the second
// column of the query result rows seen by all test threads.
// This value will vary greatly based upon how many loop
// iterations are performed prior to each test thread being
// aborted.
//
long sum = 0;
//
// NOTE: This is the total number of query result rows seen by all
// the test threads.
//
long rows = 0;
//
// NOTE: This is the total number of exceptions caught by all the
// test threads.
//
long errors = 0;
//
// NOTE: This is the total number of test threads to create.
//
int count = ${count(1)};
//
// NOTE: Create the array of thread objects.
//
Thread\[\] thread = new Thread\[count\];
//
// NOTE: Create a random number generator suitable for waiting a
// random number of milliseconds between each loop iteration
// and then selecting a random thread index.
//
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. This is also used
// to make sure that all test threads are inside of a try
// block before attempting to abort them.
//
using (ManualResetEvent goEvent = new ManualResetEvent(false))
{
//
// NOTE: Create a (reusable) delegate that will contain the code
// that most of the created test threads are to execute.
// This code will open, use, and close a single database
// connection. Multiple commands and data readers will also
// be used.
//
ThreadStart threadStart1 = delegate()
{
try
{
//
// NOTE: Wait forever for the "GO" signal so that all threads
// can start working at approximately the same time.
// This is also used to make sure that all test threads
// are inside of a try block before attempting to abort
// them.
//
goEvent.WaitOne();
//
// NOTE: Create a new connection object. We purposely avoid
// putting this inside a "using" block to help test our
// cleanup via the garbage collector.
//
SQLiteConnection connection = new SQLiteConnection(
"Data Source=${dataSource};[getTestProperties]");
//
// NOTE: Open the connection. After this point, native memory
// and resources have been allocated by this thread.
//
connection.Open();
//
// NOTE: Loop forever until the first thread signals us to stop
// via calling Thread.Abort() method on our associated
// thread object.
//
while (true)
{
//
// NOTE: Create a dictionary to temporarily hold the values
// from the data reader for this loop iteration.
//
Dictionary<long, byte\[\]> results =
new Dictionary<long, byte\[\]>();
//
// NOTE: Create a new command object using the connection
// object for this thread. Again, avoid putting this
// inside a "using" block to help test our cleanup via
// the garbage collector.
//
SQLiteCommand command = new SQLiteCommand("${sql}",
connection);
//
// NOTE: Execute the query and get the resulting data reader
// object. Again, avoid putting this inside a "using"
// block to help test our cleanup via the garbage
// collector.
//
SQLiteDataReader reader = command.ExecuteReader();
//
// NOTE: Start processing each available query result row.
// This processing (or any of the above processing)
// may be stopped at any time due to this test thread
// being aborted.
//
while (reader.Read())
{
results.Add((long)reader\["x"\],
reader\["y"\] as byte\[\]);
Interlocked.Add(ref sum,
results\[(long)reader\["x"\]\].Length);
Interlocked.Increment(ref rows);
}
//
// NOTE: Close the data reader for this loop iteration as we
// are done with it.
//
reader.Close();
reader = null;
//
// NOTE: Dispose the command for this loop iteration as we
// are done with it.
//
command.Dispose();
command = null;
}
//
// NOTE: Close the connection for this test thread as we are
// done with it. Since the above loop is infinite, it
// should only be exited via this test thread being
// aborted; therefore, execution should never reach this
// point.
//
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 half the created threads are to execute. This code
// will repeatedly call the Thread.Abort() method on all the
// other test threads until they all appear to be dead.
//
ThreadStart threadStart2 = delegate()
{
try
{
//
// NOTE: Wait forever for the "GO" signal so that all threads
// can start working at approximately the same time.
// This is also used to make sure that all test threads
// are inside of a try block before attempting to abort
// them.
//
goEvent.WaitOne();
//
// NOTE: Give the other test threads a slight head start to
// make sure that they are fully alive prior to trying
// to abort any of them.
//
Thread.Sleep(${count(2)});
//
// NOTE: Loop forever until all test threads appear to be dead.
//
while (true)
{
//
// NOTE: Wait a random number of milliseconds, up to a full
// second.
//
Thread.Sleep(random.Next(0, ${count(3)}));
//
// NOTE: Select a random thread to abort.
//
int index = random.Next(1, count);
//
// NOTE: If the thread appears to be alive, try to abort
// it.
//
try
{
if (thread\[index\].IsAlive)
thread\[index\].Abort();
}
catch (Exception e)
{
Trace.WriteLine(e);
}
//
// NOTE: If all the other threads are dead, presumably due
// to being aborted, stop now. This check is simpler,
// and possibly more reliable, than checking if any of
// the test threads are still alive via their IsAlive
// property.
//
if (Interlocked.Increment(ref errors) == count)
break;
else
Interlocked.Decrement(ref errors);
}
}
catch (Exception e)
{
Interlocked.Increment(ref errors);
Trace.WriteLine(e);
}
};
//
// 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 a list of integers with total number of data bytes
// seen, total number of query result rows seen, and the total
// number of exceptions caught by all the test threads.
//
LongList counts = new LongList();
counts.Add(sum);
counts.Add(rows);
counts.Add(errors);
return counts;
}
///////////////////////////////////////////////////////////////////////
public static void Main()
{
// do nothing.
}
}
}
}] true true true results errors [list System.Data.SQLite.dll Eagle.dll] \
WarningLevel 0]
list $code $results \
[expr {[info exists errors] ? $errors : ""}] \
[expr {$code eq "Ok" ? [catch {
object invoke _Dynamic${id}.Test${id} RunTestThreads
} result] : [set result ""]}] $result \
[collectGarbage $test_channel [expr {$count(1) * 1000}] false] \
[getSQLiteHandleCounts $test_channel] \
[reportSQLiteResources $test_channel]
} -cleanup {
cleanupDb $fileName
unset -nocomplain result results errors code sql dataSource id db fileName
} -time true -constraints [fixConstraints {eagle command.object monoBug28\
monoCrash211 monoCrash42 monoBug46 command.sql compile.DATA SQLite\
System.Data.SQLite compileCSharp !dotNetCore}] -match regexp -result \
[appendArgs "^Ok System#CodeDom#Compiler#CompilerResults#\\d+ \\{\\} 0 \\{\\d+\
\\d+ " $count(1) "\\} \\{\\} \\{" $handle_counts "\\} " $memory_used \$]}
###############################################################################
unset -nocomplain count
###############################################################################
unset -nocomplain memory_used handle_counts
###############################################################################
runSQLiteTestEpilogue
runTestEpilogue