1# 2009 March 11 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# 12# Test a race-condition that shows up in shared-cache mode. 13# 14# $Id: thread005.test,v 1.3 2009/03/20 10:24:04 danielk1977 Exp $ 15 16set testdir [file dirname $argv0] 17 18source $testdir/tester.tcl 19ifcapable !mutex||!shared_cache { 20 return 21} 22source $testdir/thread_common.tcl 23if {[info commands sqlthread] eq ""} { 24 return 25} 26 27#------------------------------------------------------------------------- 28# This test tries to exercise a race-condition that existed in shared-cache 29# mode at one point. The test uses two threads; each has a database connection 30# open on the same shared cache. The schema of the database is: 31# 32# CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE); 33# 34# One thread is a reader and the other thread a reader and a writer. The 35# writer thread repeats the following transaction as fast as possible: 36# 37# BEGIN; 38# DELETE FROM t1 WHERE a = (SELECT max(a) FROM t1); 39# INSERT INTO t1 VALUES(NULL, NULL); 40# UPDATE t1 SET b = a WHERE a = (SELECT max(a) FROM t1); 41# SELECT count(*) FROM t1 WHERE b IS NULL; 42# COMMIT; 43# 44# The reader thread does the following over and over as fast as possible: 45# 46# BEGIN; 47# SELECT count(*) FROM t1 WHERE b IS NULL; 48# COMMIT; 49# 50# The test runs for 20 seconds or until one of the "SELECT count(*)" 51# statements returns a non-zero value. If an SQLITE_LOCKED error occurs, 52# the connection issues a ROLLBACK immediately to abandon the current 53# transaction. 54# 55# If everything is working correctly, the "SELECT count(*)" statements 56# should never return a value other than 0. The "INSERT" statement 57# executed by the writer adds a row with "b IS NULL" to the table, but 58# the subsequent UPDATE statement sets its "b" value to an integer 59# immediately afterwards. 60# 61# However, before the race-condition was fixed, if the reader's SELECT 62# statement hit an error (say an SQLITE_LOCKED) at the same time as the 63# writer was executing the UPDATE statement, then it could incorrectly 64# rollback the statement-transaction belonging to the UPDATE statement. 65# The UPDATE statement would still be reported as successful to the user, 66# but it would have no effect on the database contents. 67# 68# Note that it has so far only proved possible to hit this race-condition 69# when using an ATTACHed database. There doesn't seem to be any reason 70# for this, other than that operating on an ATTACHed database means there 71# are a few more mutex grabs and releases during the window of time open 72# for the race-condition. Maybe this encourages the scheduler to context 73# switch or something... 74# 75 76# Use shared-cache mode for this test. 77# 78db close 79set ::enable_shared_cache [sqlite3_enable_shared_cache] 80sqlite3_enable_shared_cache 1 81 82file delete -force test.db test2.db 83unset -nocomplain finished 84 85do_test thread005-1.1 { 86 sqlite3 db test.db 87 execsql { ATTACH 'test2.db' AS aux } 88 execsql { 89 CREATE TABLE aux.t1(a INTEGER PRIMARY KEY, b UNIQUE); 90 INSERT INTO t1 VALUES(1, 1); 91 INSERT INTO t1 VALUES(2, 2); 92 } 93 db close 94} {} 95 96 97set ThreadProgram { 98 proc execsql {zSql {db {}}} { 99 if {$db eq ""} {set db $::DB} 100 101 set lRes [list] 102 set rc SQLITE_OK 103 104 while {$rc=="SQLITE_OK" && $zSql ne ""} { 105 set STMT [sqlite3_prepare_v2 $db $zSql -1 zSql] 106 while {[set rc [sqlite3_step $STMT]] eq "SQLITE_ROW"} { 107 for {set i 0} {$i < [sqlite3_column_count $STMT]} {incr i} { 108 lappend lRes [sqlite3_column_text $STMT 0] 109 } 110 } 111 set rc [sqlite3_finalize $STMT] 112 } 113 114 if {$rc != "SQLITE_OK"} { error "$rc [sqlite3_errmsg $db]" } 115 return $lRes 116 } 117 118 if {$isWriter} { 119 set Sql { 120 BEGIN; 121 DELETE FROM t1 WHERE a = (SELECT max(a) FROM t1); 122 INSERT INTO t1 VALUES(NULL, NULL); 123 UPDATE t1 SET b = a WHERE a = (SELECT max(a) FROM t1); 124 SELECT count(*) FROM t1 WHERE b IS NULL; 125 COMMIT; 126 } 127 } else { 128 set Sql { 129 BEGIN; 130 SELECT count(*) FROM t1 WHERE b IS NULL; 131 COMMIT; 132 } 133 } 134 135 set ::DB [sqlite3_open test.db] 136 137 execsql { ATTACH 'test2.db' AS aux } 138 139 set result "ok" 140 set finish [expr [clock_seconds]+5] 141 while {$result eq "ok" && [clock_seconds] < $finish} { 142 set rc [catch {execsql $Sql} msg] 143 if {$rc} { 144 if {[string match "SQLITE_LOCKED*" $msg]} { 145 catch { execsql ROLLBACK } 146 } else { 147 sqlite3_close $::DB 148 error $msg 149 } 150 } elseif {$msg ne "0"} { 151 set result "failed" 152 } 153 } 154 155 sqlite3_close $::DB 156 set result 157} 158 159# There is a race-condition in btree.c that means that if two threads 160# attempt to open the same database at roughly the same time, and there 161# does not already exist a shared-cache corresponding to that database, 162# then two shared-caches can be created instead of one. Things still more 163# or less work, but the two database connections do not use the same 164# shared-cache. 165# 166# If the threads run by this test hit this race-condition, the tests 167# fail (because SQLITE_BUSY may be unexpectedly returned instead of 168# SQLITE_LOCKED). To prevent this from happening, open a couple of 169# connections to test.db and test2.db now to make sure that there are 170# already shared-caches in memory for all databases opened by the 171# test threads. 172# 173sqlite3 db test.db 174sqlite3 db test2.db 175 176puts "Running thread-tests for ~20 seconds" 177thread_spawn finished(0) {set isWriter 0} $ThreadProgram 178thread_spawn finished(1) {set isWriter 1} $ThreadProgram 179if {![info exists finished(0)]} { vwait finished(0) } 180if {![info exists finished(1)]} { vwait finished(1) } 181 182catch { db close } 183catch { db2 close } 184 185do_test thread005-1.2 { 186 list $finished(0) $finished(1) 187} {ok ok} 188 189do_test thread005-1.3 { 190 sqlite3 db test.db 191 execsql { ATTACH 'test2.db' AS aux } 192 execsql { SELECT count(*) FROM t1 WHERE b IS NULL } 193} {0} 194 195sqlite3_enable_shared_cache $::enable_shared_cache 196finish_test 197