Adding upstream version 1.37.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
42613ad5c6
commit
271b368104
1329 changed files with 4727104 additions and 0 deletions
529
testdata/tcl/walthread.test
vendored
Normal file
529
testdata/tcl/walthread.test
vendored
Normal file
|
@ -0,0 +1,529 @@
|
|||
# 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 }
|
||||
ifcapable !wal { finish_test ; return }
|
||||
|
||||
set sqlite_walsummary_mmap_incr 64
|
||||
|
||||
# How long, in seconds, to run each test for. If a test is set to run for
|
||||
# 0 seconds, it is omitted entirely.
|
||||
#
|
||||
unset -nocomplain seconds
|
||||
set seconds(walthread-1) 20
|
||||
set seconds(walthread-2) 20
|
||||
set seconds(walthread-3) 20
|
||||
set seconds(walthread-4) 20
|
||||
set seconds(walthread-5) 1
|
||||
|
||||
# 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]
|
||||
}
|
||||
|
||||
# The argument is the name of a list variable in the callers context. The
|
||||
# first element of the list is removed and returned. For example:
|
||||
#
|
||||
# set L {a b c}
|
||||
# set x [lshift L]
|
||||
# assert { $x == "a" && $L == "b c" }
|
||||
#
|
||||
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.
|
||||
# -check SCRIPT Script to run after test.
|
||||
#
|
||||
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
|
||||
set P(check) {
|
||||
set ic [db eval "PRAGMA integrity_check"]
|
||||
if {$ic != "ok"} { error $ic }
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
-check {
|
||||
set P(check) [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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if {$P(seconds) == 0} {
|
||||
puts "Skipping $P(testname)"
|
||||
return
|
||||
}
|
||||
|
||||
puts "Running $P(testname) for $P(seconds) seconds..."
|
||||
|
||||
catch { db close }
|
||||
forcedelete test.db test.db-journal test.db-wal
|
||||
|
||||
sqlite3 db test.db
|
||||
eval $P(init)
|
||||
catch { 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 vars "
|
||||
set E(pid) $i
|
||||
set E(nthread) $count
|
||||
set E(seconds) $P(seconds)
|
||||
"
|
||||
set program [string map [list %TEST% $prg %VARS% $vars] {
|
||||
|
||||
%VARS%
|
||||
|
||||
proc usleep {ms} {
|
||||
set ::usleep 0
|
||||
after $ms {set ::usleep 1}
|
||||
vwait ::usleep
|
||||
}
|
||||
|
||||
proc integrity_check {{db db}} {
|
||||
set ic [$db eval {PRAGMA integrity_check}]
|
||||
if {$ic != "ok"} {error $ic}
|
||||
}
|
||||
|
||||
proc busyhandler {n} { usleep 10 ; return 0 }
|
||||
|
||||
sqlite3 db test.db
|
||||
db busy busyhandler
|
||||
db eval { SELECT randomblob($E(pid)*5) }
|
||||
|
||||
set ::finished 0
|
||||
after [expr $E(seconds) * 1000] {set ::finished 1}
|
||||
proc tt_continue {} { update ; expr ($::finished==0) }
|
||||
|
||||
set rc [catch { %TEST% } msg]
|
||||
|
||||
catch { 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
|
||||
|
||||
sqlite3 db test.db
|
||||
set res ""
|
||||
if {[catch $P(check) msg]} { set res $msg }
|
||||
do_test $P(testname).check [list set {} $res] ""
|
||||
}
|
||||
|
||||
# A wrapper around [do_thread_test] which runs the specified test twice.
|
||||
# Once using processes, once using threads. This command takes the same
|
||||
# arguments as [do_thread_test], except specifying the -processes switch
|
||||
# is illegal.
|
||||
#
|
||||
proc do_thread_test2 {args} {
|
||||
set name [lindex $args 0]
|
||||
if {[lsearch $args -processes]>=0} { error "bad option: -processes"}
|
||||
uplevel [lreplace $args 0 0 do_thread_test "$name-threads" -processes 0]
|
||||
uplevel [lreplace $args 0 0 do_thread_test "$name-processes" -processes 1]
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Start 10 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.
|
||||
#
|
||||
# There is also a single checkpointer thread. It runs the following loop:
|
||||
#
|
||||
# 1) Execute "PRAGMA wal_checkpoint"
|
||||
# 2) Sleep for 500 ms.
|
||||
#
|
||||
do_thread_test2 walthread-1 -seconds $seconds(walthread-1) -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 10 {
|
||||
|
||||
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(101 + $::E(pid)));
|
||||
INSERT INTO t1 VALUES(randomblob(101 + $::E(pid)));
|
||||
INSERT INTO t1 SELECT md5sum(x) FROM t1;
|
||||
COMMIT;
|
||||
}
|
||||
}
|
||||
|
||||
# Turn off auto-checkpoint. Otherwise, an auto-checkpoint run by a
|
||||
# writer may cause the dedicated checkpoint thread to return an
|
||||
# SQLITE_BUSY error.
|
||||
#
|
||||
db eval { PRAGMA wal_autocheckpoint = 0 }
|
||||
|
||||
set nRun 0
|
||||
while {[tt_continue]} {
|
||||
read_transaction
|
||||
write_transaction
|
||||
incr nRun
|
||||
}
|
||||
set nRun
|
||||
|
||||
} -thread ckpt 1 {
|
||||
set nRun 0
|
||||
while {[tt_continue]} {
|
||||
db eval "PRAGMA wal_checkpoint"
|
||||
usleep 500
|
||||
incr nRun
|
||||
}
|
||||
set nRun
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# This test has clients run the following procedure as fast as possible
|
||||
# in a loop:
|
||||
#
|
||||
# 1. Open a database handle.
|
||||
# 2. Execute a read-only transaction on the db.
|
||||
# 3. Do "PRAGMA journal_mode = XXX", where XXX is one of WAL or DELETE.
|
||||
# Ignore any SQLITE_BUSY error.
|
||||
# 4. Execute a write transaction to insert a row into the db.
|
||||
# 5. Run "PRAGMA integrity_check"
|
||||
#
|
||||
# At present, there are 4 clients in total. 2 do "journal_mode = WAL", and
|
||||
# two do "journal_mode = DELETE".
|
||||
#
|
||||
# Each client returns a string of the form "W w, R r", where W is the
|
||||
# number of write-transactions performed using a WAL journal, and D is
|
||||
# the number of write-transactions performed using a rollback journal.
|
||||
# For example, "192 w, 185 r".
|
||||
#
|
||||
if {[atomic_batch_write test.db]==0} {
|
||||
do_thread_test2 walthread-2 -seconds $seconds(walthread-2) -init {
|
||||
execsql { CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE) }
|
||||
} -thread RB 2 {
|
||||
|
||||
db close
|
||||
set nRun 0
|
||||
set nDel 0
|
||||
while {[tt_continue]} {
|
||||
sqlite3 db test.db
|
||||
db busy busyhandler
|
||||
db eval { SELECT * FROM sqlite_master }
|
||||
catch { db eval { PRAGMA journal_mode = DELETE } }
|
||||
db eval {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(NULL, randomblob(100+$E(pid)));
|
||||
}
|
||||
incr nRun 1
|
||||
incr nDel [file exists test.db-journal]
|
||||
if {[file exists test.db-journal] + [file exists test.db-wal] != 1} {
|
||||
error "File-system looks bad..."
|
||||
}
|
||||
db eval COMMIT
|
||||
|
||||
integrity_check
|
||||
db close
|
||||
}
|
||||
list $nRun $nDel
|
||||
set {} "[expr $nRun-$nDel] w, $nDel r"
|
||||
|
||||
} -thread WAL 2 {
|
||||
db close
|
||||
set nRun 0
|
||||
set nDel 0
|
||||
while {[tt_continue]} {
|
||||
sqlite3 db test.db
|
||||
db busy busyhandler
|
||||
db eval { SELECT * FROM sqlite_master }
|
||||
catch { db eval { PRAGMA journal_mode = WAL } }
|
||||
db eval {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(NULL, randomblob(110+$E(pid)));
|
||||
}
|
||||
incr nRun 1
|
||||
incr nDel [file exists test.db-journal]
|
||||
if {[file exists test.db-journal] + [file exists test.db-wal] != 1} {
|
||||
error "File-system looks bad..."
|
||||
}
|
||||
db eval COMMIT
|
||||
|
||||
integrity_check
|
||||
db close
|
||||
}
|
||||
set {} "[expr $nRun-$nDel] w, $nDel r"
|
||||
}
|
||||
}
|
||||
|
||||
do_thread_test walthread-3 -seconds $seconds(walthread-3) -init {
|
||||
execsql {
|
||||
PRAGMA journal_mode = WAL;
|
||||
CREATE TABLE t1(cnt PRIMARY KEY, sum1, sum2);
|
||||
CREATE INDEX i1 ON t1(sum1);
|
||||
CREATE INDEX i2 ON t1(sum2);
|
||||
INSERT INTO t1 VALUES(0, 0, 0);
|
||||
}
|
||||
} -thread t 10 {
|
||||
|
||||
set nextwrite $E(pid)
|
||||
|
||||
proc wal_hook {zDb nEntry} {
|
||||
if {$nEntry>10} {
|
||||
set rc [catch { db eval {PRAGMA wal_checkpoint} } msg]
|
||||
if {$rc && $msg != "database is locked"} { error $msg }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
db wal_hook wal_hook
|
||||
|
||||
while {[tt_continue]} {
|
||||
set max 0
|
||||
while { $max != ($nextwrite-1) && [tt_continue] } {
|
||||
set max [db eval { SELECT max(cnt) FROM t1 }]
|
||||
}
|
||||
|
||||
if {[tt_continue]} {
|
||||
set sum1 [db eval { SELECT sum(cnt) FROM t1 }]
|
||||
set sum2 [db eval { SELECT sum(sum1) FROM t1 }]
|
||||
db eval { INSERT INTO t1 VALUES($nextwrite, $sum1, $sum2) }
|
||||
incr nextwrite $E(nthread)
|
||||
integrity_check
|
||||
}
|
||||
}
|
||||
|
||||
set {} ok
|
||||
} -check {
|
||||
puts " Final db contains [db eval {SELECT count(*) FROM t1}] rows"
|
||||
puts " Final integrity-check says: [db eval {PRAGMA integrity_check}]"
|
||||
|
||||
# Check that the contents of the database are Ok.
|
||||
set c 0
|
||||
set s1 0
|
||||
set s2 0
|
||||
db eval { SELECT cnt, sum1, sum2 FROM t1 ORDER BY cnt } {
|
||||
if {$c != $cnt || $s1 != $sum1 || $s2 != $sum2} {
|
||||
error "database content is invalid"
|
||||
}
|
||||
incr s2 $s1
|
||||
incr s1 $c
|
||||
incr c 1
|
||||
}
|
||||
}
|
||||
|
||||
do_thread_test2 walthread-4 -seconds $seconds(walthread-4) -init {
|
||||
execsql {
|
||||
PRAGMA journal_mode = WAL;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
|
||||
}
|
||||
} -thread r 1 {
|
||||
# This connection only ever reads the database. Therefore the
|
||||
# busy-handler is not required. Disable it to check that this is true.
|
||||
#
|
||||
# UPDATE: That is no longer entirely true - as we don't use a blocking
|
||||
# lock to enter RECOVER state. Which means there is a small chance a
|
||||
# reader can see an SQLITE_BUSY.
|
||||
#
|
||||
while {[tt_continue]} {
|
||||
integrity_check
|
||||
}
|
||||
set {} ok
|
||||
} -thread w 1 {
|
||||
|
||||
proc wal_hook {zDb nEntry} {
|
||||
if {$nEntry>15} {db eval {PRAGMA wal_checkpoint}}
|
||||
return 0
|
||||
}
|
||||
db wal_hook wal_hook
|
||||
set row 1
|
||||
while {[tt_continue]} {
|
||||
db eval { REPLACE INTO t1 VALUES($row, randomblob(300)) }
|
||||
incr row
|
||||
if {$row == 10} { set row 1 }
|
||||
}
|
||||
|
||||
set {} ok
|
||||
}
|
||||
|
||||
|
||||
# This test case attempts to provoke a deadlock condition that existed in
|
||||
# the unix VFS at one point. The problem occurred only while recovering a
|
||||
# very large wal file (one that requires a wal-index larger than the
|
||||
# initial default allocation of 64KB).
|
||||
#
|
||||
do_thread_test walthread-5 -seconds $seconds(walthread-5) -init {
|
||||
|
||||
proc log_file_size {nFrame pgsz} {
|
||||
expr {12 + ($pgsz+16)*$nFrame}
|
||||
}
|
||||
|
||||
execsql {
|
||||
PRAGMA page_size = 1024;
|
||||
PRAGMA journal_mode = WAL;
|
||||
CREATE TABLE t1(x);
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(randomblob(900));
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 2 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 4 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 8 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 16 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 32 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 64 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 128 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 256 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 512 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 1024 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 2048 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 4096 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 8192 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 16384 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 32768 */
|
||||
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 65536 */
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
forcecopy test.db-wal bak.db-wal
|
||||
forcecopy test.db bak.db
|
||||
db close
|
||||
|
||||
forcecopy bak.db-wal test.db-wal
|
||||
forcecopy bak.db test.db
|
||||
|
||||
if {[file size test.db-wal] < [log_file_size [expr 64*1024] 1024]} {
|
||||
error "Somehow failed to create a large log file"
|
||||
}
|
||||
puts "Database with large log file recovered. Now running clients..."
|
||||
} -thread T 5 {
|
||||
db eval { SELECT count(*) FROM t1 }
|
||||
}
|
||||
unset -nocomplain seconds
|
||||
|
||||
finish_test
|
Loading…
Add table
Add a link
Reference in a new issue