120736d82Sdanielk1977# 2009 March 11 220736d82Sdanielk1977# 320736d82Sdanielk1977# The author disclaims copyright to this source code. In place of 420736d82Sdanielk1977# a legal notice, here is a blessing: 520736d82Sdanielk1977# 620736d82Sdanielk1977# May you do good and not evil. 720736d82Sdanielk1977# May you find forgiveness for yourself and forgive others. 820736d82Sdanielk1977# May you share freely, never taking more than you give. 920736d82Sdanielk1977# 1020736d82Sdanielk1977#*********************************************************************** 1120736d82Sdanielk1977# 1220736d82Sdanielk1977# Test a race-condition that shows up in shared-cache mode. 1320736d82Sdanielk1977# 146d961009Sdanielk1977# $Id: thread005.test,v 1.5 2009/03/26 14:48:07 danielk1977 Exp $ 1520736d82Sdanielk1977 1620736d82Sdanielk1977set testdir [file dirname $argv0] 1720736d82Sdanielk1977 1820736d82Sdanielk1977source $testdir/tester.tcl 196d961009Sdanielk1977if {[run_thread_tests]==0} { finish_test ; return } 206d961009Sdanielk1977ifcapable !shared_cache { 216d961009Sdanielk1977 finish_test 2220736d82Sdanielk1977 return 2320736d82Sdanielk1977} 246d961009Sdanielk1977 252b31b211Sdanielk1977db close 262b31b211Sdanielk1977 272b31b211Sdanielk1977# Use shared-cache mode for these tests. 282b31b211Sdanielk1977# 292b31b211Sdanielk1977set ::enable_shared_cache [sqlite3_enable_shared_cache] 302b31b211Sdanielk1977sqlite3_enable_shared_cache 1 312b31b211Sdanielk1977 322b31b211Sdanielk1977#------------------------------------------------------------------------- 332b31b211Sdanielk1977# This test attempts to hit the race condition fixed by commit [6363]. 342b31b211Sdanielk1977# 352b31b211Sdanielk1977proc runsql {zSql {db {}}} { 362b31b211Sdanielk1977 set rc SQLITE_OK 372b31b211Sdanielk1977 while {$rc=="SQLITE_OK" && $zSql ne ""} { 382b31b211Sdanielk1977 set STMT [sqlite3_prepare_v2 $db $zSql -1 zSql] 392b31b211Sdanielk1977 while {[set rc [sqlite3_step $STMT]] eq "SQLITE_ROW"} { } 402b31b211Sdanielk1977 set rc [sqlite3_finalize $STMT] 412b31b211Sdanielk1977 } 422b31b211Sdanielk1977 return $rc 432b31b211Sdanielk1977} 442b31b211Sdanielk1977do_test thread005-1.1 { 452b31b211Sdanielk1977 sqlite3 db test.db 462b31b211Sdanielk1977 db eval { CREATE TABLE t1(a, b) } 472b31b211Sdanielk1977 db close 482b31b211Sdanielk1977} {} 492b31b211Sdanielk1977for {set ii 2} {$ii < 500} {incr ii} { 502b31b211Sdanielk1977 unset -nocomplain finished 512b31b211Sdanielk1977 thread_spawn finished(0) {sqlite3_open test.db} 522b31b211Sdanielk1977 thread_spawn finished(1) {sqlite3_open test.db} 532b31b211Sdanielk1977 if {![info exists finished(0)]} { vwait finished(0) } 542b31b211Sdanielk1977 if {![info exists finished(1)]} { vwait finished(1) } 552b31b211Sdanielk1977 562b31b211Sdanielk1977 do_test thread005-1.$ii { 572b31b211Sdanielk1977 runsql { BEGIN } $finished(0) 582b31b211Sdanielk1977 runsql { INSERT INTO t1 VALUES(1, 2) } $finished(0) 592b31b211Sdanielk1977 602b31b211Sdanielk1977 # If the race-condition was hit, then $finished(0 and $finished(1) 612b31b211Sdanielk1977 # will not use the same pager cache. In this case the next statement 622b31b211Sdanielk1977 # can be executed succesfully. However, if the race-condition is not 632b31b211Sdanielk1977 # hit, then $finished(1) will be blocked by the write-lock held by 642b31b211Sdanielk1977 # $finished(0) on the shared-cache table t1 and the statement will 652b31b211Sdanielk1977 # return SQLITE_LOCKED. 662b31b211Sdanielk1977 # 672b31b211Sdanielk1977 runsql { SELECT * FROM t1 } $finished(1) 682b31b211Sdanielk1977 } {SQLITE_LOCKED} 692b31b211Sdanielk1977 702b31b211Sdanielk1977 sqlite3_close $finished(0) 712b31b211Sdanielk1977 sqlite3_close $finished(1) 722b31b211Sdanielk1977} 732b31b211Sdanielk1977 7420736d82Sdanielk1977 7520736d82Sdanielk1977#------------------------------------------------------------------------- 7620736d82Sdanielk1977# This test tries to exercise a race-condition that existed in shared-cache 7720736d82Sdanielk1977# mode at one point. The test uses two threads; each has a database connection 7820736d82Sdanielk1977# open on the same shared cache. The schema of the database is: 7920736d82Sdanielk1977# 8020736d82Sdanielk1977# CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE); 8120736d82Sdanielk1977# 8220736d82Sdanielk1977# One thread is a reader and the other thread a reader and a writer. The 8320736d82Sdanielk1977# writer thread repeats the following transaction as fast as possible: 8420736d82Sdanielk1977# 8520736d82Sdanielk1977# BEGIN; 8620736d82Sdanielk1977# DELETE FROM t1 WHERE a = (SELECT max(a) FROM t1); 8720736d82Sdanielk1977# INSERT INTO t1 VALUES(NULL, NULL); 8820736d82Sdanielk1977# UPDATE t1 SET b = a WHERE a = (SELECT max(a) FROM t1); 8920736d82Sdanielk1977# SELECT count(*) FROM t1 WHERE b IS NULL; 9020736d82Sdanielk1977# COMMIT; 9120736d82Sdanielk1977# 9220736d82Sdanielk1977# The reader thread does the following over and over as fast as possible: 9320736d82Sdanielk1977# 9420736d82Sdanielk1977# BEGIN; 9520736d82Sdanielk1977# SELECT count(*) FROM t1 WHERE b IS NULL; 9620736d82Sdanielk1977# COMMIT; 9720736d82Sdanielk1977# 9820736d82Sdanielk1977# The test runs for 20 seconds or until one of the "SELECT count(*)" 9920736d82Sdanielk1977# statements returns a non-zero value. If an SQLITE_LOCKED error occurs, 10020736d82Sdanielk1977# the connection issues a ROLLBACK immediately to abandon the current 10120736d82Sdanielk1977# transaction. 10220736d82Sdanielk1977# 10320736d82Sdanielk1977# If everything is working correctly, the "SELECT count(*)" statements 10420736d82Sdanielk1977# should never return a value other than 0. The "INSERT" statement 10520736d82Sdanielk1977# executed by the writer adds a row with "b IS NULL" to the table, but 10620736d82Sdanielk1977# the subsequent UPDATE statement sets its "b" value to an integer 10720736d82Sdanielk1977# immediately afterwards. 10820736d82Sdanielk1977# 10920736d82Sdanielk1977# However, before the race-condition was fixed, if the reader's SELECT 11020736d82Sdanielk1977# statement hit an error (say an SQLITE_LOCKED) at the same time as the 11120736d82Sdanielk1977# writer was executing the UPDATE statement, then it could incorrectly 11220736d82Sdanielk1977# rollback the statement-transaction belonging to the UPDATE statement. 11320736d82Sdanielk1977# The UPDATE statement would still be reported as successful to the user, 11420736d82Sdanielk1977# but it would have no effect on the database contents. 11520736d82Sdanielk1977# 11620736d82Sdanielk1977# Note that it has so far only proved possible to hit this race-condition 11720736d82Sdanielk1977# when using an ATTACHed database. There doesn't seem to be any reason 11820736d82Sdanielk1977# for this, other than that operating on an ATTACHed database means there 11920736d82Sdanielk1977# are a few more mutex grabs and releases during the window of time open 12020736d82Sdanielk1977# for the race-condition. Maybe this encourages the scheduler to context 12120736d82Sdanielk1977# switch or something... 12220736d82Sdanielk1977# 12320736d82Sdanielk1977 124*fda06befSmistachkinforcedelete test.db test2.db 1251f4969a1Sdrhunset -nocomplain finished 12620736d82Sdanielk1977 1272b31b211Sdanielk1977do_test thread005-2.1 { 12820736d82Sdanielk1977 sqlite3 db test.db 12920736d82Sdanielk1977 execsql { ATTACH 'test2.db' AS aux } 13020736d82Sdanielk1977 execsql { 13120736d82Sdanielk1977 CREATE TABLE aux.t1(a INTEGER PRIMARY KEY, b UNIQUE); 13220736d82Sdanielk1977 INSERT INTO t1 VALUES(1, 1); 13320736d82Sdanielk1977 INSERT INTO t1 VALUES(2, 2); 13420736d82Sdanielk1977 } 13520736d82Sdanielk1977 db close 13620736d82Sdanielk1977} {} 13720736d82Sdanielk1977 138df0f3c06Sdanielk1977 13920736d82Sdanielk1977set ThreadProgram { 14020736d82Sdanielk1977 proc execsql {zSql {db {}}} { 14120736d82Sdanielk1977 if {$db eq ""} {set db $::DB} 14220736d82Sdanielk1977 14320736d82Sdanielk1977 set lRes [list] 14420736d82Sdanielk1977 set rc SQLITE_OK 14520736d82Sdanielk1977 14620736d82Sdanielk1977 while {$rc=="SQLITE_OK" && $zSql ne ""} { 14720736d82Sdanielk1977 set STMT [sqlite3_prepare_v2 $db $zSql -1 zSql] 14820736d82Sdanielk1977 while {[set rc [sqlite3_step $STMT]] eq "SQLITE_ROW"} { 14920736d82Sdanielk1977 for {set i 0} {$i < [sqlite3_column_count $STMT]} {incr i} { 15020736d82Sdanielk1977 lappend lRes [sqlite3_column_text $STMT 0] 15120736d82Sdanielk1977 } 15220736d82Sdanielk1977 } 15320736d82Sdanielk1977 set rc [sqlite3_finalize $STMT] 15420736d82Sdanielk1977 } 15520736d82Sdanielk1977 15620736d82Sdanielk1977 if {$rc != "SQLITE_OK"} { error "$rc [sqlite3_errmsg $db]" } 15720736d82Sdanielk1977 return $lRes 15820736d82Sdanielk1977 } 15920736d82Sdanielk1977 16020736d82Sdanielk1977 if {$isWriter} { 16120736d82Sdanielk1977 set Sql { 16220736d82Sdanielk1977 BEGIN; 16320736d82Sdanielk1977 DELETE FROM t1 WHERE a = (SELECT max(a) FROM t1); 16420736d82Sdanielk1977 INSERT INTO t1 VALUES(NULL, NULL); 16520736d82Sdanielk1977 UPDATE t1 SET b = a WHERE a = (SELECT max(a) FROM t1); 16620736d82Sdanielk1977 SELECT count(*) FROM t1 WHERE b IS NULL; 16720736d82Sdanielk1977 COMMIT; 16820736d82Sdanielk1977 } 16920736d82Sdanielk1977 } else { 17020736d82Sdanielk1977 set Sql { 17120736d82Sdanielk1977 BEGIN; 17220736d82Sdanielk1977 SELECT count(*) FROM t1 WHERE b IS NULL; 17320736d82Sdanielk1977 COMMIT; 17420736d82Sdanielk1977 } 17520736d82Sdanielk1977 } 17620736d82Sdanielk1977 17720736d82Sdanielk1977 set ::DB [sqlite3_open test.db] 17820736d82Sdanielk1977 17920736d82Sdanielk1977 execsql { ATTACH 'test2.db' AS aux } 18020736d82Sdanielk1977 18120736d82Sdanielk1977 set result "ok" 18220736d82Sdanielk1977 set finish [expr [clock_seconds]+5] 18320736d82Sdanielk1977 while {$result eq "ok" && [clock_seconds] < $finish} { 18420736d82Sdanielk1977 set rc [catch {execsql $Sql} msg] 18520736d82Sdanielk1977 if {$rc} { 18620736d82Sdanielk1977 if {[string match "SQLITE_LOCKED*" $msg]} { 18720736d82Sdanielk1977 catch { execsql ROLLBACK } 18820736d82Sdanielk1977 } else { 189df0f3c06Sdanielk1977 sqlite3_close $::DB 19020736d82Sdanielk1977 error $msg 19120736d82Sdanielk1977 } 19220736d82Sdanielk1977 } elseif {$msg ne "0"} { 19320736d82Sdanielk1977 set result "failed" 19420736d82Sdanielk1977 } 19520736d82Sdanielk1977 } 19620736d82Sdanielk1977 19720736d82Sdanielk1977 sqlite3_close $::DB 19820736d82Sdanielk1977 set result 19920736d82Sdanielk1977} 20020736d82Sdanielk1977 201df0f3c06Sdanielk1977# There is a race-condition in btree.c that means that if two threads 202df0f3c06Sdanielk1977# attempt to open the same database at roughly the same time, and there 203df0f3c06Sdanielk1977# does not already exist a shared-cache corresponding to that database, 204df0f3c06Sdanielk1977# then two shared-caches can be created instead of one. Things still more 205df0f3c06Sdanielk1977# or less work, but the two database connections do not use the same 206df0f3c06Sdanielk1977# shared-cache. 207df0f3c06Sdanielk1977# 208df0f3c06Sdanielk1977# If the threads run by this test hit this race-condition, the tests 209df0f3c06Sdanielk1977# fail (because SQLITE_BUSY may be unexpectedly returned instead of 210df0f3c06Sdanielk1977# SQLITE_LOCKED). To prevent this from happening, open a couple of 211df0f3c06Sdanielk1977# connections to test.db and test2.db now to make sure that there are 212df0f3c06Sdanielk1977# already shared-caches in memory for all databases opened by the 213df0f3c06Sdanielk1977# test threads. 214df0f3c06Sdanielk1977# 215df0f3c06Sdanielk1977sqlite3 db test.db 216df0f3c06Sdanielk1977sqlite3 db test2.db 217df0f3c06Sdanielk1977 21820736d82Sdanielk1977puts "Running thread-tests for ~20 seconds" 21920736d82Sdanielk1977thread_spawn finished(0) {set isWriter 0} $ThreadProgram 22020736d82Sdanielk1977thread_spawn finished(1) {set isWriter 1} $ThreadProgram 22120736d82Sdanielk1977if {![info exists finished(0)]} { vwait finished(0) } 22220736d82Sdanielk1977if {![info exists finished(1)]} { vwait finished(1) } 22320736d82Sdanielk1977 224df0f3c06Sdanielk1977catch { db close } 225df0f3c06Sdanielk1977catch { db2 close } 226df0f3c06Sdanielk1977 2272b31b211Sdanielk1977do_test thread005-2.2 { 22820736d82Sdanielk1977 list $finished(0) $finished(1) 22920736d82Sdanielk1977} {ok ok} 23020736d82Sdanielk1977 2312b31b211Sdanielk1977do_test thread005-2.3 { 23220736d82Sdanielk1977 sqlite3 db test.db 23320736d82Sdanielk1977 execsql { ATTACH 'test2.db' AS aux } 23420736d82Sdanielk1977 execsql { SELECT count(*) FROM t1 WHERE b IS NULL } 23520736d82Sdanielk1977} {0} 23620736d82Sdanielk1977 23720736d82Sdanielk1977sqlite3_enable_shared_cache $::enable_shared_cache 23820736d82Sdanielk1977finish_test 239