Annotation of embedaddon/sqlite3/test/walthread.test, revision 1.1.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>