AryaWu/sqlite
0
1/*2** 2003 December 183**4** The author disclaims copyright to this source code. In place of5** 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 SQLite library in a multithreaded environment.13*/14#include "sqliteInt.h"15#include "tclsqlite.h"16#if SQLITE_OS_UNIX && SQLITE_THREADSAFE17#include <stdlib.h>18#include <string.h>19#include <pthread.h>20#include <sched.h>21#include <ctype.h>22 23extern const char *sqlite3ErrName(int);24 25/*26** Each thread is controlled by an instance of the following27** structure.28*/29typedef struct Thread Thread;30struct Thread {31 /* The first group of fields are writable by the leader and read-only32 ** 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 the40 ** leader. */41 int completed; /* Number of operations completed */42 sqlite3 *db; /* Open database */43 sqlite3_stmt *pStmt; /* 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[100]; /* result columns */49 const char *colv[100]; /* result column names */50};51 52/*53** There can be as many as 26 threads running at once. Each is named54** by a capital letter: A, B, C, ..., Y, Z.55*/56#define N_THREAD 2657static Thread threadset[N_THREAD];58 59static void test_barrier(){60 sqlite3_mutex *pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_APP1);61 sqlite3_mutex_enter(pMutex);62 sqlite3_mutex_leave(pMutex);63}64 65/*66** The main loop for a thread. Threads use busy waiting. 67*/68static void *test_thread_main(void *pArg){69 Thread *p = (Thread*)pArg;70 if( p->db ){71 sqlite3_close(p->db);72 }73 sqlite3_open(p->zFilename, &p->db);74 if( SQLITE_OK!=sqlite3_errcode(p->db) ){75 p->zErr = strdup(sqlite3_errmsg(p->db));76 sqlite3_close(p->db);77 p->db = 0;78 }79 p->pStmt = 0;80 test_barrier();81 p->completed = 1;82 while( p->opnum<=p->completed ) sched_yield();83 test_barrier();84 while( p->xOp ){85 if( p->zErr && p->zErr!=p->zStaticErr ){86 sqlite3_free(p->zErr);87 p->zErr = 0;88 }89 (*p->xOp)(p);90 test_barrier();91 p->completed++;92 while( p->opnum<=p->completed ) sched_yield();93 test_barrier();94 }95 if( p->pStmt ){96 sqlite3_finalize(p->pStmt);97 p->pStmt = 0;98 }99 if( p->db ){100 sqlite3_close(p->db);101 p->db = 0;102 }103 if( p->zErr && p->zErr!=p->zStaticErr ){104 sqlite3_free(p->zErr);105 p->zErr = 0;106 }107 test_barrier();108 p->completed++;109#ifndef SQLITE_OMIT_DEPRECATED110 sqlite3_thread_cleanup();111#endif112 return 0;113}114 115/*116** Get a thread ID which is an upper case letter. Return the index.117** If the argument is not a valid thread ID put an error message in118** the interpreter and return -1.119*/120static int parse_thread_id(Tcl_Interp *interp, const char *zArg){121 if( zArg==0 || zArg[0]==0 || zArg[1]!=0 || !isupper((unsigned char)zArg[0]) ){122 Tcl_AppendResult(interp, "thread ID must be an upper case letter", NULL);123 return -1;124 }125 return zArg[0] - 'A';126}127 128/*129** Usage: thread_create NAME FILENAME130**131** NAME should be an upper case letter. Start the thread running with132** an open connection to the given database.133*/134static int SQLITE_TCLAPI tcl_thread_create(135 void *NotUsed,136 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */137 int argc, /* Number of arguments */138 const char **argv /* Text of each argument */139){140 int i;141 pthread_t x;142 int rc;143 144 if( argc!=3 ){145 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],146 " ID FILENAME", NULL);147 return TCL_ERROR;148 }149 i = parse_thread_id(interp, argv[1]);150 if( i<0 ) return TCL_ERROR;151 if( threadset[i].busy ){152 Tcl_AppendResult(interp, "thread ", argv[1], " is already running", NULL);153 return TCL_ERROR;154 }155 threadset[i].busy = 1;156 sqlite3_free(threadset[i].zFilename);157 threadset[i].zFilename = sqlite3_mprintf("%s", argv[2]);158 threadset[i].opnum = 1;159 threadset[i].completed = 0;160 rc = pthread_create(&x, 0, test_thread_main, &threadset[i]);161 if( rc ){162 Tcl_AppendResult(interp, "failed to create the thread", NULL);163 sqlite3_free(threadset[i].zFilename);164 threadset[i].busy = 0;165 return TCL_ERROR;166 }167 pthread_detach(x);168 return TCL_OK;169}170 171/*172** Wait for a thread to reach its idle state.173*/174static void test_thread_wait(Thread *p){175 test_barrier();176 while( p->opnum>p->completed ) sched_yield();177 test_barrier();178}179 180/*181** Usage: thread_wait ID182**183** Wait on thread ID to reach its idle state.184*/185static int SQLITE_TCLAPI tcl_thread_wait(186 void *NotUsed,187 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */188 int argc, /* Number of arguments */189 const char **argv /* Text of each argument */190){191 int i;192 193 if( argc!=2 ){194 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],195 " ID", NULL);196 return TCL_ERROR;197 }198 i = parse_thread_id(interp, argv[1]);199 if( i<0 ) return TCL_ERROR;200 if( !threadset[i].busy ){201 Tcl_AppendResult(interp, "no such thread", NULL);202 return TCL_ERROR;203 }204 test_thread_wait(&threadset[i]);205 return TCL_OK;206}207 208/*209** Stop a thread.210*/211static void test_stop_thread(Thread *p){212 test_thread_wait(p);213 p->xOp = 0;214 p->opnum++;215 test_thread_wait(p);216 sqlite3_free(p->zArg);217 p->zArg = 0;218 sqlite3_free(p->zFilename);219 p->zFilename = 0;220 p->busy = 0;221}222 223/*224** Usage: thread_halt ID225**226** Cause a thread to shut itself down. Wait for the shutdown to be227** completed. If ID is "*" then stop all threads.228*/229static int SQLITE_TCLAPI tcl_thread_halt(230 void *NotUsed,231 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */232 int argc, /* Number of arguments */233 const char **argv /* Text of each argument */234){235 int i;236 237 if( argc!=2 ){238 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],239 " ID", NULL);240 return TCL_ERROR;241 }242 if( argv[1][0]=='*' && argv[1][1]==0 ){243 for(i=0; i<N_THREAD; i++){244 if( threadset[i].busy ) test_stop_thread(&threadset[i]);245 }246 }else{247 i = parse_thread_id(interp, argv[1]);248 if( i<0 ) return TCL_ERROR;249 if( !threadset[i].busy ){250 Tcl_AppendResult(interp, "no such thread", NULL);251 return TCL_ERROR;252 }253 test_stop_thread(&threadset[i]);254 }255 return TCL_OK;256}257 258/*259** Usage: thread_argc ID260**261** Wait on the most recent thread_step to complete, then return the262** number of columns in the result set.263*/264static int SQLITE_TCLAPI tcl_thread_argc(265 void *NotUsed,266 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */267 int argc, /* Number of arguments */268 const char **argv /* Text of each argument */269){270 int i;271 char zBuf[100];272 273 if( argc!=2 ){274 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],275 " ID", NULL);276 return TCL_ERROR;277 }278 i = parse_thread_id(interp, argv[1]);279 if( i<0 ) return TCL_ERROR;280 if( !threadset[i].busy ){281 Tcl_AppendResult(interp, "no such thread", NULL);282 return TCL_ERROR;283 }284 test_thread_wait(&threadset[i]);285 sqlite3_snprintf(sizeof(zBuf), zBuf, "%d", threadset[i].argc);286 Tcl_AppendResult(interp, zBuf, NULL);287 return TCL_OK;288}289 290/*291** Usage: thread_argv ID N292**293** Wait on the most recent thread_step to complete, then return the294** value of the N-th columns in the result set.295*/296static int SQLITE_TCLAPI tcl_thread_argv(297 void *NotUsed,298 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */299 int argc, /* Number of arguments */300 const char **argv /* Text of each argument */301){302 int i;303 int n;304 305 if( argc!=3 ){306 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],307 " ID N", NULL);308 return TCL_ERROR;309 }310 i = parse_thread_id(interp, argv[1]);311 if( i<0 ) return TCL_ERROR;312 if( !threadset[i].busy ){313 Tcl_AppendResult(interp, "no such thread", NULL);314 return TCL_ERROR;315 }316 if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;317 test_thread_wait(&threadset[i]);318 if( n<0 || n>=threadset[i].argc ){319 Tcl_AppendResult(interp, "column number out of range", NULL);320 return TCL_ERROR;321 }322 Tcl_AppendResult(interp, threadset[i].argv[n], NULL);323 return TCL_OK;324}325 326/*327** Usage: thread_colname ID N328**329** Wait on the most recent thread_step to complete, then return the330** name of the N-th columns in the result set.331*/332static int SQLITE_TCLAPI tcl_thread_colname(333 void *NotUsed,334 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */335 int argc, /* Number of arguments */336 const char **argv /* Text of each argument */337){338 int i;339 int n;340 341 if( argc!=3 ){342 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],343 " ID N", NULL);344 return TCL_ERROR;345 }346 i = parse_thread_id(interp, argv[1]);347 if( i<0 ) return TCL_ERROR;348 if( !threadset[i].busy ){349 Tcl_AppendResult(interp, "no such thread", NULL);350 return TCL_ERROR;351 }352 if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;353 test_thread_wait(&threadset[i]);354 if( n<0 || n>=threadset[i].argc ){355 Tcl_AppendResult(interp, "column number out of range", NULL);356 return TCL_ERROR;357 }358 Tcl_AppendResult(interp, threadset[i].colv[n], NULL);359 return TCL_OK;360}361 362/*363** Usage: thread_result ID364**365** Wait on the most recent operation to complete, then return the366** result code from that operation.367*/368static int SQLITE_TCLAPI tcl_thread_result(369 void *NotUsed,370 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */371 int argc, /* Number of arguments */372 const char **argv /* Text of each argument */373){374 int i;375 const char *zName;376 377 if( argc!=2 ){378 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],379 " ID", NULL);380 return TCL_ERROR;381 }382 i = parse_thread_id(interp, argv[1]);383 if( i<0 ) return TCL_ERROR;384 if( !threadset[i].busy ){385 Tcl_AppendResult(interp, "no such thread", NULL);386 return TCL_ERROR;387 }388 test_thread_wait(&threadset[i]);389 zName = sqlite3ErrName(threadset[i].rc);390 Tcl_AppendResult(interp, zName, NULL);391 return TCL_OK;392}393 394/*395** Usage: thread_error ID396**397** Wait on the most recent operation to complete, then return the398** error string.399*/400static int SQLITE_TCLAPI tcl_thread_error(401 void *NotUsed,402 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */403 int argc, /* Number of arguments */404 const char **argv /* Text of each argument */405){406 int i;407 408 if( argc!=2 ){409 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],410 " ID", NULL);411 return TCL_ERROR;412 }413 i = parse_thread_id(interp, argv[1]);414 if( i<0 ) return TCL_ERROR;415 if( !threadset[i].busy ){416 Tcl_AppendResult(interp, "no such thread", NULL);417 return TCL_ERROR;418 }419 test_thread_wait(&threadset[i]);420 Tcl_AppendResult(interp, threadset[i].zErr, NULL);421 return TCL_OK;422}423 424/*425** This procedure runs in the thread to compile an SQL statement.426*/427static void do_compile(Thread *p){428 if( p->db==0 ){429 p->zErr = p->zStaticErr = "no database is open";430 p->rc = SQLITE_ERROR;431 return;432 }433 if( p->pStmt ){434 sqlite3_finalize(p->pStmt);435 p->pStmt = 0;436 }437 p->rc = sqlite3_prepare(p->db, p->zArg, -1, &p->pStmt, 0);438}439 440/*441** Usage: thread_compile ID SQL442**443** Compile a new virtual machine.444*/445static int SQLITE_TCLAPI tcl_thread_compile(446 void *NotUsed,447 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */448 int argc, /* Number of arguments */449 const char **argv /* Text of each argument */450){451 int i;452 if( argc!=3 ){453 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],454 " ID SQL", NULL);455 return TCL_ERROR;456 }457 i = parse_thread_id(interp, argv[1]);458 if( i<0 ) return TCL_ERROR;459 if( !threadset[i].busy ){460 Tcl_AppendResult(interp, "no such thread", NULL);461 return TCL_ERROR;462 }463 test_thread_wait(&threadset[i]);464 threadset[i].xOp = do_compile;465 sqlite3_free(threadset[i].zArg);466 threadset[i].zArg = sqlite3_mprintf("%s", argv[2]);467 test_barrier();468 threadset[i].opnum++;469 return TCL_OK;470}471 472/*473** This procedure runs in the thread to step the virtual machine.474*/475static void do_step(Thread *p){476 int i;477 if( p->pStmt==0 ){478 p->zErr = p->zStaticErr = "no virtual machine available";479 p->rc = SQLITE_ERROR;480 return;481 }482 p->rc = sqlite3_step(p->pStmt);483 if( p->rc==SQLITE_ROW ){484 p->argc = sqlite3_column_count(p->pStmt);485 for(i=0; i<sqlite3_data_count(p->pStmt); i++){486 p->argv[i] = (char*)sqlite3_column_text(p->pStmt, i);487 }488 for(i=0; i<p->argc; i++){489 p->colv[i] = sqlite3_column_name(p->pStmt, i);490 }491 }492}493 494/*495** Usage: thread_step ID496**497** Advance the virtual machine by one step498*/499static int SQLITE_TCLAPI tcl_thread_step(500 void *NotUsed,501 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */502 int argc, /* Number of arguments */503 const char **argv /* Text of each argument */504){505 int i;506 if( argc!=2 ){507 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],508 " IDL", NULL);509 return TCL_ERROR;510 }511 i = parse_thread_id(interp, argv[1]);512 if( i<0 ) return TCL_ERROR;513 if( !threadset[i].busy ){514 Tcl_AppendResult(interp, "no such thread", NULL);515 return TCL_ERROR;516 }517 test_thread_wait(&threadset[i]);518 threadset[i].xOp = do_step;519 test_barrier();520 threadset[i].opnum++;521 return TCL_OK;522}523 524/*525** This procedure runs in the thread to finalize a virtual machine.526*/527static void do_finalize(Thread *p){528 if( p->pStmt==0 ){529 p->zErr = p->zStaticErr = "no virtual machine available";530 p->rc = SQLITE_ERROR;531 return;532 }533 p->rc = sqlite3_finalize(p->pStmt);534 p->pStmt = 0;535}536 537/*538** Usage: thread_finalize ID539**540** Finalize the virtual machine.541*/542static int SQLITE_TCLAPI tcl_thread_finalize(543 void *NotUsed,544 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */545 int argc, /* Number of arguments */546 const char **argv /* Text of each argument */547){548 int i;549 if( argc!=2 ){550 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],551 " IDL", NULL);552 return TCL_ERROR;553 }554 i = parse_thread_id(interp, argv[1]);555 if( i<0 ) return TCL_ERROR;556 if( !threadset[i].busy ){557 Tcl_AppendResult(interp, "no such thread", NULL);558 return TCL_ERROR;559 }560 test_thread_wait(&threadset[i]);561 threadset[i].xOp = do_finalize;562 sqlite3_free(threadset[i].zArg);563 threadset[i].zArg = 0;564 test_barrier();565 threadset[i].opnum++;566 return TCL_OK;567}568 569/*570** Usage: thread_swap ID ID571**572** Interchange the sqlite* pointer between two threads.573*/574static int SQLITE_TCLAPI 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 sqlite3 *temp;582 if( argc!=3 ){583 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],584 " ID1 ID2", NULL);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", NULL);591 return TCL_ERROR;592 }593 test_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", NULL);598 return TCL_ERROR;599 }600 test_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** Usage: thread_db_get ID609**610** Return the database connection pointer for the given thread. Then611** remove the pointer from the thread itself. Afterwards, the thread612** can be stopped and the connection can be used by the main thread.613*/614static int SQLITE_TCLAPI tcl_thread_db_get(615 void *NotUsed,616 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */617 int argc, /* Number of arguments */618 const char **argv /* Text of each argument */619){620 int i;621 char zBuf[100];622 extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);623 if( argc!=2 ){624 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],625 " ID", NULL);626 return TCL_ERROR;627 }628 i = parse_thread_id(interp, argv[1]);629 if( i<0 ) return TCL_ERROR;630 if( !threadset[i].busy ){631 Tcl_AppendResult(interp, "no such thread", NULL);632 return TCL_ERROR;633 }634 test_thread_wait(&threadset[i]);635 sqlite3TestMakePointerStr(interp, zBuf, threadset[i].db);636 threadset[i].db = 0;637 Tcl_AppendResult(interp, zBuf, (char*)0);638 return TCL_OK;639}640 641/*642** Usage: thread_db_put ID DB643**644*/645static int SQLITE_TCLAPI tcl_thread_db_put(646 void *NotUsed,647 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */648 int argc, /* Number of arguments */649 const char **argv /* Text of each argument */650){651 int i;652 extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);653 extern void *sqlite3TestTextToPtr(const char *);654 if( argc!=3 ){655 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],656 " ID DB", NULL);657 return TCL_ERROR;658 }659 i = parse_thread_id(interp, argv[1]);660 if( i<0 ) return TCL_ERROR;661 if( !threadset[i].busy ){662 Tcl_AppendResult(interp, "no such thread", NULL);663 return TCL_ERROR;664 }665 test_thread_wait(&threadset[i]);666 assert( !threadset[i].db );667 threadset[i].db = (sqlite3*)sqlite3TestTextToPtr(argv[2]);668 return TCL_OK;669}670 671/*672** Usage: thread_stmt_get ID673**674** Return the database stmt pointer for the given thread. Then675** remove the pointer from the thread itself. 676*/677static int SQLITE_TCLAPI tcl_thread_stmt_get(678 void *NotUsed,679 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */680 int argc, /* Number of arguments */681 const char **argv /* Text of each argument */682){683 int i;684 char zBuf[100];685 extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);686 if( argc!=2 ){687 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],688 " ID", NULL);689 return TCL_ERROR;690 }691 i = parse_thread_id(interp, argv[1]);692 if( i<0 ) return TCL_ERROR;693 if( !threadset[i].busy ){694 Tcl_AppendResult(interp, "no such thread", NULL);695 return TCL_ERROR;696 }697 test_thread_wait(&threadset[i]);698 sqlite3TestMakePointerStr(interp, zBuf, threadset[i].pStmt);699 threadset[i].pStmt = 0;700 Tcl_AppendResult(interp, zBuf, (char*)0);701 return TCL_OK;702}703 704/*705** Register commands with the TCL interpreter.706*/707int Sqlitetest4_Init(Tcl_Interp *interp){708 static struct {709 char *zName;710 Tcl_CmdProc *xProc;711 } aCmd[] = {712 { "thread_create", (Tcl_CmdProc*)tcl_thread_create },713 { "thread_wait", (Tcl_CmdProc*)tcl_thread_wait },714 { "thread_halt", (Tcl_CmdProc*)tcl_thread_halt },715 { "thread_argc", (Tcl_CmdProc*)tcl_thread_argc },716 { "thread_argv", (Tcl_CmdProc*)tcl_thread_argv },717 { "thread_colname", (Tcl_CmdProc*)tcl_thread_colname },718 { "thread_result", (Tcl_CmdProc*)tcl_thread_result },719 { "thread_error", (Tcl_CmdProc*)tcl_thread_error },720 { "thread_compile", (Tcl_CmdProc*)tcl_thread_compile },721 { "thread_step", (Tcl_CmdProc*)tcl_thread_step },722 { "thread_finalize", (Tcl_CmdProc*)tcl_thread_finalize },723 { "thread_swap", (Tcl_CmdProc*)tcl_thread_swap },724 { "thread_db_get", (Tcl_CmdProc*)tcl_thread_db_get },725 { "thread_db_put", (Tcl_CmdProc*)tcl_thread_db_put },726 { "thread_stmt_get", (Tcl_CmdProc*)tcl_thread_stmt_get },727 };728 int i;729 730 for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){731 Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);732 }733 return TCL_OK;734}735#else736int Sqlitetest4_Init(Tcl_Interp *interp){ return TCL_OK; }737#endif /* SQLITE_OS_UNIX */738 