Annotation of embedaddon/sqlite3/test/walthread.test, revision 1.1
1.1 ! misho 1: # 2010 April 13
! 2: #
! 3: # The author disclaims copyright to this source code. In place of
! 4: # a legal notice, here is a blessing:
! 5: #
! 6: # May you do good and not evil.
! 7: # May you find forgiveness for yourself and forgive others.
! 8: # May you share freely, never taking more than you give.
! 9: #
! 10: #***********************************************************************
! 11: # This file implements regression tests for SQLite library. The
! 12: # focus of this file is testing the operation of the library in
! 13: # "PRAGMA journal_mode=WAL" mode with multiple threads.
! 14: #
! 15:
! 16: set testdir [file dirname $argv0]
! 17:
! 18: source $testdir/tester.tcl
! 19: source $testdir/lock_common.tcl
! 20: if {[run_thread_tests]==0} { finish_test ; return }
! 21: ifcapable !wal { finish_test ; return }
! 22:
! 23: set sqlite_walsummary_mmap_incr 64
! 24:
! 25: # How long, in seconds, to run each test for. If a test is set to run for
! 26: # 0 seconds, it is omitted entirely.
! 27: #
! 28: unset -nocomplain seconds
! 29: set seconds(walthread-1) 20
! 30: set seconds(walthread-2) 20
! 31: set seconds(walthread-3) 20
! 32: set seconds(walthread-4) 20
! 33: set seconds(walthread-5) 1
! 34:
! 35: # The parameter is the name of a variable in the callers context. The
! 36: # variable may or may not exist when this command is invoked.
! 37: #
! 38: # If the variable does exist, its value is returned. Otherwise, this
! 39: # command uses [vwait] to wait until it is set, then returns the value.
! 40: # In other words, this is a version of the [set VARNAME] command that
! 41: # blocks until a variable exists.
! 42: #
! 43: proc wait_for_var {varname} {
! 44: if {0==[uplevel [list info exists $varname]]} {
! 45: uplevel [list vwait $varname]
! 46: }
! 47: uplevel [list set $varname]
! 48: }
! 49:
! 50: # The argument is the name of a list variable in the callers context. The
! 51: # first element of the list is removed and returned. For example:
! 52: #
! 53: # set L {a b c}
! 54: # set x [lshift L]
! 55: # assert { $x == "a" && $L == "b c" }
! 56: #
! 57: proc lshift {lvar} {
! 58: upvar $lvar L
! 59: set ret [lindex $L 0]
! 60: set L [lrange $L 1 end]
! 61: return $ret
! 62: }
! 63:
! 64:
! 65: #-------------------------------------------------------------------------
! 66: # do_thread_test TESTNAME OPTIONS...
! 67: #
! 68: # where OPTIONS are:
! 69: #
! 70: # -seconds SECONDS How many seconds to run the test for
! 71: # -init SCRIPT Script to run before test.
! 72: # -thread NAME COUNT SCRIPT Scripts to run in threads (or processes).
! 73: # -processes BOOLEAN True to use processes instead of threads.
! 74: # -check SCRIPT Script to run after test.
! 75: #
! 76: proc do_thread_test {args} {
! 77:
! 78: set A $args
! 79:
! 80: set P(testname) [lshift A]
! 81: set P(seconds) 5
! 82: set P(init) ""
! 83: set P(threads) [list]
! 84: set P(processes) 0
! 85: set P(check) {
! 86: set ic [db eval "PRAGMA integrity_check"]
! 87: if {$ic != "ok"} { error $ic }
! 88: }
! 89:
! 90: unset -nocomplain ::done
! 91:
! 92: while {[llength $A]>0} {
! 93: set a [lshift A]
! 94: switch -glob -- $a {
! 95: -seconds {
! 96: set P(seconds) [lshift A]
! 97: }
! 98:
! 99: -init {
! 100: set P(init) [lshift A]
! 101: }
! 102:
! 103: -processes {
! 104: set P(processes) [lshift A]
! 105: }
! 106:
! 107: -check {
! 108: set P(check) [lshift A]
! 109: }
! 110:
! 111: -thread {
! 112: set name [lshift A]
! 113: set count [lshift A]
! 114: set prg [lshift A]
! 115: lappend P(threads) [list $name $count $prg]
! 116: }
! 117:
! 118: default {
! 119: error "Unknown option: $a"
! 120: }
! 121: }
! 122: }
! 123:
! 124: if {$P(seconds) == 0} {
! 125: puts "Skipping $P(testname)"
! 126: return
! 127: }
! 128:
! 129: puts "Running $P(testname) for $P(seconds) seconds..."
! 130:
! 131: catch { db close }
! 132: forcedelete test.db test.db-journal test.db-wal
! 133:
! 134: sqlite3 db test.db
! 135: eval $P(init)
! 136: catch { db close }
! 137:
! 138: foreach T $P(threads) {
! 139: set name [lindex $T 0]
! 140: set count [lindex $T 1]
! 141: set prg [lindex $T 2]
! 142:
! 143: for {set i 1} {$i <= $count} {incr i} {
! 144: set vars "
! 145: set E(pid) $i
! 146: set E(nthread) $count
! 147: set E(seconds) $P(seconds)
! 148: "
! 149: set program [string map [list %TEST% $prg %VARS% $vars] {
! 150:
! 151: %VARS%
! 152:
! 153: proc usleep {ms} {
! 154: set ::usleep 0
! 155: after $ms {set ::usleep 1}
! 156: vwait ::usleep
! 157: }
! 158:
! 159: proc integrity_check {{db db}} {
! 160: set ic [$db eval {PRAGMA integrity_check}]
! 161: if {$ic != "ok"} {error $ic}
! 162: }
! 163:
! 164: proc busyhandler {n} { usleep 10 ; return 0 }
! 165:
! 166: sqlite3 db test.db
! 167: db busy busyhandler
! 168: db eval { SELECT randomblob($E(pid)*5) }
! 169:
! 170: set ::finished 0
! 171: after [expr $E(seconds) * 1000] {set ::finished 1}
! 172: proc tt_continue {} { update ; expr ($::finished==0) }
! 173:
! 174: set rc [catch { %TEST% } msg]
! 175:
! 176: catch { db close }
! 177: list $rc $msg
! 178: }]
! 179:
! 180: if {$P(processes)==0} {
! 181: sqlthread spawn ::done($name,$i) $program
! 182: } else {
! 183: testfixture_nb ::done($name,$i) $program
! 184: }
! 185: }
! 186: }
! 187:
! 188: set report " Results:"
! 189: foreach T $P(threads) {
! 190: set name [lindex $T 0]
! 191: set count [lindex $T 1]
! 192: set prg [lindex $T 2]
! 193:
! 194: set reslist [list]
! 195: for {set i 1} {$i <= $count} {incr i} {
! 196: set res [wait_for_var ::done($name,$i)]
! 197: lappend reslist [lindex $res 1]
! 198: do_test $P(testname).$name.$i [list lindex $res 0] 0
! 199: }
! 200:
! 201: append report " $name $reslist"
! 202: }
! 203: puts $report
! 204:
! 205: sqlite3 db test.db
! 206: set res ""
! 207: if {[catch $P(check) msg]} { set res $msg }
! 208: do_test $P(testname).check [list set {} $res] ""
! 209: }
! 210:
! 211: # A wrapper around [do_thread_test] which runs the specified test twice.
! 212: # Once using processes, once using threads. This command takes the same
! 213: # arguments as [do_thread_test], except specifying the -processes switch
! 214: # is illegal.
! 215: #
! 216: proc do_thread_test2 {args} {
! 217: set name [lindex $args 0]
! 218: if {[lsearch $args -processes]>=0} { error "bad option: -processes"}
! 219: uplevel [lreplace $args 0 0 do_thread_test "$name-threads" -processes 0]
! 220: uplevel [lreplace $args 0 0 do_thread_test "$name-processes" -processes 1]
! 221: }
! 222:
! 223: #--------------------------------------------------------------------------
! 224: # Start 10 threads. Each thread performs both read and write
! 225: # transactions. Each read transaction consists of:
! 226: #
! 227: # 1) Reading the md5sum of all but the last table row,
! 228: # 2) Running integrity check.
! 229: # 3) Reading the value stored in the last table row,
! 230: # 4) Check that the values read in steps 1 and 3 are the same, and that
! 231: # the md5sum of all but the last table row has not changed.
! 232: #
! 233: # Each write transaction consists of:
! 234: #
! 235: # 1) Modifying the contents of t1 (inserting, updating, deleting rows).
! 236: # 2) Appending a new row to the table containing the md5sum() of all
! 237: # rows in the table.
! 238: #
! 239: # Each of the N threads runs N read transactions followed by a single write
! 240: # transaction in a loop as fast as possible.
! 241: #
! 242: # There is also a single checkpointer thread. It runs the following loop:
! 243: #
! 244: # 1) Execute "PRAGMA wal_checkpoint"
! 245: # 2) Sleep for 500 ms.
! 246: #
! 247: do_thread_test2 walthread-1 -seconds $seconds(walthread-1) -init {
! 248: execsql {
! 249: PRAGMA journal_mode = WAL;
! 250: CREATE TABLE t1(x PRIMARY KEY);
! 251: PRAGMA lock_status;
! 252: INSERT INTO t1 VALUES(randomblob(100));
! 253: INSERT INTO t1 VALUES(randomblob(100));
! 254: INSERT INTO t1 SELECT md5sum(x) FROM t1;
! 255: }
! 256: } -thread main 10 {
! 257:
! 258: proc read_transaction {} {
! 259: set results [db eval {
! 260: BEGIN;
! 261: PRAGMA integrity_check;
! 262: SELECT md5sum(x) FROM t1 WHERE rowid != (SELECT max(rowid) FROM t1);
! 263: SELECT x FROM t1 WHERE rowid = (SELECT max(rowid) FROM t1);
! 264: SELECT md5sum(x) FROM t1 WHERE rowid != (SELECT max(rowid) FROM t1);
! 265: COMMIT;
! 266: }]
! 267:
! 268: if {[llength $results]!=4
! 269: || [lindex $results 0] != "ok"
! 270: || [lindex $results 1] != [lindex $results 2]
! 271: || [lindex $results 2] != [lindex $results 3]
! 272: } {
! 273: error "Failed read transaction: $results"
! 274: }
! 275: }
! 276:
! 277: proc write_transaction {} {
! 278: db eval {
! 279: BEGIN;
! 280: INSERT INTO t1 VALUES(randomblob(100));
! 281: INSERT INTO t1 VALUES(randomblob(100));
! 282: INSERT INTO t1 SELECT md5sum(x) FROM t1;
! 283: COMMIT;
! 284: }
! 285: }
! 286:
! 287: # Turn off auto-checkpoint. Otherwise, an auto-checkpoint run by a
! 288: # writer may cause the dedicated checkpoint thread to return an
! 289: # SQLITE_BUSY error.
! 290: #
! 291: db eval { PRAGMA wal_autocheckpoint = 0 }
! 292:
! 293: set nRun 0
! 294: while {[tt_continue]} {
! 295: read_transaction
! 296: write_transaction
! 297: incr nRun
! 298: }
! 299: set nRun
! 300:
! 301: } -thread ckpt 1 {
! 302: set nRun 0
! 303: while {[tt_continue]} {
! 304: db eval "PRAGMA wal_checkpoint"
! 305: usleep 500
! 306: incr nRun
! 307: }
! 308: set nRun
! 309: }
! 310:
! 311: #--------------------------------------------------------------------------
! 312: # This test has clients run the following procedure as fast as possible
! 313: # in a loop:
! 314: #
! 315: # 1. Open a database handle.
! 316: # 2. Execute a read-only transaction on the db.
! 317: # 3. Do "PRAGMA journal_mode = XXX", where XXX is one of WAL or DELETE.
! 318: # Ignore any SQLITE_BUSY error.
! 319: # 4. Execute a write transaction to insert a row into the db.
! 320: # 5. Run "PRAGMA integrity_check"
! 321: #
! 322: # At present, there are 4 clients in total. 2 do "journal_mode = WAL", and
! 323: # two do "journal_mode = DELETE".
! 324: #
! 325: # Each client returns a string of the form "W w, R r", where W is the
! 326: # number of write-transactions performed using a WAL journal, and D is
! 327: # the number of write-transactions performed using a rollback journal.
! 328: # For example, "192 w, 185 r".
! 329: #
! 330: do_thread_test2 walthread-2 -seconds $seconds(walthread-2) -init {
! 331: execsql { CREATE TABLE t1(x INTEGER PRIMARY KEY, y UNIQUE) }
! 332: } -thread RB 2 {
! 333:
! 334: db close
! 335: set nRun 0
! 336: set nDel 0
! 337: while {[tt_continue]} {
! 338: sqlite3 db test.db
! 339: db busy busyhandler
! 340: db eval { SELECT * FROM sqlite_master }
! 341: catch { db eval { PRAGMA journal_mode = DELETE } }
! 342: db eval {
! 343: BEGIN;
! 344: INSERT INTO t1 VALUES(NULL, randomblob(100+$E(pid)));
! 345: }
! 346: incr nRun 1
! 347: incr nDel [file exists test.db-journal]
! 348: if {[file exists test.db-journal] + [file exists test.db-wal] != 1} {
! 349: error "File-system looks bad..."
! 350: }
! 351: db eval COMMIT
! 352:
! 353: integrity_check
! 354: db close
! 355: }
! 356: list $nRun $nDel
! 357: set {} "[expr $nRun-$nDel] w, $nDel r"
! 358:
! 359: } -thread WAL 2 {
! 360: db close
! 361: set nRun 0
! 362: set nDel 0
! 363: while {[tt_continue]} {
! 364: sqlite3 db test.db
! 365: db busy busyhandler
! 366: db eval { SELECT * FROM sqlite_master }
! 367: catch { db eval { PRAGMA journal_mode = WAL } }
! 368: db eval {
! 369: BEGIN;
! 370: INSERT INTO t1 VALUES(NULL, randomblob(110+$E(pid)));
! 371: }
! 372: incr nRun 1
! 373: incr nDel [file exists test.db-journal]
! 374: if {[file exists test.db-journal] + [file exists test.db-wal] != 1} {
! 375: error "File-system looks bad..."
! 376: }
! 377: db eval COMMIT
! 378:
! 379: integrity_check
! 380: db close
! 381: }
! 382: set {} "[expr $nRun-$nDel] w, $nDel r"
! 383: }
! 384:
! 385: do_thread_test walthread-3 -seconds $seconds(walthread-3) -init {
! 386: execsql {
! 387: PRAGMA journal_mode = WAL;
! 388: CREATE TABLE t1(cnt PRIMARY KEY, sum1, sum2);
! 389: CREATE INDEX i1 ON t1(sum1);
! 390: CREATE INDEX i2 ON t1(sum2);
! 391: INSERT INTO t1 VALUES(0, 0, 0);
! 392: }
! 393: } -thread t 10 {
! 394:
! 395: set nextwrite $E(pid)
! 396:
! 397: proc wal_hook {zDb nEntry} {
! 398: if {$nEntry>10} {
! 399: set rc [catch { db eval {PRAGMA wal_checkpoint} } msg]
! 400: if {$rc && $msg != "database is locked"} { error $msg }
! 401: }
! 402: return 0
! 403: }
! 404: db wal_hook wal_hook
! 405:
! 406: while {[tt_continue]} {
! 407: set max 0
! 408: while { $max != ($nextwrite-1) && [tt_continue] } {
! 409: set max [db eval { SELECT max(cnt) FROM t1 }]
! 410: }
! 411:
! 412: if {[tt_continue]} {
! 413: set sum1 [db eval { SELECT sum(cnt) FROM t1 }]
! 414: set sum2 [db eval { SELECT sum(sum1) FROM t1 }]
! 415: db eval { INSERT INTO t1 VALUES($nextwrite, $sum1, $sum2) }
! 416: incr nextwrite $E(nthread)
! 417: integrity_check
! 418: }
! 419: }
! 420:
! 421: set {} ok
! 422: } -check {
! 423: puts " Final db contains [db eval {SELECT count(*) FROM t1}] rows"
! 424: puts " Final integrity-check says: [db eval {PRAGMA integrity_check}]"
! 425:
! 426: # Check that the contents of the database are Ok.
! 427: set c 0
! 428: set s1 0
! 429: set s2 0
! 430: db eval { SELECT cnt, sum1, sum2 FROM t1 ORDER BY cnt } {
! 431: if {$c != $cnt || $s1 != $sum1 || $s2 != $sum2} {
! 432: error "database content is invalid"
! 433: }
! 434: incr s2 $s1
! 435: incr s1 $c
! 436: incr c 1
! 437: }
! 438: }
! 439:
! 440: do_thread_test2 walthread-4 -seconds $seconds(walthread-4) -init {
! 441: execsql {
! 442: PRAGMA journal_mode = WAL;
! 443: CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
! 444: }
! 445: } -thread r 1 {
! 446: # This connection only ever reads the database. Therefore the
! 447: # busy-handler is not required. Disable it to check that this is true.
! 448: #
! 449: # UPDATE: That is no longer entirely true - as we don't use a blocking
! 450: # lock to enter RECOVER state. Which means there is a small chance a
! 451: # reader can see an SQLITE_BUSY.
! 452: #
! 453: while {[tt_continue]} {
! 454: integrity_check
! 455: }
! 456: set {} ok
! 457: } -thread w 1 {
! 458:
! 459: proc wal_hook {zDb nEntry} {
! 460: if {$nEntry>15} {db eval {PRAGMA wal_checkpoint}}
! 461: return 0
! 462: }
! 463: db wal_hook wal_hook
! 464: set row 1
! 465: while {[tt_continue]} {
! 466: db eval { REPLACE INTO t1 VALUES($row, randomblob(300)) }
! 467: incr row
! 468: if {$row == 10} { set row 1 }
! 469: }
! 470:
! 471: set {} ok
! 472: }
! 473:
! 474:
! 475: # This test case attempts to provoke a deadlock condition that existed in
! 476: # the unix VFS at one point. The problem occurred only while recovering a
! 477: # very large wal file (one that requires a wal-index larger than the
! 478: # initial default allocation of 64KB).
! 479: #
! 480: do_thread_test walthread-5 -seconds $seconds(walthread-5) -init {
! 481:
! 482: proc log_file_size {nFrame pgsz} {
! 483: expr {12 + ($pgsz+16)*$nFrame}
! 484: }
! 485:
! 486: execsql {
! 487: PRAGMA page_size = 1024;
! 488: PRAGMA journal_mode = WAL;
! 489: CREATE TABLE t1(x);
! 490: BEGIN;
! 491: INSERT INTO t1 VALUES(randomblob(900));
! 492: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 2 */
! 493: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 4 */
! 494: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 8 */
! 495: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 16 */
! 496: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 32 */
! 497: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 64 */
! 498: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 128 */
! 499: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 256 */
! 500: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 512 */
! 501: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 1024 */
! 502: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 2048 */
! 503: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 4096 */
! 504: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 8192 */
! 505: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 16384 */
! 506: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 32768 */
! 507: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 65536 */
! 508: COMMIT;
! 509: }
! 510:
! 511: forcecopy test.db-wal bak.db-wal
! 512: forcecopy test.db bak.db
! 513: db close
! 514:
! 515: forcecopy bak.db-wal test.db-wal
! 516: forcecopy bak.db test.db
! 517:
! 518: if {[file size test.db-wal] < [log_file_size [expr 64*1024] 1024]} {
! 519: error "Somehow failed to create a large log file"
! 520: }
! 521: puts "Database with large log file recovered. Now running clients..."
! 522: } -thread T 5 {
! 523: db eval { SELECT count(*) FROM t1 }
! 524: }
! 525: unset -nocomplain seconds
! 526:
! 527: finish_test
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>