sqlite/test/walthread.test
2010-04-28 17:48:44 +00:00

259 lines
6.5 KiB
Plaintext

# 2010 April 13
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The
# focus of this file is testing the operation of the library in
# "PRAGMA journal_mode=WAL" mode with multiple threads.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
if {[run_thread_tests]==0} { finish_test ; return }
set sqlite_walsummary_mmap_incr 64
# The number of threads to start. And the amount of time to run the test
# for. Respectively.
#
set NTHREAD 10
set SECONDS 5
# The parameter is the name of a variable in the callers context. The
# variable may or may not exist when this command is invoked.
#
# If the variable does exist, its value is returned. Otherwise, this
# command uses [vwait] to wait until it is set, then returns the value.
# In other words, this is a version of the [set VARNAME] command that
# blocks until a variable exists.
#
proc wait_for_var {varname} {
if {0==[uplevel [list info exists $varname]]} {
uplevel [list vwait $varname]
}
uplevel [list set $varname]
}
proc lshift {lvar} {
upvar $lvar L
set ret [lindex $L 0]
set L [lrange $L 1 end]
return $ret
}
#-------------------------------------------------------------------------
# do_thread_test TESTNAME OPTIONS...
#
# where OPTIONS are:
#
# -seconds SECONDS How many seconds to run the test for
# -init SCRIPT Script to run before test.
# -thread NAME COUNT SCRIPT Scripts to run in threads (or processes).
# -processes BOOLEAN True to use processes instead of threads.
#
proc do_thread_test {args} {
set A $args
set P(testname) [lshift A]
set P(seconds) 5
set P(init) ""
set P(threads) [list]
set P(processes) 0
unset -nocomplain ::done
while {[llength $A]>0} {
set a [lshift A]
switch -glob -- $a {
-seconds {
set P(seconds) [lshift A]
}
-init {
set P(init) [lshift A]
}
-processes {
set P(processes) [lshift A]
}
-thread {
set name [lshift A]
set count [lshift A]
set prg [lshift A]
lappend P(threads) [list $name $count $prg]
}
default {
error "Unknown option: $a"
}
}
}
puts "Running $P(testname) for $P(seconds) seconds..."
catch { db close }
file delete -force test.db test.db-journal test.db-wal
sqlite3 db test.db
eval $P(init)
db close
foreach T $P(threads) {
set name [lindex $T 0]
set count [lindex $T 1]
set prg [lindex $T 2]
for {set i 1} {$i <= $count} {incr i} {
set program [string map [list %TEST% $prg %SECONDS% $P(seconds) %I% $i] {
set tid %I%
proc usleep {ms} {
set ::usleep 0
after $ms {set ::usleep 1}
vwait ::usleep
}
proc busyhandler {n} { usleep 10 ; return 0 }
sqlite3 db test.db
db busy busyhandler
db eval { SELECT randomblob($tid*5) }
set ::finished 0
after [expr %SECONDS% * 1000] {set ::finished 1}
proc tt_continue {} { expr ($::finished==0) }
set rc [catch { %TEST% } msg]
db close
list $rc $msg
}]
if {$P(processes)==0} {
sqlthread spawn ::done($name,$i) $program
} else {
testfixture_nb ::done($name,$i) $program
}
}
}
set report " Results:"
foreach T $P(threads) {
set name [lindex $T 0]
set count [lindex $T 1]
set prg [lindex $T 2]
set reslist [list]
for {set i 1} {$i <= $count} {incr i} {
set res [wait_for_var ::done($name,$i)]
lappend reslist [lindex $res 1]
do_test $P(testname).$name.$i [list lindex $res 0] 0
}
append report " $name $reslist"
}
puts $report
}
#--------------------------------------------------------------------------
# Start NTHREAD threads. Each thread performs both read and write
# transactions. Each read transaction consists of:
#
# 1) Reading the md5sum of all but the last table row,
# 2) Running integrity check.
# 3) Reading the value stored in the last table row,
# 4) Check that the values read in steps 1 and 3 are the same, and that
# the md5sum of all but the last table row has not changed.
#
# Each write transaction consists of:
#
# 1) Modifying the contents of t1 (inserting, updating, deleting rows).
# 2) Appending a new row to the table containing the md5sum() of all
# rows in the table.
#
# Each of the N threads runs N read transactions followed by a single write
# transaction in a loop as fast as possible.
#
# Ther is also a single checkpointer thread. It runs the following loop:
#
# 1) Execute "PRAGMA checkpoint"
# 2) Sleep for 500 ms.
#
foreach {mode name} {
0 walthread-1-threads
1 walthread-1-processes
} {
do_thread_test $name -processes $mode -seconds $SECONDS -init {
execsql {
PRAGMA journal_mode = WAL;
CREATE TABLE t1(x PRIMARY KEY);
PRAGMA lock_status;
INSERT INTO t1 VALUES(randomblob(100));
INSERT INTO t1 VALUES(randomblob(100));
INSERT INTO t1 SELECT md5sum(x) FROM t1;
}
} -thread main $NTHREAD {
proc read_transaction {} {
set results [db eval {
BEGIN;
PRAGMA integrity_check;
SELECT md5sum(x) FROM t1 WHERE rowid != (SELECT max(rowid) FROM t1);
SELECT x FROM t1 WHERE rowid = (SELECT max(rowid) FROM t1);
SELECT md5sum(x) FROM t1 WHERE rowid != (SELECT max(rowid) FROM t1);
COMMIT;
}]
if {[llength $results]!=4
|| [lindex $results 0] != "ok"
|| [lindex $results 1] != [lindex $results 2]
|| [lindex $results 2] != [lindex $results 3]
} {
error "Failed read transaction: $results"
}
}
proc write_transaction {} {
db eval {
BEGIN;
INSERT INTO t1 VALUES(randomblob(100));
INSERT INTO t1 VALUES(randomblob(100));
INSERT INTO t1 SELECT md5sum(x) FROM t1;
COMMIT;
}
}
set nRun 0
while {[tt_continue]} {
read_transaction
write_transaction
usleep 1
incr nRun
}
set nRun
} -thread ckpt 1 {
set nRun 0
while {[tt_continue]} {
db eval "PRAGMA checkpoint"
usleep 500
incr nRun
}
set nRun
}
}
finish_test