xref: /sqlite-3.40.0/test/thread005.test (revision fda06bef)
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