]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add new thread-testing code and fix locking under Linux threads. Ticket #530. (CVS...
authordrh <drh@noemail.net>
Fri, 19 Dec 2003 02:52:05 +0000 (02:52 +0000)
committerdrh <drh@noemail.net>
Fri, 19 Dec 2003 02:52:05 +0000 (02:52 +0000)
FossilOrigin-Name: b36a4bb61094d539273c21a9e4042384f10a7806

main.mk
manifest
manifest.uuid
src/os.c
src/tclsqlite.c
src/test4.c [new file with mode: 0644]
test/thread1.test [new file with mode: 0644]

diff --git a/main.mk b/main.mk
index 3d8722dd5abcf6d374e985f40a4da51075d9a2cd..7be90d844c498f56186943aa17903caba837db86 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -112,6 +112,7 @@ TESTSRC = \
   $(TOP)/src/test1.c \
   $(TOP)/src/test2.c \
   $(TOP)/src/test3.c \
+  $(TOP)/src/test4.c \
   $(TOP)/src/md5.c
 
 # Header files used by all library source files.
@@ -312,7 +313,7 @@ tclsqlite:  $(TOP)/src/tclsqlite.c libsqlite.a
 testfixture$(EXE):     $(TOP)/src/tclsqlite.c libsqlite.a $(TESTSRC)
        $(TCCX) $(TCL_FLAGS) -DTCLSH=1 -DSQLITE_TEST=1 -o testfixture$(EXE) \
                $(TESTSRC) $(TOP)/src/tclsqlite.c \
-               libsqlite.a $(LIBTCL)
+               libsqlite.a $(LIBTCL) $(THREADLIB)
 
 fulltest:      testfixture$(EXE) sqlite$(EXE)
        ./testfixture$(EXE) $(TOP)/test/all.test
index e06ab96271b0bd42de49ce9bb818521328f60e40..05c6df0f67bf2bbbca4e6c72831273a06f9dcd7a 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Typo\son\sthe\s"datatypes.html"\sdocument.\s(CVS\s1136)
-D 2003-12-18T14:19:41
+C Add\snew\sthread-testing\scode\sand\sfix\slocking\sunder\sLinux\sthreads.\s\sTicket\s#530.\s(CVS\s1137)
+D 2003-12-19T02:52:06
 F Makefile.in 5cb273b7d0e945d47ee8b9ad1c2a04ce79927d2d
 F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906
 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd
@@ -16,7 +16,7 @@ F doc/report1.txt a031aaf37b185e4fa540223cb516d3bccec7eeac
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
 F libtool bbbea7d79c23323e4100103836028e4fad0d9242
 F ltmain.sh abfb9387049fff6996afc6e325736597795baf11
-F main.mk 3e200c199e46c2b7c3106fd2c3bfa11cd0aa22c9
+F main.mk c97237fc95f38e9041a3fbf5128efa6e23a3de77
 F publish.sh 86b5e8535830a2588f62ce1d5d1ef00e1dede23a
 F spec.template a38492f1c1dd349fc24cb0565e08afc53045304b
 F sqlite.1 83f4a9d37bdf2b7ef079a82d54eaf2e3509ee6ea
@@ -38,7 +38,7 @@ F src/hash.h cd0433998bc1a3759d244e1637fe5a3c13b53bf8
 F src/insert.c 01f66866f35c986eab4a57373ca689a3255ef2df
 F src/main.c 3dd3cae00bade294011da5a3cf9ff660a610c545
 F src/md5.c fe4f9c9c6f71dfc26af8da63e4d04489b1430565
-F src/os.c 226d32db1f36f8932b318b1757c8623be6e4244f
+F src/os.c 000b62b95cad6e3518cb7b71ba9376be192b19dc
 F src/os.h 729395fefcca4b81ae056aa9ff67b72bb40dd9e0
 F src/pager.c ca24fced1ca4c2b8ea519d5fe8ec69a2d846276f
 F src/pager.h 5da62c83443f26b1792cfd72c96c422f91aadd31
@@ -52,10 +52,11 @@ F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
 F src/sqlite.h.in e6cfff01fafc8a82ce82cd8c932af421dc9adb54
 F src/sqliteInt.h f8549cf426920e43efb105a08484768cdb73c808
 F src/table.c d845cb101b5afc1f7fea083c99e3d2fa7998d895
-F src/tclsqlite.c 3efac6b5861ac149c41251d4d4c420c94be5ba6a
+F src/tclsqlite.c 7425d6980a1d96d6d3af911f935ee699c2390db7
 F src/test1.c f9d5816610f7ec4168ab7b098d5207a5708712b6
 F src/test2.c 5014337d8576b731cce5b5a14bec4f0daf432700
 F src/test3.c 30985ebdfaf3ee1462a9b0652d3efbdc8d9798f5
+F src/test4.c 99d0b1d2736b0e61fed996a3e41614f626d7b56b
 F src/threadtest.c d641a5219e718e18a1a80a50eb9bb549f451f42e
 F src/tokenize.c d10e7f0b4d8634f6f37237b4e65314e3e5a3a34b
 F src/trigger.c ce83e017b407d046e909d05373d7f8ee70f9f7f9
@@ -129,6 +130,7 @@ F test/tableapi.test d881e787779a175238b72f55b5e50d3a85ab47a6
 F test/tclsqlite.test f141303e0f2e9a616b551813e2b21bd38c5dca50
 F test/temptable.test c82bd6f800f10e8cf96921af6315e5f1c21e2692
 F test/tester.tcl 2671536d3650c29e7c105219f277568b0884cb58
+F test/thread1.test e98de6574910978017a621d0851fda13e257763d
 F test/trans.test 75e7a171b5d2d94ee56766459113e2ad0e5f809d
 F test/trigger1.test 3fe06daecf8334df840025e154e95fd4727600d7
 F test/trigger2.test 0767ab30cb5a2c8402c8524f3d566b410b6f5263
@@ -177,7 +179,7 @@ F www/speed.tcl 2f6b1155b99d39adb185f900456d1d592c4832b3
 F www/sqlite.tcl 3c83b08cf9f18aa2d69453ff441a36c40e431604
 F www/tclsqlite.tcl b9271d44dcf147a93c98f8ecf28c927307abd6da
 F www/vdbe.tcl 9b9095d4495f37697fd1935d10e14c6015e80aa1
-P a0451ccf2d0377536073ecaa2d6b70d05f5a5734
-R 9e43f2ff205876924bdbd5d3204b97cb
+P 80b1e277123c07b2db7441a9e600dd69ef55a0da
+R ea1fab6d3d994f942c20af002987010c
 U drh
-Z 47f92686e837a5b11a2d636a008c6c95
+Z b3907cc74f1a3c0fe5cfc79f0eafa427
index 447b55bef18b08261879ab04bf0bbab59cd6a14f..aa294d8158dc1198d0b29f3cc79219b1b8f38021 100644 (file)
@@ -1 +1 @@
-80b1e277123c07b2db7441a9e600dd69ef55a0da
\ No newline at end of file
+b36a4bb61094d539273c21a9e4042384f10a7806
\ No newline at end of file
index e9b4d6f521ab97580afd6d608b1f7a038e32c9e1..60928161f21a0c6161c8ad955b599678fb403c89 100644 (file)
--- a/src/os.c
+++ b/src/os.c
 # define fcntl(A,B,C) 0
 #endif
 
+/*
+** Macros used to determine whether or not to use threads.  The
+** SQLITE_UNIX_THREADS macro is defined if we are synchronizing for
+** Posix threads and SQLITE_W32_THREADS is defined if we are
+** synchronizing using Win32 threads.
+*/
+#if OS_UNIX && defined(THREADSAFE) && THREADSAFE
+# include <pthread.h>
+# define SQLITE_UNIX_THREADS 1
+#endif
+#if OS_WIN && defined(THREADSAFE) && THREADSAFE
+# define SQLITE_W32_THREADS 1
+#endif
+#if OS_MAC && defined(THREADSAFE) && THREADSAFE
+# include <Multiprocessing.h>
+# define SQLITE_MACOS_MULTITASKING 1
+#endif
+
 /*
 ** Macros for performance tracing.  Normally turned off
 */
@@ -155,6 +173,9 @@ static unsigned int elapse;
 struct inodeKey {
   dev_t dev;   /* Device number */
   ino_t ino;   /* Inode number */
+#ifdef SQLITE_UNIX_THREADS
+  pthread_t thread_id;   /* Which thread are we */
+#endif
 };
 
 /*
@@ -190,6 +211,9 @@ static struct lockInfo *findLockInfo(int fd){
   memset(&key, 0, sizeof(key));
   key.dev = statbuf.st_dev;
   key.ino = statbuf.st_ino;
+#ifdef SQLITE_UNIX_THREADS
+  key.thread_id = pthread_self();
+#endif
   pInfo = (struct lockInfo*)sqliteHashFind(&lockHash, &key, sizeof(key));
   if( pInfo==0 ){
     struct lockInfo *pOld;
@@ -1467,24 +1491,6 @@ int sqliteOsSleep(int ms){
 #endif
 }
 
-/*
-** Macros used to determine whether or not to use threads.  The
-** SQLITE_UNIX_THREADS macro is defined if we are synchronizing for
-** Posix threads and SQLITE_W32_THREADS is defined if we are
-** synchronizing using Win32 threads.
-*/
-#if OS_UNIX && defined(THREADSAFE) && THREADSAFE
-# include <pthread.h>
-# define SQLITE_UNIX_THREADS 1
-#endif
-#if OS_WIN && defined(THREADSAFE) && THREADSAFE
-# define SQLITE_W32_THREADS 1
-#endif
-#if OS_MAC && defined(THREADSAFE) && THREADSAFE
-# include <Multiprocessing.h>
-# define SQLITE_MACOS_MULTITASKING 1
-#endif
-
 /*
 ** Static variables used for thread synchronization
 */
index ee7cf6f61fc0f5b492f45bc76e685c0fa9714705..cd6a1ba13119bf527bf7c8c9b41070860f95ae20 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** A TCL Interface to SQLite
 **
-** $Id: tclsqlite.c,v 1.51 2003/10/18 09:37:26 danielk1977 Exp $
+** $Id: tclsqlite.c,v 1.52 2003/12/19 02:52:09 drh Exp $
 */
 #ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */
 
@@ -1074,10 +1074,12 @@ int TCLSH_MAIN(int argc, char **argv){
     extern int Sqlitetest1_Init(Tcl_Interp*);
     extern int Sqlitetest2_Init(Tcl_Interp*);
     extern int Sqlitetest3_Init(Tcl_Interp*);
+    extern int Sqlitetest4_Init(Tcl_Interp*);
     extern int Md5_Init(Tcl_Interp*);
     Sqlitetest1_Init(interp);
     Sqlitetest2_Init(interp);
     Sqlitetest3_Init(interp);
+    Sqlitetest4_Init(interp);
     Md5_Init(interp);
   }
 #endif
diff --git a/src/test4.c b/src/test4.c
new file mode 100644 (file)
index 0000000..1c6423c
--- /dev/null
@@ -0,0 +1,594 @@
+/*
+** 2003 December 18
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Code for testing the the SQLite library in a multithreaded environment.
+**
+** $Id: test4.c,v 1.1 2003/12/19 02:52:09 drh Exp $
+*/
+#include "sqliteInt.h"
+#include "tcl.h"
+#if defined(OS_UNIX) && defined(THREADSAFE) && THREADSAFE==1
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <sched.h>
+#include <ctype.h>
+
+/*
+** Each thread is controlled by an instance of the following
+** structure.
+*/
+typedef struct Thread Thread;
+struct Thread {
+  /* The first group of fields are writable by the master and read-only
+  ** to the thread. */
+  char *zFilename;       /* Name of database file */
+  void (*xOp)(Thread*);  /* next operation to do */
+  char *zArg;            /* argument usable by xOp */
+  int opnum;             /* Operation number */
+  int busy;              /* True if this thread is in use */
+
+  /* The next group of fields are writable by the thread but read-only to the
+  ** master. */
+  int completed;        /* Number of operations completed */
+  sqlite *db;           /* Open database */
+  sqlite_vm *vm;        /* Pending operation */
+  char *zErr;           /* operation error */
+  char *zStaticErr;     /* Static error message */
+  int rc;               /* operation return code */
+  int argc;             /* number of columns in result */
+  const char **argv;    /* result columns */
+  const char **colv;    /* result column names */
+};
+
+/*
+** There can be as many as 26 threads running at once.  Each is named
+** by a capital letter: A, B, C, ..., Y, Z.
+*/
+#define N_THREAD 26
+static Thread threadset[N_THREAD];
+
+
+/*
+** The main loop for a thread.  Threads use busy waiting. 
+*/
+static void *thread_main(void *pArg){
+  Thread *p = (Thread*)pArg;
+  if( p->db ){
+    sqlite_close(p->db);
+  }
+  p->db = sqlite_open(p->zFilename, 0, &p->zErr);
+  p->vm = 0;
+  p->completed = 1;
+  while( p->opnum<=p->completed ) sched_yield();
+  while( p->xOp ){
+    if( p->zErr && p->zErr!=p->zStaticErr ){
+      sqlite_freemem(p->zErr);
+      p->zErr = 0;
+    }
+    (*p->xOp)(p);
+    p->completed++;
+    while( p->opnum<=p->completed ) sched_yield();
+  }
+  if( p->vm ){
+    sqlite_finalize(p->vm, 0);
+    p->vm = 0;
+  }
+  if( p->db ){
+    sqlite_close(p->db);
+    p->db = 0;
+  }
+  if( p->zErr && p->zErr!=p->zStaticErr ){
+    sqlite_freemem(p->zErr);
+    p->zErr = 0;
+  }
+  p->completed++;
+  return 0;
+}
+
+/*
+** Get a thread ID which is an upper case letter.  Return the index.
+** If the argument is not a valid thread ID put an error message in
+** the interpreter and return -1.
+*/
+static int parse_thread_id(Tcl_Interp *interp, const char *zArg){
+  if( zArg==0 || zArg[0]==0 || zArg[1]!=0 || !isupper(zArg[0]) ){
+    Tcl_AppendResult(interp, "thread ID must be an upper case letter", 0);
+    return -1;
+  }
+  return zArg[0] - 'A';
+}
+
+/*
+** Usage:    thread_create NAME  FILENAME
+**
+** NAME should be an upper case letter.  Start the thread running with
+** an open connection to the given database.
+*/
+static int tcl_thread_create(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  pthread_t x;
+  int rc;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID FILENAME", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( threadset[i].busy ){
+    Tcl_AppendResult(interp, "thread ", argv[1], " is already running", 0);
+    return TCL_ERROR;
+  }
+  threadset[i].busy = 1;
+  sqliteFree(threadset[i].zFilename);
+  threadset[i].zFilename = sqliteStrDup(argv[2]);
+  threadset[i].opnum = 1;
+  rc = pthread_create(&x, 0, thread_main, &threadset[i]);
+  if( rc ){
+    Tcl_AppendResult(interp, "failed to create the thread", 0);
+    sqliteFree(threadset[i].zFilename);
+    threadset[i].busy = 0;
+    return TCL_ERROR;
+  }
+  pthread_detach(x);
+  return TCL_OK;
+}
+
+/*
+** Wait for a thread to reach its idle state.
+*/
+static void thread_wait(Thread *p){
+  while( p->opnum>p->completed ) sched_yield();
+}
+
+/*
+** Usage:  thread_wait ID
+**
+** Wait on thread ID to reach its idle state.
+*/
+static int tcl_thread_wait(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  return TCL_OK;
+}
+
+/*
+** Stop a thread.
+*/
+static void stop_thread(Thread *p){
+  thread_wait(p);
+  p->xOp = 0;
+  p->opnum++;
+  thread_wait(p);
+  sqliteFree(p->zArg);
+  p->zArg = 0;
+  sqliteFree(p->zFilename);
+  p->zFilename = 0;
+  p->busy = 0;
+}
+
+/*
+** Usage:  thread_halt ID
+**
+** Cause a thread to shut itself down.  Wait for the shutdown to be
+** completed.  If ID is "*" then stop all threads.
+*/
+static int tcl_thread_halt(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  if( argv[1][0]=='*' && argv[1][1]==0 ){
+    for(i=0; i<N_THREAD; i++){
+      if( threadset[i].busy ) stop_thread(&threadset[i]);
+    }
+  }else{
+    i = parse_thread_id(interp, argv[1]);
+    if( i<0 ) return TCL_ERROR;
+    if( !threadset[i].busy ){
+      Tcl_AppendResult(interp, "no such thread", 0);
+      return TCL_ERROR;
+    }
+    stop_thread(&threadset[i]);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_argc  ID
+**
+** Wait on the most recent thread_step to complete, then return the
+** number of columns in the result set.
+*/
+static int tcl_thread_argc(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  char zBuf[100];
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  sprintf(zBuf, "%d", threadset[i].argc);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_argv  ID   N
+**
+** Wait on the most recent thread_step to complete, then return the
+** value of the N-th columns in the result set.
+*/
+static int tcl_thread_argv(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  int n;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID N", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
+  thread_wait(&threadset[i]);
+  if( n<0 || n>=threadset[i].argc ){
+    Tcl_AppendResult(interp, "column number out of range", 0);
+    return TCL_ERROR;
+  }
+  Tcl_AppendResult(interp, threadset[i].argv[n], 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_colname  ID   N
+**
+** Wait on the most recent thread_step to complete, then return the
+** name of the N-th columns in the result set.
+*/
+static int tcl_thread_colname(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  int n;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID N", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
+  thread_wait(&threadset[i]);
+  if( n<0 || n>=threadset[i].argc ){
+    Tcl_AppendResult(interp, "column number out of range", 0);
+    return TCL_ERROR;
+  }
+  Tcl_AppendResult(interp, threadset[i].colv[n], 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_result  ID
+**
+** Wait on the most recent operation to complete, then return the
+** result code from that operation.
+*/
+static int tcl_thread_result(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  const char *zName;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  switch( threadset[i].rc ){
+    case SQLITE_OK:         zName = "SQLITE_OK";          break;
+    case SQLITE_ERROR:      zName = "SQLITE_ERROR";       break;
+    case SQLITE_INTERNAL:   zName = "SQLITE_INTERNAL";    break;
+    case SQLITE_PERM:       zName = "SQLITE_PERM";        break;
+    case SQLITE_ABORT:      zName = "SQLITE_ABORT";       break;
+    case SQLITE_BUSY:       zName = "SQLITE_BUSY";        break;
+    case SQLITE_LOCKED:     zName = "SQLITE_LOCKED";      break;
+    case SQLITE_NOMEM:      zName = "SQLITE_NOMEM";       break;
+    case SQLITE_READONLY:   zName = "SQLITE_READONLY";    break;
+    case SQLITE_INTERRUPT:  zName = "SQLITE_INTERRUPT";   break;
+    case SQLITE_IOERR:      zName = "SQLITE_IOERR";       break;
+    case SQLITE_CORRUPT:    zName = "SQLITE_CORRUPT";     break;
+    case SQLITE_NOTFOUND:   zName = "SQLITE_NOTFOUND";    break;
+    case SQLITE_FULL:       zName = "SQLITE_FULL";        break;
+    case SQLITE_CANTOPEN:   zName = "SQLITE_CANTOPEN";    break;
+    case SQLITE_PROTOCOL:   zName = "SQLITE_PROTOCOL";    break;
+    case SQLITE_EMPTY:      zName = "SQLITE_EMPTY";       break;
+    case SQLITE_SCHEMA:     zName = "SQLITE_SCHEMA";      break;
+    case SQLITE_TOOBIG:     zName = "SQLITE_TOOBIG";      break;
+    case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT";  break;
+    case SQLITE_MISMATCH:   zName = "SQLITE_MISMATCH";    break;
+    case SQLITE_MISUSE:     zName = "SQLITE_MISUSE";      break;
+    case SQLITE_NOLFS:      zName = "SQLITE_NOLFS";       break;
+    case SQLITE_AUTH:       zName = "SQLITE_AUTH";        break;
+    case SQLITE_FORMAT:     zName = "SQLITE_FORMAT";      break;
+    case SQLITE_RANGE:      zName = "SQLITE_RANGE";       break;
+    case SQLITE_ROW:        zName = "SQLITE_ROW";         break;
+    case SQLITE_DONE:       zName = "SQLITE_DONE";        break;
+    default:                zName = "SQLITE_Unknown";     break;
+  }
+  Tcl_AppendResult(interp, zName, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage: thread_error  ID
+**
+** Wait on the most recent operation to complete, then return the
+** error string.
+*/
+static int tcl_thread_error(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  Tcl_AppendResult(interp, threadset[i].zErr, 0);
+  return TCL_OK;
+}
+
+/*
+** This procedure runs in the thread to compile an SQL statement.
+*/
+static void do_compile(Thread *p){
+  if( p->db==0 ){
+    p->zErr = p->zStaticErr = "no database is open";
+    p->rc = SQLITE_ERROR;
+    return;
+  }
+  if( p->vm ){
+    sqlite_finalize(p->vm, 0);
+    p->vm = 0;
+  }
+  p->rc = sqlite_compile(p->db, p->zArg, 0, &p->vm, &p->zErr);
+}
+
+/*
+** Usage: thread_compile ID SQL
+**
+** Compile a new virtual machine.
+*/
+static int tcl_thread_compile(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID SQL", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  threadset[i].xOp = do_compile;
+  sqliteFree(threadset[i].zArg);
+  threadset[i].zArg = sqliteStrDup(argv[2]);
+  threadset[i].opnum++;
+  return TCL_OK;
+}
+
+/*
+** This procedure runs in the thread to step the virtual machine.
+*/
+static void do_step(Thread *p){
+  if( p->vm==0 ){
+    p->zErr = p->zStaticErr = "no virtual machine available";
+    p->rc = SQLITE_ERROR;
+    return;
+  }
+  p->rc = sqlite_step(p->vm, &p->argc, &p->argv, &p->colv);
+}
+
+/*
+** Usage: thread_step ID
+**
+** Advance the virtual machine by one step
+*/
+static int tcl_thread_step(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " IDL", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  threadset[i].xOp = do_step;
+  threadset[i].opnum++;
+  return TCL_OK;
+}
+
+/*
+** This procedure runs in the thread to finalize a virtual machine.
+*/
+static void do_finalize(Thread *p){
+  if( p->vm==0 ){
+    p->zErr = p->zStaticErr = "no virtual machine available";
+    p->rc = SQLITE_ERROR;
+    return;
+  }
+  p->rc = sqlite_finalize(p->vm, &p->zErr);
+  p->vm = 0;
+}
+
+/*
+** Usage: thread_finalize ID
+**
+** Finalize the virtual machine.
+*/
+static int tcl_thread_finalize(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  int i;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " IDL", 0);
+    return TCL_ERROR;
+  }
+  i = parse_thread_id(interp, argv[1]);
+  if( i<0 ) return TCL_ERROR;
+  if( !threadset[i].busy ){
+    Tcl_AppendResult(interp, "no such thread", 0);
+    return TCL_ERROR;
+  }
+  thread_wait(&threadset[i]);
+  threadset[i].xOp = do_finalize;
+  sqliteFree(threadset[i].zArg);
+  threadset[i].zArg = 0;
+  threadset[i].opnum++;
+  return TCL_OK;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest4_Init(Tcl_Interp *interp){
+  static struct {
+     char *zName;
+     Tcl_CmdProc *xProc;
+  } aCmd[] = {
+     { "thread_create",     (Tcl_CmdProc*)tcl_thread_create     },
+     { "thread_wait",       (Tcl_CmdProc*)tcl_thread_wait       },
+     { "thread_halt",       (Tcl_CmdProc*)tcl_thread_halt       },
+     { "thread_argc",       (Tcl_CmdProc*)tcl_thread_argc       },
+     { "thread_argv",       (Tcl_CmdProc*)tcl_thread_argv       },
+     { "thread_colname",    (Tcl_CmdProc*)tcl_thread_colname    },
+     { "thread_result",     (Tcl_CmdProc*)tcl_thread_result     },
+     { "thread_error",      (Tcl_CmdProc*)tcl_thread_error      },
+     { "thread_compile",    (Tcl_CmdProc*)tcl_thread_compile    },
+     { "thread_step",       (Tcl_CmdProc*)tcl_thread_step       },
+     { "thread_finalize",   (Tcl_CmdProc*)tcl_thread_finalize   },
+  };
+  int i;
+
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
+  }
+  return TCL_OK;
+}
+#else
+int Sqlitetest4_Init(Tcl_Interp *interp){ return TCL_OK; }
+#endif /* OS_UNIX */
diff --git a/test/thread1.test b/test/thread1.test
new file mode 100644 (file)
index 0000000..8dd1bb7
--- /dev/null
@@ -0,0 +1,88 @@
+# 2003 December 18
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is multithreading behavior
+#
+# $Id: thread1.test,v 1.1 2003/12/19 02:52:09 drh Exp $
+
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# Skip this whole file if the thread testing code is not enabled
+#
+if {[llength [info command thread_step]]==0} {
+  finish_test
+  return
+}
+
+# Create some data to work with
+#
+do_test thread1-1.1 {
+  execsql {
+    CREATE TABLE t1(a,b);
+    INSERT INTO t1 VALUES(1,'abcdefgh');
+    INSERT INTO t1 SELECT a+1, b||b FROM t1;
+    INSERT INTO t1 SELECT a+2, b||b FROM t1;
+    INSERT INTO t1 SELECT a+4, b||b FROM t1;
+    SELECT count(*), max(length(b)) FROM t1;
+  }
+} {8 64}
+
+# Interleave two threads on read access.  Then make sure a third
+# thread can write the database.
+#
+do_test thread1-1.2 {
+  thread_create A test.db
+  thread_create B test.db
+  thread_create C test.db
+  thread_compile A {SELECT a FROM t1}
+  thread_step A
+  thread_result A
+} SQLITE_ROW
+do_test thread1-1.3 {
+  thread_argc A
+} 1
+do_test thread1-1.4 {
+  thread_argv A 0
+} 1
+do_test thread1-1.5 {
+  thread_compile B {SELECT b FROM t1}
+  thread_step B
+  thread_result B
+} SQLITE_ROW
+do_test thread1-1.6 {
+  thread_argc B
+} 1
+do_test thread1-1.7 {
+  thread_argv B 0
+} abcdefgh
+do_test thread1-1.8 {
+  thread_finalize A
+  thread_result A
+} SQLITE_OK
+do_test thread1-1.9 {
+  thread_finalize B
+  thread_result B
+} SQLITE_OK
+do_test thread1-1.10 {
+  thread_compile C {CREATE TABLE t2(x,y)}
+  thread_step C
+  thread_result C
+} SQLITE_DONE
+do_test thread1-1.11 {
+  thread_finalize C
+  thread_result C
+} SQLITE_OK
+
+
+thread_halt *   
+finish_test