From 2ab2d55f017b0945a8722ccb0d6e5133b0df15a4 Mon Sep 17 00:00:00 2001
From: drh <drh@noemail.net>
Date: Wed, 15 May 2013 16:08:33 +0000
Subject: [PATCH] Fix the sharedA.test module so that it does not attempt to
 run TCL callbacks on a different thread from where the interpreter was
 originally created.

FossilOrigin-Name: 65ff754e3521aa8ee9135d235166cac2a8f57ebd
---
 manifest          | 14 +++++------
 manifest.uuid     |  2 +-
 test/sharedA.test | 64 ++++++++++++++++++++++++++++++++++++-----------
 3 files changed, 58 insertions(+), 22 deletions(-)

diff --git a/manifest b/manifest
index 024b7eeade..cda4f88975 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Do\snot\srun\ssharedA.test\sif\sthe\ssystem\sis\snot\sthreadsafe.
-D 2013-05-15T15:53:52.221
+C Fix\sthe\ssharedA.test\smodule\sso\sthat\sit\sdoes\snot\sattempt\sto\srun\sTCL\scallbacks\non\sa\sdifferent\sthread\sfrom\swhere\sthe\sinterpreter\swas\soriginally\screated.
+D 2013-05-15T16:08:33.601
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in ce81671efd6223d19d4c8c6b88ac2c4134427111
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -738,7 +738,7 @@ F test/shared6.test 866bb4982c45ce216c61ded5e8fde4e7e2f3ffa9
 F test/shared7.test 960760bc8d03e1419e70dea69cf41db62853616e
 F test/shared8.test b27befbefbe7f4517f1d6b7ff8f64a41ec74165d
 F test/shared9.test 5f2a8f79b4d6c7d107a01ffa1ed05ae7e6333e21
-F test/sharedA.test 8eddeda2407bdcd335ec8b8ba731aefc0e3e8232
+F test/sharedA.test 0cdf1a76dfa00e6beee66af5b534b1e8df2720f5
 F test/shared_err.test 0079c05c97d88cfa03989b7c20a8b266983087aa
 F test/sharedlock.test ffa0a3c4ac192145b310f1254f8afca4d553eabf
 F test/shell1.test 4a2f57952719972c6f862134463f8712e953c038
@@ -1064,7 +1064,7 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
 F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac
-P 47dd65a890955f333d431e275f3f4d95d34a5ba5
-R 7b8246f3bd54a0ac6d93356e2396ef91
-U dan
-Z d923d1c201be4d3e0bbb63e5d84a5ca9
+P d484eaf8d6dfaf2c1065b93b2a52a6db91c09fa4
+R 2b3d38cfaeff54ce7cad59c19d675f6e
+U drh
+Z adae8d677b3886690161b439bdcd47db
diff --git a/manifest.uuid b/manifest.uuid
index 7ab7a5a608..2eaef6834a 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-d484eaf8d6dfaf2c1065b93b2a52a6db91c09fa4
\ No newline at end of file
+65ff754e3521aa8ee9135d235166cac2a8f57ebd
\ No newline at end of file
diff --git a/test/sharedA.test b/test/sharedA.test
index e0031ccc03..146fb26be0 100644
--- a/test/sharedA.test
+++ b/test/sharedA.test
@@ -89,11 +89,31 @@ do_test 1.3 {
 
 #-------------------------------------------------------------------------
 #
+# sqlite3RollbackAll() loops through all attached b-trees and rolls
+# back each one separately.  Then if the SQLITE_InternChanges flag is
+# set, it resets the schema.  Both of the above steps must be done
+# while holding a mutex, otherwise another thread might slip in and
+# try to use the new schema with the old data.
+#
+# The following sequence of tests attempt to verify that the actions
+# taken by sqlite3RollbackAll() are thread-atomic (that they cannot be
+# interrupted by a separate thread.)  
+#
+# Note that a TCL interpreter can only be used within the thread in which
+# it was originally created (because it uses thread-local-storage).  
+# The tvfs callbacks must therefore only run on the main thread.  
+# There is some trickery in the read_callback procedure to ensure that
+# this is the case.
+#
 testvfs tvfs
-tvfs filter xRead
-tvfs script read_callback
-proc read_callback {args} { }
 
+# Set up two databases and two database connections.
+#
+#   db1:  main(test.db), two(test2.db)
+#   db2:  main(test.db)
+#
+# The cache for test.db is shared between db1 and db2.
+#
 do_test 2.1 {
   forcedelete test.db test.db2
   sqlite3 db1 test.db -vfs tvfs
@@ -112,12 +132,12 @@ do_test 2.1 {
   db2 eval { SELECT * FROM t1 }
 } {1 2 3}
 
+# Create a prepared statement on db2 that will attempt a schema change
+# in test.db.  Meanwhile, start a transaction on db1 that changes
+# the schema of test.db and that creates a rollback journal on test2.db
+#
 do_test 2.2 {
   set ::STMT [sqlite3_prepare db2 "CREATE INDEX i1 ON t1(x)" -1 tail]
-  set {} {}
-} {}
-
-do_test 2.3 {
   db1 eval {
     BEGIN;
       CREATE INDEX i1 ON t1(x);
@@ -125,21 +145,38 @@ do_test 2.3 {
   }
 } {}
 
-set ::bFired 0
+# Set up a callback that will cause db2 to try to execute its
+# schema change when db1 accesses the journal file of test2.db.
+#
+# This callback will be invoked after the content of test.db has
+# be rolled back but before the schema has been reset.  If the
+# sqlite3RollbackAll() operation is not thread-atomic, then the
+# db2 statement in the callback will see old content with the newer
+# schema, which is wrong.
+#
+tvfs filter xRead
+tvfs script read_callback
+unset -nocomplain ::some_time_laster
+unset -nocomplain ::thread_result
 proc read_callback {call file args} { 
-  if { $::bFired==0 && [string match *test.db2-journal $file] } {
+  if {[string match *test.db2-journal $file]} {
+    tvfs filter {}   ;# Ensure that tvfs callbacks to do run on the
+                      # child thread
     sqlthread spawn ::thread_result [subst -nocommands {
       sqlite3_step $::STMT
       set rc [sqlite3_finalize $::STMT]
     }]
-    after 1000 { set ::bFired 1 }
-    vwait ::bFired
+    after 1000 { set ::some_time_later 1 }
+    vwait ::some_time_later
   }
 }
-do_test 2.4 { db1 eval ROLLBACK } {}
+do_test 2.3 { db1 eval ROLLBACK } {}
 
+# Verify that the db2 statement invoked by the callback detected the
+# schema change.
+#
 if {[info exists ::thread_result]==0} { vwait ::thread_result }
-do_test 2.5 { 
+do_test 2.4 { 
   list $::thread_result [sqlite3_errmsg db2] 
 } {SQLITE_SCHEMA {database schema has changed}}
 
@@ -149,4 +186,3 @@ tvfs delete
 
 sqlite3_enable_shared_cache $::enable_shared_cache
 finish_test
-