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