dan | d3f8f94 | 2010-04-13 11:35:01 +0000 | [diff] [blame] | 1 | # 2010 April 13 |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 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 | #*********************************************************************** |
dan | d3f8f94 | 2010-04-13 11:35:01 +0000 | [diff] [blame] | 11 | # This file implements regression tests for SQLite library. The |
| 12 | # focus of this file is testing the operation of the library in |
dan | cd11fb2 | 2010-04-26 10:40:52 +0000 | [diff] [blame] | 13 | # "PRAGMA journal_mode=WAL" mode with multiple threads. |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 14 | # |
| 15 | |
| 16 | set testdir [file dirname $argv0] |
| 17 | |
| 18 | source $testdir/tester.tcl |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 19 | source $testdir/lock_common.tcl |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 20 | if {[run_thread_tests]==0} { finish_test ; return } |
dan | 5cf5353 | 2010-05-01 16:40:20 +0000 | [diff] [blame] | 21 | ifcapable !wal { finish_test ; return } |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 22 | |
dan | cd11fb2 | 2010-04-26 10:40:52 +0000 | [diff] [blame] | 23 | set sqlite_walsummary_mmap_incr 64 |
| 24 | |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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. |
dan | cd11fb2 | 2010-04-26 10:40:52 +0000 | [diff] [blame] | 27 | # |
drh | 529b187 | 2010-05-03 17:24:45 +0000 | [diff] [blame] | 28 | unset -nocomplain seconds |
dan | 31c0390 | 2010-04-29 14:51:33 +0000 | [diff] [blame] | 29 | set seconds(walthread-1) 20 |
| 30 | set seconds(walthread-2) 20 |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 31 | set seconds(walthread-3) 20 |
dan | 31c0390 | 2010-04-29 14:51:33 +0000 | [diff] [blame] | 32 | set seconds(walthread-4) 20 |
dan | bb16ff7 | 2010-05-01 08:30:34 +0000 | [diff] [blame] | 33 | set seconds(walthread-5) 1 |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 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] |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 46 | } |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 47 | uplevel [list set $varname] |
| 48 | } |
| 49 | |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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 | # |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 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. |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 74 | # -check SCRIPT Script to run after test. |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 75 | # |
| 76 | proc do_thread_test {args} { |
dan | e45d442 | 2010-04-28 18:17:23 +0000 | [diff] [blame] | 77 | |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 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 |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 85 | set P(check) { |
| 86 | set ic [db eval "PRAGMA integrity_check"] |
| 87 | if {$ic != "ok"} { error $ic } |
| 88 | } |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 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 | |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 107 | -check { |
| 108 | set P(check) [lshift A] |
| 109 | } |
| 110 | |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 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 | } |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 122 | } |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 123 | |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 124 | if {$P(seconds) == 0} { |
| 125 | puts "Skipping $P(testname)" |
| 126 | return |
| 127 | } |
| 128 | |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 129 | puts "Running $P(testname) for $P(seconds) seconds..." |
| 130 | |
| 131 | catch { db close } |
mistachkin | fda06be | 2011-08-02 00:57:34 +0000 | [diff] [blame] | 132 | forcedelete test.db test.db-journal test.db-wal |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 133 | |
| 134 | sqlite3 db test.db |
| 135 | eval $P(init) |
dan | bb16ff7 | 2010-05-01 08:30:34 +0000 | [diff] [blame] | 136 | catch { db close } |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 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} { |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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] { |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 150 | |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 151 | %VARS% |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 152 | |
| 153 | proc usleep {ms} { |
| 154 | set ::usleep 0 |
| 155 | after $ms {set ::usleep 1} |
| 156 | vwait ::usleep |
| 157 | } |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 158 | |
| 159 | proc integrity_check {{db db}} { |
| 160 | set ic [$db eval {PRAGMA integrity_check}] |
| 161 | if {$ic != "ok"} {error $ic} |
| 162 | } |
| 163 | |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 164 | proc busyhandler {n} { usleep 10 ; return 0 } |
| 165 | |
| 166 | sqlite3 db test.db |
| 167 | db busy busyhandler |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 168 | db eval { SELECT randomblob($E(pid)*5) } |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 169 | |
| 170 | set ::finished 0 |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 171 | after [expr $E(seconds) * 1000] {set ::finished 1} |
dan | e45d442 | 2010-04-28 18:17:23 +0000 | [diff] [blame] | 172 | proc tt_continue {} { update ; expr ($::finished==0) } |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 173 | |
| 174 | set rc [catch { %TEST% } msg] |
| 175 | |
dan | e45d442 | 2010-04-28 18:17:23 +0000 | [diff] [blame] | 176 | catch { db close } |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 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 |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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] "" |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 209 | } |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 210 | |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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 | |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 223 | #-------------------------------------------------------------------------- |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 224 | # Start 10 threads. Each thread performs both read and write |
dan | 5e0ce87 | 2010-04-28 17:48:44 +0000 | [diff] [blame] | 225 | # transactions. Each read transaction consists of: |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 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 | # |
dan | e45d442 | 2010-04-28 18:17:23 +0000 | [diff] [blame] | 242 | # There is also a single checkpointer thread. It runs the following loop: |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 243 | # |
dan | 5a299f9 | 2010-05-03 11:05:08 +0000 | [diff] [blame] | 244 | # 1) Execute "PRAGMA wal_checkpoint" |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 245 | # 2) Sleep for 500 ms. |
| 246 | # |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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; |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 255 | } |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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; |
dan | 809badc | 2012-08-07 13:55:41 +0000 | [diff] [blame] | 280 | INSERT INTO t1 VALUES(randomblob(101 + $::E(pid))); |
| 281 | INSERT INTO t1 VALUES(randomblob(101 + $::E(pid))); |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 282 | INSERT INTO t1 SELECT md5sum(x) FROM t1; |
| 283 | COMMIT; |
| 284 | } |
| 285 | } |
| 286 | |
dan | f9b7671 | 2010-06-01 14:12:45 +0000 | [diff] [blame] | 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 | |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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]} { |
dan | 5a299f9 | 2010-05-03 11:05:08 +0000 | [diff] [blame] | 304 | db eval "PRAGMA wal_checkpoint" |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 305 | usleep 500 |
| 306 | incr nRun |
| 307 | } |
| 308 | set nRun |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 309 | } |
| 310 | |
dan | e45d442 | 2010-04-28 18:17:23 +0000 | [diff] [blame] | 311 | #-------------------------------------------------------------------------- |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 312 | # This test has clients run the following procedure as fast as possible |
| 313 | # in a loop: |
dan | e45d442 | 2010-04-28 18:17:23 +0000 | [diff] [blame] | 314 | # |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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 { |
dan | e45d442 | 2010-04-28 18:17:23 +0000 | [diff] [blame] | 333 | |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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))); |
dan | e45d442 | 2010-04-28 18:17:23 +0000 | [diff] [blame] | 345 | } |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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..." |
dan | e45d442 | 2010-04-28 18:17:23 +0000 | [diff] [blame] | 350 | } |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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 | |
dan | 31c0390 | 2010-04-29 14:51:33 +0000 | [diff] [blame] | 385 | do_thread_test walthread-3 -seconds $seconds(walthread-3) -init { |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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} { |
dan | f9b7671 | 2010-06-01 14:12:45 +0000 | [diff] [blame] | 398 | if {$nEntry>10} { |
| 399 | set rc [catch { db eval {PRAGMA wal_checkpoint} } msg] |
| 400 | if {$rc && $msg != "database is locked"} { error $msg } |
| 401 | } |
dan | b4e3a6f | 2010-04-29 08:47:28 +0000 | [diff] [blame] | 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 |
dan | e45d442 | 2010-04-28 18:17:23 +0000 | [diff] [blame] | 437 | } |
| 438 | } |
| 439 | |
dan | 31c0390 | 2010-04-29 14:51:33 +0000 | [diff] [blame] | 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. |
dan | fa408ad | 2010-05-25 15:23:51 +0000 | [diff] [blame] | 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 | } |
dan | 31c0390 | 2010-04-29 14:51:33 +0000 | [diff] [blame] | 456 | set {} ok |
| 457 | } -thread w 1 { |
| 458 | |
| 459 | proc wal_hook {zDb nEntry} { |
drh | 5def084 | 2010-05-05 20:00:25 +0000 | [diff] [blame] | 460 | if {$nEntry>15} {db eval {PRAGMA wal_checkpoint}} |
dan | 31c0390 | 2010-04-29 14:51:33 +0000 | [diff] [blame] | 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 | |
dan | bb16ff7 | 2010-05-01 08:30:34 +0000 | [diff] [blame] | 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 | # |
dan | bb16ff7 | 2010-05-01 08:30:34 +0000 | [diff] [blame] | 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 | |
mistachkin | fda06be | 2011-08-02 00:57:34 +0000 | [diff] [blame] | 511 | forcecopy test.db-wal bak.db-wal |
| 512 | forcecopy test.db bak.db |
dan | bb16ff7 | 2010-05-01 08:30:34 +0000 | [diff] [blame] | 513 | db close |
| 514 | |
mistachkin | fda06be | 2011-08-02 00:57:34 +0000 | [diff] [blame] | 515 | forcecopy bak.db-wal test.db-wal |
| 516 | forcecopy bak.db test.db |
dan | bb16ff7 | 2010-05-01 08:30:34 +0000 | [diff] [blame] | 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 | } |
drh | d0b2677 | 2010-05-03 19:20:46 +0000 | [diff] [blame] | 525 | unset -nocomplain seconds |
dan | bb16ff7 | 2010-05-01 08:30:34 +0000 | [diff] [blame] | 526 | |
dan | 7c24610 | 2010-04-12 19:00:29 +0000 | [diff] [blame] | 527 | finish_test |