xref: /sqlite-3.40.0/test/notify2.test (revision 8ab4b9e9)
1404ca075Sdanielk1977# 2009 March 04
2404ca075Sdanielk1977#
3404ca075Sdanielk1977# The author disclaims copyright to this source code.  In place of
4404ca075Sdanielk1977# a legal notice, here is a blessing:
5404ca075Sdanielk1977#
6404ca075Sdanielk1977#    May you do good and not evil.
7404ca075Sdanielk1977#    May you find forgiveness for yourself and forgive others.
8404ca075Sdanielk1977#    May you share freely, never taking more than you give.
9404ca075Sdanielk1977#
10404ca075Sdanielk1977#***********************************************************************
11404ca075Sdanielk1977#
12b71c1751Sdrh# $Id: notify2.test,v 1.7 2009/03/30 11:59:31 drh Exp $
13404ca075Sdanielk1977
14404ca075Sdanielk1977set testdir [file dirname $argv0]
15404ca075Sdanielk1977source $testdir/tester.tcl
166d961009Sdanielk1977if {[run_thread_tests]==0} { finish_test ; return }
178594373aSdanielk1977ifcapable !unlock_notify||!shared_cache { finish_test ; return }
18404ca075Sdanielk1977
19404ca075Sdanielk1977# The tests in this file test the sqlite3_blocking_step() function in
20404ca075Sdanielk1977# test_thread.c. sqlite3_blocking_step() is not an SQLite API function,
21404ca075Sdanielk1977# it is just a demonstration of how the sqlite3_unlock_notify() function
22404ca075Sdanielk1977# can be used to synchronize multi-threaded access to SQLite databases
23404ca075Sdanielk1977# in shared-cache mode.
24404ca075Sdanielk1977#
25404ca075Sdanielk1977# Since the implementation of sqlite3_blocking_step() is included on the
26404ca075Sdanielk1977# website as example code, it is important to test that it works.
27404ca075Sdanielk1977#
28404ca075Sdanielk1977# notify2-1.*:
29404ca075Sdanielk1977#
30404ca075Sdanielk1977#   This test uses $nThread threads. Each thread opens the main database
31404ca075Sdanielk1977#   and attaches two other databases. Each database contains a single table.
32404ca075Sdanielk1977#
33404ca075Sdanielk1977#   Each thread repeats transactions over and over for 20 seconds. Each
34404ca075Sdanielk1977#   transaction consists of 3 operations. Each operation is either a read
35404ca075Sdanielk1977#   or a write of one of the tables. The read operations verify an invariant
36404ca075Sdanielk1977#   to make sure that things are working as expected. If an SQLITE_LOCKED
37404ca075Sdanielk1977#   error is returned the current transaction is rolled back immediately.
38404ca075Sdanielk1977#
39404ca075Sdanielk1977#   This exercise is repeated twice, once using sqlite3_step(), and the
40404ca075Sdanielk1977#   other using sqlite3_blocking_step(). The results are compared to ensure
41404ca075Sdanielk1977#   that sqlite3_blocking_step() resulted in higher transaction throughput.
42404ca075Sdanielk1977#
43404ca075Sdanielk1977
44404ca075Sdanielk1977db close
45404ca075Sdanielk1977set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
46404ca075Sdanielk1977
47404ca075Sdanielk1977# Number of threads to run simultaneously.
48404ca075Sdanielk1977#
4972bcfa6eSdrhset nThread 6
50404ca075Sdanielk1977set nSecond 5
51404ca075Sdanielk1977
52404ca075Sdanielk1977# The Tcl script executed by each of the $nThread threads used by this test.
53404ca075Sdanielk1977#
54404ca075Sdanielk1977set ThreadProgram {
55404ca075Sdanielk1977
56404ca075Sdanielk1977  # Proc used by threads to execute SQL.
57404ca075Sdanielk1977  #
58404ca075Sdanielk1977  proc execsql_blocking {db zSql} {
59404ca075Sdanielk1977    set lRes [list]
60404ca075Sdanielk1977    set rc SQLITE_OK
61404ca075Sdanielk1977
62a8bbef84Sdanielk1977set sql $zSql
63a8bbef84Sdanielk1977
64404ca075Sdanielk1977    while {$rc=="SQLITE_OK" && $zSql ne ""} {
6565a2ea11Sdanielk1977      set STMT [$::xPrepare $db $zSql -1 zSql]
66404ca075Sdanielk1977      while {[set rc [$::xStep $STMT]] eq "SQLITE_ROW"} {
67404ca075Sdanielk1977        for {set i 0} {$i < [sqlite3_column_count $STMT]} {incr i} {
68404ca075Sdanielk1977          lappend lRes [sqlite3_column_text $STMT 0]
69404ca075Sdanielk1977        }
70404ca075Sdanielk1977      }
71404ca075Sdanielk1977      set rc [sqlite3_finalize $STMT]
72404ca075Sdanielk1977    }
73404ca075Sdanielk1977
74a8bbef84Sdanielk1977    if {$rc != "SQLITE_OK"} { error "$rc $sql [sqlite3_errmsg $db]" }
75404ca075Sdanielk1977    return $lRes
76404ca075Sdanielk1977  }
77404ca075Sdanielk1977
78a8bbef84Sdanielk1977  proc execsql_retry {db sql} {
79a8bbef84Sdanielk1977    set msg "SQLITE_LOCKED blah..."
80a8bbef84Sdanielk1977    while { [string match SQLITE_LOCKED* $msg] } {
81a8bbef84Sdanielk1977      catch { execsql_blocking $db $sql } msg
82a8bbef84Sdanielk1977    }
83a8bbef84Sdanielk1977  }
84a8bbef84Sdanielk1977
85404ca075Sdanielk1977  proc select_one {args} {
86404ca075Sdanielk1977    set n [llength $args]
87404ca075Sdanielk1977    lindex $args [expr int($n*rand())]
88404ca075Sdanielk1977  }
89404ca075Sdanielk1977
90a8bbef84Sdanielk1977  proc opendb {} {
91404ca075Sdanielk1977    # Open a database connection. Attach the two auxillary databases.
92404ca075Sdanielk1977    set ::DB [sqlite3_open test.db]
93a8bbef84Sdanielk1977    execsql_retry $::DB { ATTACH 'test2.db' AS aux2; }
94a8bbef84Sdanielk1977    execsql_retry $::DB { ATTACH 'test3.db' AS aux3; }
95404ca075Sdanielk1977  }
96404ca075Sdanielk1977
97a8bbef84Sdanielk1977  opendb
98a8bbef84Sdanielk1977
99a8bbef84Sdanielk1977  #after 2000
100a8bbef84Sdanielk1977
101404ca075Sdanielk1977  # This loop runs for ~20 seconds.
102404ca075Sdanielk1977  #
103404ca075Sdanielk1977  set iStart [clock_seconds]
104*8ab4b9e9Sdan  set nOp 0
105*8ab4b9e9Sdan  set nAttempt 0
106404ca075Sdanielk1977  while { ([clock_seconds]-$iStart) < $nSecond } {
107404ca075Sdanielk1977
108404ca075Sdanielk1977    # Each transaction does 3 operations. Each operation is either a read
109404ca075Sdanielk1977    # or write of a randomly selected table (t1, t2 or t3). Set the variables
110404ca075Sdanielk1977    # $SQL(1), $SQL(2) and $SQL(3) to the SQL commands used to implement
111404ca075Sdanielk1977    # each operation.
112404ca075Sdanielk1977    #
113404ca075Sdanielk1977    for {set ii 1} {$ii <= 3} {incr ii} {
11465a2ea11Sdanielk1977      foreach {tbl database} [select_one {t1 main} {t2 aux2} {t3 aux3}] {}
11565a2ea11Sdanielk1977
11665a2ea11Sdanielk1977      set SQL($ii) [string map [list xxx $tbl yyy $database] [select_one {
117404ca075Sdanielk1977            SELECT
118404ca075Sdanielk1977              (SELECT b FROM xxx WHERE a=(SELECT max(a) FROM xxx))==total(a)
119404ca075Sdanielk1977              FROM xxx WHERE a!=(SELECT max(a) FROM xxx);
120404ca075Sdanielk1977      } {
121404ca075Sdanielk1977            DELETE FROM xxx WHERE a<(SELECT max(a)-100 FROM xxx);
122404ca075Sdanielk1977            INSERT INTO xxx SELECT NULL, total(a) FROM xxx;
123a8bbef84Sdanielk1977      } {
124a8bbef84Sdanielk1977            CREATE INDEX IF NOT EXISTS yyy.xxx_i ON xxx(b);
125a8bbef84Sdanielk1977      } {
126a8bbef84Sdanielk1977            DROP INDEX IF EXISTS yyy.xxx_i;
12765a2ea11Sdanielk1977      }
12865a2ea11Sdanielk1977      ]]
129404ca075Sdanielk1977    }
130404ca075Sdanielk1977
131404ca075Sdanielk1977    # Execute the SQL transaction.
132404ca075Sdanielk1977    #
133*8ab4b9e9Sdan    incr nAttempt
134404ca075Sdanielk1977    set rc [catch { execsql_blocking $::DB "
135404ca075Sdanielk1977        BEGIN;
136404ca075Sdanielk1977          $SQL(1);
137404ca075Sdanielk1977          $SQL(2);
138404ca075Sdanielk1977          $SQL(3);
139404ca075Sdanielk1977        COMMIT;
140404ca075Sdanielk1977      "
141404ca075Sdanielk1977    } msg]
142404ca075Sdanielk1977
143a8bbef84Sdanielk1977    if {$rc && [string match "SQLITE_LOCKED*" $msg]
144a8bbef84Sdanielk1977            || [string match "SQLITE_SCHEMA*" $msg]
145a8bbef84Sdanielk1977    } {
146404ca075Sdanielk1977      # Hit an SQLITE_LOCKED error. Rollback the current transaction.
147a8bbef84Sdanielk1977      set rc [catch { execsql_blocking $::DB ROLLBACK } msg]
148a8bbef84Sdanielk1977      if {$rc && [string match "SQLITE_LOCKED*" $msg]} {
149a8bbef84Sdanielk1977        sqlite3_close $::DB
150a8bbef84Sdanielk1977        opendb
151a8bbef84Sdanielk1977      }
152404ca075Sdanielk1977    } elseif {$rc} {
153404ca075Sdanielk1977      # Hit some other kind of error. This is a malfunction.
154404ca075Sdanielk1977      error $msg
155404ca075Sdanielk1977    } else {
15648864df9Smistachkin      # No error occurred. Check that any SELECT statements in the transaction
157404ca075Sdanielk1977      # returned "1". Otherwise, the invariant was false, indicating that
15848864df9Smistachkin      # some malfunction has occurred.
159404ca075Sdanielk1977      foreach r $msg { if {$r != 1} { puts "Invariant check failed: $msg" } }
160*8ab4b9e9Sdan      incr nOp
161404ca075Sdanielk1977    }
162404ca075Sdanielk1977  }
163404ca075Sdanielk1977
164404ca075Sdanielk1977  # Close the database connection and return 0.
165404ca075Sdanielk1977  #
166404ca075Sdanielk1977  sqlite3_close $::DB
167*8ab4b9e9Sdan  list $nOp $nAttempt
168404ca075Sdanielk1977}
169404ca075Sdanielk1977
17065a2ea11Sdanielk1977foreach {iTest xStep xPrepare} {
17165a2ea11Sdanielk1977  1 sqlite3_blocking_step sqlite3_blocking_prepare_v2
172a8bbef84Sdanielk1977  2 sqlite3_step          sqlite3_nonblocking_prepare_v2
17365a2ea11Sdanielk1977} {
174fda06befSmistachkin  forcedelete test.db test2.db test3.db
175404ca075Sdanielk1977
17665a2ea11Sdanielk1977  set ThreadSetup "set xStep $xStep;set xPrepare $xPrepare;set nSecond $nSecond"
177404ca075Sdanielk1977
178404ca075Sdanielk1977  # Set up the database schema used by this test. Each thread opens file
179404ca075Sdanielk1977  # test.db as the main database, then attaches files test2.db and test3.db
180404ca075Sdanielk1977  # as auxillary databases. Each file contains a single table (t1, t2 and t3, in
181404ca075Sdanielk1977  # files test.db, test2.db and test3.db, respectively).
182404ca075Sdanielk1977  #
183404ca075Sdanielk1977  do_test notify2-$iTest.1.1 {
184404ca075Sdanielk1977    sqlite3 db test.db
185404ca075Sdanielk1977    execsql {
186404ca075Sdanielk1977      ATTACH 'test2.db' AS aux2;
187404ca075Sdanielk1977      ATTACH 'test3.db' AS aux3;
188404ca075Sdanielk1977      CREATE TABLE main.t1(a INTEGER PRIMARY KEY, b);
189404ca075Sdanielk1977      CREATE TABLE aux2.t2(a INTEGER PRIMARY KEY, b);
190404ca075Sdanielk1977      CREATE TABLE aux3.t3(a INTEGER PRIMARY KEY, b);
191404ca075Sdanielk1977      INSERT INTO t1 SELECT NULL, 0;
192404ca075Sdanielk1977      INSERT INTO t2 SELECT NULL, 0;
193404ca075Sdanielk1977      INSERT INTO t3 SELECT NULL, 0;
194404ca075Sdanielk1977    }
195404ca075Sdanielk1977  } {}
196404ca075Sdanielk1977  do_test notify2-$iTest.1.2 {
197404ca075Sdanielk1977    db close
198404ca075Sdanielk1977  } {}
199404ca075Sdanielk1977
200404ca075Sdanielk1977
201404ca075Sdanielk1977  # Launch $nThread threads. Then wait for them to finish.
202404ca075Sdanielk1977  #
203404ca075Sdanielk1977  puts "Running $xStep test for $nSecond seconds"
204404ca075Sdanielk1977  unset -nocomplain finished
205404ca075Sdanielk1977  for {set ii 0} {$ii < $nThread} {incr ii} {
206404ca075Sdanielk1977    thread_spawn finished($ii) $ThreadSetup $ThreadProgram
207404ca075Sdanielk1977  }
208404ca075Sdanielk1977  for {set ii 0} {$ii < $nThread} {incr ii} {
209404ca075Sdanielk1977    do_test notify2-$iTest.2.$ii {
210404ca075Sdanielk1977      if {![info exists finished($ii)]} { vwait finished($ii) }
211*8ab4b9e9Sdan      incr anSuccess($xStep) [lindex $finished($ii) 0]
212*8ab4b9e9Sdan      incr anAttempt($xStep) [lindex $finished($ii) 1]
213*8ab4b9e9Sdan      expr 0
214404ca075Sdanielk1977    } {0}
215404ca075Sdanielk1977  }
216404ca075Sdanielk1977
217404ca075Sdanielk1977  # Count the total number of succesful writes.
218404ca075Sdanielk1977  do_test notify2-$iTest.3.1 {
219404ca075Sdanielk1977    sqlite3 db test.db
220404ca075Sdanielk1977    execsql {
221404ca075Sdanielk1977      ATTACH 'test2.db' AS aux2;
222404ca075Sdanielk1977      ATTACH 'test3.db' AS aux3;
223404ca075Sdanielk1977    }
224404ca075Sdanielk1977    set anWrite($xStep) [execsql {
225404ca075Sdanielk1977      SELECT (SELECT max(a) FROM t1)
226404ca075Sdanielk1977           + (SELECT max(a) FROM t2)
227404ca075Sdanielk1977           + (SELECT max(a) FROM t3)
228404ca075Sdanielk1977    }]
229404ca075Sdanielk1977    db close
230404ca075Sdanielk1977  } {}
231404ca075Sdanielk1977}
232404ca075Sdanielk1977
23372bcfa6eSdrh# The following tests checks to make sure sqlite3_blocking_step() is
234*8ab4b9e9Sdan# faster than sqlite3_step(). "Faster" in this case means uses fewer
235*8ab4b9e9Sdan# CPU cycles. This is not always the same as faster in wall-clock time
236*8ab4b9e9Sdan# for this type of test. The number of CPU cycles per transaction is
237*8ab4b9e9Sdan# roughly proportional to the number of attempts made (i.e. one plus the
238*8ab4b9e9Sdan# number of SQLITE_BUSY or SQLITE_LOCKED errors that require the transaction
239*8ab4b9e9Sdan# to be retried). So this test just measures that a greater percentage of
240*8ab4b9e9Sdan# transactions attempted using blocking_step() succeed.
241*8ab4b9e9Sdan#
242*8ab4b9e9Sdan# The blocking_step() function is almost always faster on multi-core and is
243*8ab4b9e9Sdan# usually faster on single-core.  But sometimes, by chance, step() will be
244*8ab4b9e9Sdan# faster on a single core, in which case the
24572bcfa6eSdrh# following test will fail.
24672bcfa6eSdrh#
247b71c1751Sdrhputs "The following test seeks to demonstrate that the sqlite3_unlock_notify()"
248*8ab4b9e9Sdanputs "interface helps multi-core systems to run more efficiently.  This test"
249*8ab4b9e9Sdanputs "sometimes fails on single-core machines."
25072bcfa6eSdrhputs [array get anWrite]
251404ca075Sdanielk1977do_test notify2-3 {
252*8ab4b9e9Sdan  set blocking [expr {
253*8ab4b9e9Sdan    double($anSuccess(sqlite3_blocking_step)) /
254*8ab4b9e9Sdan    double($anAttempt(sqlite3_blocking_step))
255*8ab4b9e9Sdan  }]
256*8ab4b9e9Sdan  set non [expr {
257*8ab4b9e9Sdan    double($anSuccess(sqlite3_step)) /
258*8ab4b9e9Sdan    double($anAttempt(sqlite3_step))
259*8ab4b9e9Sdan  }]
260*8ab4b9e9Sdan  puts -nonewline [format " blocking: %.1f%% non-blocking %.1f%% ..." \
261*8ab4b9e9Sdan    [expr $blocking*100.0] [expr $non*100.0]]
262*8ab4b9e9Sdan
263*8ab4b9e9Sdan  expr {$blocking > $non}
264404ca075Sdanielk1977} {1}
265404ca075Sdanielk1977
266404ca075Sdanielk1977sqlite3_enable_shared_cache $::enable_shared_cache
267404ca075Sdanielk1977finish_test
268