/ Check-in [b36a4bb6]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add new thread-testing code and fix locking under Linux threads. Ticket #530. (CVS 1137)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: b36a4bb61094d539273c21a9e4042384f10a7806
User & Date: drh 2003-12-19 02:52:06
Context
2003-12-19
08:40
A better fix for ticket #530 - one that is likely to work on unix implementations in addition to linux. Also more tests for multi-thread locking added. (CVS 1138) check-in: 7dddbeb5 user: drh tags: trunk
02:52
Add new thread-testing code and fix locking under Linux threads. Ticket #530. (CVS 1137) check-in: b36a4bb6 user: drh tags: trunk
2003-12-18
14:19
Typo on the "datatypes.html" document. (CVS 1136) check-in: 80b1e277 user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to main.mk.

   108    108     $(TOP)/src/btree.c \
   109    109     $(TOP)/src/func.c \
   110    110     $(TOP)/src/os.c \
   111    111     $(TOP)/src/pager.c \
   112    112     $(TOP)/src/test1.c \
   113    113     $(TOP)/src/test2.c \
   114    114     $(TOP)/src/test3.c \
          115  +  $(TOP)/src/test4.c \
   115    116     $(TOP)/src/md5.c
   116    117   
   117    118   # Header files used by all library source files.
   118    119   #
   119    120   HDR = \
   120    121      sqlite.h  \
   121    122      $(TOP)/src/btree.h \
................................................................................
   308    309   tclsqlite:	$(TOP)/src/tclsqlite.c libsqlite.a
   309    310   	$(TCCX) $(TCL_FLAGS) -DTCLSH=1 -o tclsqlite \
   310    311   		$(TOP)/src/tclsqlite.c libsqlite.a $(LIBTCL)
   311    312   
   312    313   testfixture$(EXE):	$(TOP)/src/tclsqlite.c libsqlite.a $(TESTSRC)
   313    314   	$(TCCX) $(TCL_FLAGS) -DTCLSH=1 -DSQLITE_TEST=1 -o testfixture$(EXE) \
   314    315   		$(TESTSRC) $(TOP)/src/tclsqlite.c \
   315         -		libsqlite.a $(LIBTCL)
          316  +		libsqlite.a $(LIBTCL) $(THREADLIB)
   316    317   
   317    318   fulltest:	testfixture$(EXE) sqlite$(EXE)
   318    319   	./testfixture$(EXE) $(TOP)/test/all.test
   319    320   
   320    321   test:	testfixture$(EXE) sqlite$(EXE)
   321    322   	./testfixture$(EXE) $(TOP)/test/quick.test
   322    323   

Changes to src/os.c.

    57     57   ** that always succeeds.  This means that locking does not occur under
    58     58   ** DJGPP.  But its DOS - what did you expect?
    59     59   */
    60     60   #ifdef __DJGPP__
    61     61   # define fcntl(A,B,C) 0
    62     62   #endif
    63     63   
           64  +/*
           65  +** Macros used to determine whether or not to use threads.  The
           66  +** SQLITE_UNIX_THREADS macro is defined if we are synchronizing for
           67  +** Posix threads and SQLITE_W32_THREADS is defined if we are
           68  +** synchronizing using Win32 threads.
           69  +*/
           70  +#if OS_UNIX && defined(THREADSAFE) && THREADSAFE
           71  +# include <pthread.h>
           72  +# define SQLITE_UNIX_THREADS 1
           73  +#endif
           74  +#if OS_WIN && defined(THREADSAFE) && THREADSAFE
           75  +# define SQLITE_W32_THREADS 1
           76  +#endif
           77  +#if OS_MAC && defined(THREADSAFE) && THREADSAFE
           78  +# include <Multiprocessing.h>
           79  +# define SQLITE_MACOS_MULTITASKING 1
           80  +#endif
           81  +
    64     82   /*
    65     83   ** Macros for performance tracing.  Normally turned off
    66     84   */
    67     85   #if 0
    68     86   static int last_page = 0;
    69     87   __inline__ unsigned long long int hwtime(void){
    70     88     unsigned long long int x;
................................................................................
   151    169   /*
   152    170   ** An instance of the following structure serves as the key used
   153    171   ** to locate a particular lockInfo structure given its inode. 
   154    172   */
   155    173   struct inodeKey {
   156    174     dev_t dev;   /* Device number */
   157    175     ino_t ino;   /* Inode number */
          176  +#ifdef SQLITE_UNIX_THREADS
          177  +  pthread_t thread_id;   /* Which thread are we */
          178  +#endif
   158    179   };
   159    180   
   160    181   /*
   161    182   ** An instance of the following structure is allocated for each inode.
   162    183   ** A single inode can have multiple file descriptors, so each OsFile
   163    184   ** structure contains a pointer to an instance of this object and this
   164    185   ** object keeps a count of the number of OsFiles pointing to it.
................................................................................
   186    207     struct stat statbuf;
   187    208     struct lockInfo *pInfo;
   188    209     rc = fstat(fd, &statbuf);
   189    210     if( rc!=0 ) return 0;
   190    211     memset(&key, 0, sizeof(key));
   191    212     key.dev = statbuf.st_dev;
   192    213     key.ino = statbuf.st_ino;
          214  +#ifdef SQLITE_UNIX_THREADS
          215  +  key.thread_id = pthread_self();
          216  +#endif
   193    217     pInfo = (struct lockInfo*)sqliteHashFind(&lockHash, &key, sizeof(key));
   194    218     if( pInfo==0 ){
   195    219       struct lockInfo *pOld;
   196    220       pInfo = sqliteMalloc( sizeof(*pInfo) );
   197    221       if( pInfo==0 ) return 0;
   198    222       pInfo->key = key;
   199    223       pInfo->nRef = 1;
................................................................................
  1463   1487     UInt32 finalTicks;
  1464   1488     UInt32 ticks = (((UInt32)ms+16)*3)/50;  /* 1/60 sec per tick */
  1465   1489     Delay(ticks, &finalTicks);
  1466   1490     return (int)((ticks*50)/3);
  1467   1491   #endif
  1468   1492   }
  1469   1493   
  1470         -/*
  1471         -** Macros used to determine whether or not to use threads.  The
  1472         -** SQLITE_UNIX_THREADS macro is defined if we are synchronizing for
  1473         -** Posix threads and SQLITE_W32_THREADS is defined if we are
  1474         -** synchronizing using Win32 threads.
  1475         -*/
  1476         -#if OS_UNIX && defined(THREADSAFE) && THREADSAFE
  1477         -# include <pthread.h>
  1478         -# define SQLITE_UNIX_THREADS 1
  1479         -#endif
  1480         -#if OS_WIN && defined(THREADSAFE) && THREADSAFE
  1481         -# define SQLITE_W32_THREADS 1
  1482         -#endif
  1483         -#if OS_MAC && defined(THREADSAFE) && THREADSAFE
  1484         -# include <Multiprocessing.h>
  1485         -# define SQLITE_MACOS_MULTITASKING 1
  1486         -#endif
  1487         -
  1488   1494   /*
  1489   1495   ** Static variables used for thread synchronization
  1490   1496   */
  1491   1497   static int inMutex = 0;
  1492   1498   #ifdef SQLITE_UNIX_THREADS
  1493   1499     static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  1494   1500   #endif

Changes to src/tclsqlite.c.

     7      7   **    May you do good and not evil.
     8      8   **    May you find forgiveness for yourself and forgive others.
     9      9   **    May you share freely, never taking more than you give.
    10     10   **
    11     11   *************************************************************************
    12     12   ** A TCL Interface to SQLite
    13     13   **
    14         -** $Id: tclsqlite.c,v 1.51 2003/10/18 09:37:26 danielk1977 Exp $
           14  +** $Id: tclsqlite.c,v 1.52 2003/12/19 02:52:09 drh Exp $
    15     15   */
    16     16   #ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */
    17     17   
    18     18   #include "sqliteInt.h"
    19     19   #include "tcl.h"
    20     20   #include <stdlib.h>
    21     21   #include <string.h>
................................................................................
  1070   1070     interp = Tcl_CreateInterp();
  1071   1071     Sqlite_Init(interp);
  1072   1072   #ifdef SQLITE_TEST
  1073   1073     {
  1074   1074       extern int Sqlitetest1_Init(Tcl_Interp*);
  1075   1075       extern int Sqlitetest2_Init(Tcl_Interp*);
  1076   1076       extern int Sqlitetest3_Init(Tcl_Interp*);
         1077  +    extern int Sqlitetest4_Init(Tcl_Interp*);
  1077   1078       extern int Md5_Init(Tcl_Interp*);
  1078   1079       Sqlitetest1_Init(interp);
  1079   1080       Sqlitetest2_Init(interp);
  1080   1081       Sqlitetest3_Init(interp);
         1082  +    Sqlitetest4_Init(interp);
  1081   1083       Md5_Init(interp);
  1082   1084     }
  1083   1085   #endif
  1084   1086     if( argc>=2 ){
  1085   1087       int i;
  1086   1088       Tcl_SetVar(interp,"argv0",argv[1],TCL_GLOBAL_ONLY);
  1087   1089       Tcl_SetVar(interp,"argv", "", TCL_GLOBAL_ONLY);

Added src/test4.c.

            1  +/*
            2  +** 2003 December 18
            3  +**
            4  +** The author disclaims copyright to this source code.  In place of
            5  +** a legal notice, here is a blessing:
            6  +**
            7  +**    May you do good and not evil.
            8  +**    May you find forgiveness for yourself and forgive others.
            9  +**    May you share freely, never taking more than you give.
           10  +**
           11  +*************************************************************************
           12  +** Code for testing the the SQLite library in a multithreaded environment.
           13  +**
           14  +** $Id: test4.c,v 1.1 2003/12/19 02:52:09 drh Exp $
           15  +*/
           16  +#include "sqliteInt.h"
           17  +#include "tcl.h"
           18  +#if defined(OS_UNIX) && defined(THREADSAFE) && THREADSAFE==1
           19  +#include <stdlib.h>
           20  +#include <string.h>
           21  +#include <pthread.h>
           22  +#include <sched.h>
           23  +#include <ctype.h>
           24  +
           25  +/*
           26  +** Each thread is controlled by an instance of the following
           27  +** structure.
           28  +*/
           29  +typedef struct Thread Thread;
           30  +struct Thread {
           31  +  /* The first group of fields are writable by the master and read-only
           32  +  ** to the thread. */
           33  +  char *zFilename;       /* Name of database file */
           34  +  void (*xOp)(Thread*);  /* next operation to do */
           35  +  char *zArg;            /* argument usable by xOp */
           36  +  int opnum;             /* Operation number */
           37  +  int busy;              /* True if this thread is in use */
           38  +
           39  +  /* The next group of fields are writable by the thread but read-only to the
           40  +  ** master. */
           41  +  int completed;        /* Number of operations completed */
           42  +  sqlite *db;           /* Open database */
           43  +  sqlite_vm *vm;        /* Pending operation */
           44  +  char *zErr;           /* operation error */
           45  +  char *zStaticErr;     /* Static error message */
           46  +  int rc;               /* operation return code */
           47  +  int argc;             /* number of columns in result */
           48  +  const char **argv;    /* result columns */
           49  +  const char **colv;    /* result column names */
           50  +};
           51  +
           52  +/*
           53  +** There can be as many as 26 threads running at once.  Each is named
           54  +** by a capital letter: A, B, C, ..., Y, Z.
           55  +*/
           56  +#define N_THREAD 26
           57  +static Thread threadset[N_THREAD];
           58  +
           59  +
           60  +/*
           61  +** The main loop for a thread.  Threads use busy waiting. 
           62  +*/
           63  +static void *thread_main(void *pArg){
           64  +  Thread *p = (Thread*)pArg;
           65  +  if( p->db ){
           66  +    sqlite_close(p->db);
           67  +  }
           68  +  p->db = sqlite_open(p->zFilename, 0, &p->zErr);
           69  +  p->vm = 0;
           70  +  p->completed = 1;
           71  +  while( p->opnum<=p->completed ) sched_yield();
           72  +  while( p->xOp ){
           73  +    if( p->zErr && p->zErr!=p->zStaticErr ){
           74  +      sqlite_freemem(p->zErr);
           75  +      p->zErr = 0;
           76  +    }
           77  +    (*p->xOp)(p);
           78  +    p->completed++;
           79  +    while( p->opnum<=p->completed ) sched_yield();
           80  +  }
           81  +  if( p->vm ){
           82  +    sqlite_finalize(p->vm, 0);
           83  +    p->vm = 0;
           84  +  }
           85  +  if( p->db ){
           86  +    sqlite_close(p->db);
           87  +    p->db = 0;
           88  +  }
           89  +  if( p->zErr && p->zErr!=p->zStaticErr ){
           90  +    sqlite_freemem(p->zErr);
           91  +    p->zErr = 0;
           92  +  }
           93  +  p->completed++;
           94  +  return 0;
           95  +}
           96  +
           97  +/*
           98  +** Get a thread ID which is an upper case letter.  Return the index.
           99  +** If the argument is not a valid thread ID put an error message in
          100  +** the interpreter and return -1.
          101  +*/
          102  +static int parse_thread_id(Tcl_Interp *interp, const char *zArg){
          103  +  if( zArg==0 || zArg[0]==0 || zArg[1]!=0 || !isupper(zArg[0]) ){
          104  +    Tcl_AppendResult(interp, "thread ID must be an upper case letter", 0);
          105  +    return -1;
          106  +  }
          107  +  return zArg[0] - 'A';
          108  +}
          109  +
          110  +/*
          111  +** Usage:    thread_create NAME  FILENAME
          112  +**
          113  +** NAME should be an upper case letter.  Start the thread running with
          114  +** an open connection to the given database.
          115  +*/
          116  +static int tcl_thread_create(
          117  +  void *NotUsed,
          118  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          119  +  int argc,              /* Number of arguments */
          120  +  const char **argv      /* Text of each argument */
          121  +){
          122  +  int i;
          123  +  pthread_t x;
          124  +  int rc;
          125  +
          126  +  if( argc!=3 ){
          127  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          128  +       " ID FILENAME", 0);
          129  +    return TCL_ERROR;
          130  +  }
          131  +  i = parse_thread_id(interp, argv[1]);
          132  +  if( i<0 ) return TCL_ERROR;
          133  +  if( threadset[i].busy ){
          134  +    Tcl_AppendResult(interp, "thread ", argv[1], " is already running", 0);
          135  +    return TCL_ERROR;
          136  +  }
          137  +  threadset[i].busy = 1;
          138  +  sqliteFree(threadset[i].zFilename);
          139  +  threadset[i].zFilename = sqliteStrDup(argv[2]);
          140  +  threadset[i].opnum = 1;
          141  +  rc = pthread_create(&x, 0, thread_main, &threadset[i]);
          142  +  if( rc ){
          143  +    Tcl_AppendResult(interp, "failed to create the thread", 0);
          144  +    sqliteFree(threadset[i].zFilename);
          145  +    threadset[i].busy = 0;
          146  +    return TCL_ERROR;
          147  +  }
          148  +  pthread_detach(x);
          149  +  return TCL_OK;
          150  +}
          151  +
          152  +/*
          153  +** Wait for a thread to reach its idle state.
          154  +*/
          155  +static void thread_wait(Thread *p){
          156  +  while( p->opnum>p->completed ) sched_yield();
          157  +}
          158  +
          159  +/*
          160  +** Usage:  thread_wait ID
          161  +**
          162  +** Wait on thread ID to reach its idle state.
          163  +*/
          164  +static int tcl_thread_wait(
          165  +  void *NotUsed,
          166  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          167  +  int argc,              /* Number of arguments */
          168  +  const char **argv      /* Text of each argument */
          169  +){
          170  +  int i;
          171  +
          172  +  if( argc!=2 ){
          173  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          174  +       " ID", 0);
          175  +    return TCL_ERROR;
          176  +  }
          177  +  i = parse_thread_id(interp, argv[1]);
          178  +  if( i<0 ) return TCL_ERROR;
          179  +  if( !threadset[i].busy ){
          180  +    Tcl_AppendResult(interp, "no such thread", 0);
          181  +    return TCL_ERROR;
          182  +  }
          183  +  thread_wait(&threadset[i]);
          184  +  return TCL_OK;
          185  +}
          186  +
          187  +/*
          188  +** Stop a thread.
          189  +*/
          190  +static void stop_thread(Thread *p){
          191  +  thread_wait(p);
          192  +  p->xOp = 0;
          193  +  p->opnum++;
          194  +  thread_wait(p);
          195  +  sqliteFree(p->zArg);
          196  +  p->zArg = 0;
          197  +  sqliteFree(p->zFilename);
          198  +  p->zFilename = 0;
          199  +  p->busy = 0;
          200  +}
          201  +
          202  +/*
          203  +** Usage:  thread_halt ID
          204  +**
          205  +** Cause a thread to shut itself down.  Wait for the shutdown to be
          206  +** completed.  If ID is "*" then stop all threads.
          207  +*/
          208  +static int tcl_thread_halt(
          209  +  void *NotUsed,
          210  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          211  +  int argc,              /* Number of arguments */
          212  +  const char **argv      /* Text of each argument */
          213  +){
          214  +  int i;
          215  +
          216  +  if( argc!=2 ){
          217  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          218  +       " ID", 0);
          219  +    return TCL_ERROR;
          220  +  }
          221  +  if( argv[1][0]=='*' && argv[1][1]==0 ){
          222  +    for(i=0; i<N_THREAD; i++){
          223  +      if( threadset[i].busy ) stop_thread(&threadset[i]);
          224  +    }
          225  +  }else{
          226  +    i = parse_thread_id(interp, argv[1]);
          227  +    if( i<0 ) return TCL_ERROR;
          228  +    if( !threadset[i].busy ){
          229  +      Tcl_AppendResult(interp, "no such thread", 0);
          230  +      return TCL_ERROR;
          231  +    }
          232  +    stop_thread(&threadset[i]);
          233  +  }
          234  +  return TCL_OK;
          235  +}
          236  +
          237  +/*
          238  +** Usage: thread_argc  ID
          239  +**
          240  +** Wait on the most recent thread_step to complete, then return the
          241  +** number of columns in the result set.
          242  +*/
          243  +static int tcl_thread_argc(
          244  +  void *NotUsed,
          245  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          246  +  int argc,              /* Number of arguments */
          247  +  const char **argv      /* Text of each argument */
          248  +){
          249  +  int i;
          250  +  char zBuf[100];
          251  +
          252  +  if( argc!=2 ){
          253  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          254  +       " ID", 0);
          255  +    return TCL_ERROR;
          256  +  }
          257  +  i = parse_thread_id(interp, argv[1]);
          258  +  if( i<0 ) return TCL_ERROR;
          259  +  if( !threadset[i].busy ){
          260  +    Tcl_AppendResult(interp, "no such thread", 0);
          261  +    return TCL_ERROR;
          262  +  }
          263  +  thread_wait(&threadset[i]);
          264  +  sprintf(zBuf, "%d", threadset[i].argc);
          265  +  Tcl_AppendResult(interp, zBuf, 0);
          266  +  return TCL_OK;
          267  +}
          268  +
          269  +/*
          270  +** Usage: thread_argv  ID   N
          271  +**
          272  +** Wait on the most recent thread_step to complete, then return the
          273  +** value of the N-th columns in the result set.
          274  +*/
          275  +static int tcl_thread_argv(
          276  +  void *NotUsed,
          277  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          278  +  int argc,              /* Number of arguments */
          279  +  const char **argv      /* Text of each argument */
          280  +){
          281  +  int i;
          282  +  int n;
          283  +
          284  +  if( argc!=3 ){
          285  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          286  +       " ID N", 0);
          287  +    return TCL_ERROR;
          288  +  }
          289  +  i = parse_thread_id(interp, argv[1]);
          290  +  if( i<0 ) return TCL_ERROR;
          291  +  if( !threadset[i].busy ){
          292  +    Tcl_AppendResult(interp, "no such thread", 0);
          293  +    return TCL_ERROR;
          294  +  }
          295  +  if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
          296  +  thread_wait(&threadset[i]);
          297  +  if( n<0 || n>=threadset[i].argc ){
          298  +    Tcl_AppendResult(interp, "column number out of range", 0);
          299  +    return TCL_ERROR;
          300  +  }
          301  +  Tcl_AppendResult(interp, threadset[i].argv[n], 0);
          302  +  return TCL_OK;
          303  +}
          304  +
          305  +/*
          306  +** Usage: thread_colname  ID   N
          307  +**
          308  +** Wait on the most recent thread_step to complete, then return the
          309  +** name of the N-th columns in the result set.
          310  +*/
          311  +static int tcl_thread_colname(
          312  +  void *NotUsed,
          313  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          314  +  int argc,              /* Number of arguments */
          315  +  const char **argv      /* Text of each argument */
          316  +){
          317  +  int i;
          318  +  int n;
          319  +
          320  +  if( argc!=3 ){
          321  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          322  +       " ID N", 0);
          323  +    return TCL_ERROR;
          324  +  }
          325  +  i = parse_thread_id(interp, argv[1]);
          326  +  if( i<0 ) return TCL_ERROR;
          327  +  if( !threadset[i].busy ){
          328  +    Tcl_AppendResult(interp, "no such thread", 0);
          329  +    return TCL_ERROR;
          330  +  }
          331  +  if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
          332  +  thread_wait(&threadset[i]);
          333  +  if( n<0 || n>=threadset[i].argc ){
          334  +    Tcl_AppendResult(interp, "column number out of range", 0);
          335  +    return TCL_ERROR;
          336  +  }
          337  +  Tcl_AppendResult(interp, threadset[i].colv[n], 0);
          338  +  return TCL_OK;
          339  +}
          340  +
          341  +/*
          342  +** Usage: thread_result  ID
          343  +**
          344  +** Wait on the most recent operation to complete, then return the
          345  +** result code from that operation.
          346  +*/
          347  +static int tcl_thread_result(
          348  +  void *NotUsed,
          349  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          350  +  int argc,              /* Number of arguments */
          351  +  const char **argv      /* Text of each argument */
          352  +){
          353  +  int i;
          354  +  const char *zName;
          355  +
          356  +  if( argc!=2 ){
          357  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          358  +       " ID", 0);
          359  +    return TCL_ERROR;
          360  +  }
          361  +  i = parse_thread_id(interp, argv[1]);
          362  +  if( i<0 ) return TCL_ERROR;
          363  +  if( !threadset[i].busy ){
          364  +    Tcl_AppendResult(interp, "no such thread", 0);
          365  +    return TCL_ERROR;
          366  +  }
          367  +  thread_wait(&threadset[i]);
          368  +  switch( threadset[i].rc ){
          369  +    case SQLITE_OK:         zName = "SQLITE_OK";          break;
          370  +    case SQLITE_ERROR:      zName = "SQLITE_ERROR";       break;
          371  +    case SQLITE_INTERNAL:   zName = "SQLITE_INTERNAL";    break;
          372  +    case SQLITE_PERM:       zName = "SQLITE_PERM";        break;
          373  +    case SQLITE_ABORT:      zName = "SQLITE_ABORT";       break;
          374  +    case SQLITE_BUSY:       zName = "SQLITE_BUSY";        break;
          375  +    case SQLITE_LOCKED:     zName = "SQLITE_LOCKED";      break;
          376  +    case SQLITE_NOMEM:      zName = "SQLITE_NOMEM";       break;
          377  +    case SQLITE_READONLY:   zName = "SQLITE_READONLY";    break;
          378  +    case SQLITE_INTERRUPT:  zName = "SQLITE_INTERRUPT";   break;
          379  +    case SQLITE_IOERR:      zName = "SQLITE_IOERR";       break;
          380  +    case SQLITE_CORRUPT:    zName = "SQLITE_CORRUPT";     break;
          381  +    case SQLITE_NOTFOUND:   zName = "SQLITE_NOTFOUND";    break;
          382  +    case SQLITE_FULL:       zName = "SQLITE_FULL";        break;
          383  +    case SQLITE_CANTOPEN:   zName = "SQLITE_CANTOPEN";    break;
          384  +    case SQLITE_PROTOCOL:   zName = "SQLITE_PROTOCOL";    break;
          385  +    case SQLITE_EMPTY:      zName = "SQLITE_EMPTY";       break;
          386  +    case SQLITE_SCHEMA:     zName = "SQLITE_SCHEMA";      break;
          387  +    case SQLITE_TOOBIG:     zName = "SQLITE_TOOBIG";      break;
          388  +    case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT";  break;
          389  +    case SQLITE_MISMATCH:   zName = "SQLITE_MISMATCH";    break;
          390  +    case SQLITE_MISUSE:     zName = "SQLITE_MISUSE";      break;
          391  +    case SQLITE_NOLFS:      zName = "SQLITE_NOLFS";       break;
          392  +    case SQLITE_AUTH:       zName = "SQLITE_AUTH";        break;
          393  +    case SQLITE_FORMAT:     zName = "SQLITE_FORMAT";      break;
          394  +    case SQLITE_RANGE:      zName = "SQLITE_RANGE";       break;
          395  +    case SQLITE_ROW:        zName = "SQLITE_ROW";         break;
          396  +    case SQLITE_DONE:       zName = "SQLITE_DONE";        break;
          397  +    default:                zName = "SQLITE_Unknown";     break;
          398  +  }
          399  +  Tcl_AppendResult(interp, zName, 0);
          400  +  return TCL_OK;
          401  +}
          402  +
          403  +/*
          404  +** Usage: thread_error  ID
          405  +**
          406  +** Wait on the most recent operation to complete, then return the
          407  +** error string.
          408  +*/
          409  +static int tcl_thread_error(
          410  +  void *NotUsed,
          411  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          412  +  int argc,              /* Number of arguments */
          413  +  const char **argv      /* Text of each argument */
          414  +){
          415  +  int i;
          416  +
          417  +  if( argc!=2 ){
          418  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          419  +       " ID", 0);
          420  +    return TCL_ERROR;
          421  +  }
          422  +  i = parse_thread_id(interp, argv[1]);
          423  +  if( i<0 ) return TCL_ERROR;
          424  +  if( !threadset[i].busy ){
          425  +    Tcl_AppendResult(interp, "no such thread", 0);
          426  +    return TCL_ERROR;
          427  +  }
          428  +  thread_wait(&threadset[i]);
          429  +  Tcl_AppendResult(interp, threadset[i].zErr, 0);
          430  +  return TCL_OK;
          431  +}
          432  +
          433  +/*
          434  +** This procedure runs in the thread to compile an SQL statement.
          435  +*/
          436  +static void do_compile(Thread *p){
          437  +  if( p->db==0 ){
          438  +    p->zErr = p->zStaticErr = "no database is open";
          439  +    p->rc = SQLITE_ERROR;
          440  +    return;
          441  +  }
          442  +  if( p->vm ){
          443  +    sqlite_finalize(p->vm, 0);
          444  +    p->vm = 0;
          445  +  }
          446  +  p->rc = sqlite_compile(p->db, p->zArg, 0, &p->vm, &p->zErr);
          447  +}
          448  +
          449  +/*
          450  +** Usage: thread_compile ID SQL
          451  +**
          452  +** Compile a new virtual machine.
          453  +*/
          454  +static int tcl_thread_compile(
          455  +  void *NotUsed,
          456  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          457  +  int argc,              /* Number of arguments */
          458  +  const char **argv      /* Text of each argument */
          459  +){
          460  +  int i;
          461  +  if( argc!=3 ){
          462  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          463  +       " ID SQL", 0);
          464  +    return TCL_ERROR;
          465  +  }
          466  +  i = parse_thread_id(interp, argv[1]);
          467  +  if( i<0 ) return TCL_ERROR;
          468  +  if( !threadset[i].busy ){
          469  +    Tcl_AppendResult(interp, "no such thread", 0);
          470  +    return TCL_ERROR;
          471  +  }
          472  +  thread_wait(&threadset[i]);
          473  +  threadset[i].xOp = do_compile;
          474  +  sqliteFree(threadset[i].zArg);
          475  +  threadset[i].zArg = sqliteStrDup(argv[2]);
          476  +  threadset[i].opnum++;
          477  +  return TCL_OK;
          478  +}
          479  +
          480  +/*
          481  +** This procedure runs in the thread to step the virtual machine.
          482  +*/
          483  +static void do_step(Thread *p){
          484  +  if( p->vm==0 ){
          485  +    p->zErr = p->zStaticErr = "no virtual machine available";
          486  +    p->rc = SQLITE_ERROR;
          487  +    return;
          488  +  }
          489  +  p->rc = sqlite_step(p->vm, &p->argc, &p->argv, &p->colv);
          490  +}
          491  +
          492  +/*
          493  +** Usage: thread_step ID
          494  +**
          495  +** Advance the virtual machine by one step
          496  +*/
          497  +static int tcl_thread_step(
          498  +  void *NotUsed,
          499  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          500  +  int argc,              /* Number of arguments */
          501  +  const char **argv      /* Text of each argument */
          502  +){
          503  +  int i;
          504  +  if( argc!=2 ){
          505  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          506  +       " IDL", 0);
          507  +    return TCL_ERROR;
          508  +  }
          509  +  i = parse_thread_id(interp, argv[1]);
          510  +  if( i<0 ) return TCL_ERROR;
          511  +  if( !threadset[i].busy ){
          512  +    Tcl_AppendResult(interp, "no such thread", 0);
          513  +    return TCL_ERROR;
          514  +  }
          515  +  thread_wait(&threadset[i]);
          516  +  threadset[i].xOp = do_step;
          517  +  threadset[i].opnum++;
          518  +  return TCL_OK;
          519  +}
          520  +
          521  +/*
          522  +** This procedure runs in the thread to finalize a virtual machine.
          523  +*/
          524  +static void do_finalize(Thread *p){
          525  +  if( p->vm==0 ){
          526  +    p->zErr = p->zStaticErr = "no virtual machine available";
          527  +    p->rc = SQLITE_ERROR;
          528  +    return;
          529  +  }
          530  +  p->rc = sqlite_finalize(p->vm, &p->zErr);
          531  +  p->vm = 0;
          532  +}
          533  +
          534  +/*
          535  +** Usage: thread_finalize ID
          536  +**
          537  +** Finalize the virtual machine.
          538  +*/
          539  +static int tcl_thread_finalize(
          540  +  void *NotUsed,
          541  +  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
          542  +  int argc,              /* Number of arguments */
          543  +  const char **argv      /* Text of each argument */
          544  +){
          545  +  int i;
          546  +  if( argc!=2 ){
          547  +    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          548  +       " IDL", 0);
          549  +    return TCL_ERROR;
          550  +  }
          551  +  i = parse_thread_id(interp, argv[1]);
          552  +  if( i<0 ) return TCL_ERROR;
          553  +  if( !threadset[i].busy ){
          554  +    Tcl_AppendResult(interp, "no such thread", 0);
          555  +    return TCL_ERROR;
          556  +  }
          557  +  thread_wait(&threadset[i]);
          558  +  threadset[i].xOp = do_finalize;
          559  +  sqliteFree(threadset[i].zArg);
          560  +  threadset[i].zArg = 0;
          561  +  threadset[i].opnum++;
          562  +  return TCL_OK;
          563  +}
          564  +
          565  +/*
          566  +** Register commands with the TCL interpreter.
          567  +*/
          568  +int Sqlitetest4_Init(Tcl_Interp *interp){
          569  +  static struct {
          570  +     char *zName;
          571  +     Tcl_CmdProc *xProc;
          572  +  } aCmd[] = {
          573  +     { "thread_create",     (Tcl_CmdProc*)tcl_thread_create     },
          574  +     { "thread_wait",       (Tcl_CmdProc*)tcl_thread_wait       },
          575  +     { "thread_halt",       (Tcl_CmdProc*)tcl_thread_halt       },
          576  +     { "thread_argc",       (Tcl_CmdProc*)tcl_thread_argc       },
          577  +     { "thread_argv",       (Tcl_CmdProc*)tcl_thread_argv       },
          578  +     { "thread_colname",    (Tcl_CmdProc*)tcl_thread_colname    },
          579  +     { "thread_result",     (Tcl_CmdProc*)tcl_thread_result     },
          580  +     { "thread_error",      (Tcl_CmdProc*)tcl_thread_error      },
          581  +     { "thread_compile",    (Tcl_CmdProc*)tcl_thread_compile    },
          582  +     { "thread_step",       (Tcl_CmdProc*)tcl_thread_step       },
          583  +     { "thread_finalize",   (Tcl_CmdProc*)tcl_thread_finalize   },
          584  +  };
          585  +  int i;
          586  +
          587  +  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
          588  +    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
          589  +  }
          590  +  return TCL_OK;
          591  +}
          592  +#else
          593  +int Sqlitetest4_Init(Tcl_Interp *interp){ return TCL_OK; }
          594  +#endif /* OS_UNIX */

Added test/thread1.test.

            1  +# 2003 December 18
            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  +# This file implements regression tests for SQLite library.  The
           12  +# focus of this script is multithreading behavior
           13  +#
           14  +# $Id: thread1.test,v 1.1 2003/12/19 02:52:09 drh Exp $
           15  +
           16  +
           17  +set testdir [file dirname $argv0]
           18  +source $testdir/tester.tcl
           19  +
           20  +# Skip this whole file if the thread testing code is not enabled
           21  +#
           22  +if {[llength [info command thread_step]]==0} {
           23  +  finish_test
           24  +  return
           25  +}
           26  +
           27  +# Create some data to work with
           28  +#
           29  +do_test thread1-1.1 {
           30  +  execsql {
           31  +    CREATE TABLE t1(a,b);
           32  +    INSERT INTO t1 VALUES(1,'abcdefgh');
           33  +    INSERT INTO t1 SELECT a+1, b||b FROM t1;
           34  +    INSERT INTO t1 SELECT a+2, b||b FROM t1;
           35  +    INSERT INTO t1 SELECT a+4, b||b FROM t1;
           36  +    SELECT count(*), max(length(b)) FROM t1;
           37  +  }
           38  +} {8 64}
           39  +
           40  +# Interleave two threads on read access.  Then make sure a third
           41  +# thread can write the database.
           42  +#
           43  +do_test thread1-1.2 {
           44  +  thread_create A test.db
           45  +  thread_create B test.db
           46  +  thread_create C test.db
           47  +  thread_compile A {SELECT a FROM t1}
           48  +  thread_step A
           49  +  thread_result A
           50  +} SQLITE_ROW
           51  +do_test thread1-1.3 {
           52  +  thread_argc A
           53  +} 1
           54  +do_test thread1-1.4 {
           55  +  thread_argv A 0
           56  +} 1
           57  +do_test thread1-1.5 {
           58  +  thread_compile B {SELECT b FROM t1}
           59  +  thread_step B
           60  +  thread_result B
           61  +} SQLITE_ROW
           62  +do_test thread1-1.6 {
           63  +  thread_argc B
           64  +} 1
           65  +do_test thread1-1.7 {
           66  +  thread_argv B 0
           67  +} abcdefgh
           68  +do_test thread1-1.8 {
           69  +  thread_finalize A
           70  +  thread_result A
           71  +} SQLITE_OK
           72  +do_test thread1-1.9 {
           73  +  thread_finalize B
           74  +  thread_result B
           75  +} SQLITE_OK
           76  +do_test thread1-1.10 {
           77  +  thread_compile C {CREATE TABLE t2(x,y)}
           78  +  thread_step C
           79  +  thread_result C
           80  +} SQLITE_DONE
           81  +do_test thread1-1.11 {
           82  +  thread_finalize C
           83  +  thread_result C
           84  +} SQLITE_OK
           85  +
           86  +
           87  +thread_halt *   
           88  +finish_test