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