4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
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.
11 *************************************************************************
12 ** Code for testing the SQLite library in a multithreaded environment.
14 #include "sqliteInt.h"
15 #if defined(INCLUDE_SQLITE_TCL_H)
16 # include "sqlite_tcl.h"
20 #if SQLITE_OS_UNIX && SQLITE_THREADSAFE
27 extern const char *sqlite3ErrName(int);
30 ** Each thread is controlled by an instance of the following
33 typedef struct Thread Thread
;
35 /* The first group of fields are writable by the leader and read-only
37 char *zFilename
; /* Name of database file */
38 void (*xOp
)(Thread
*); /* next operation to do */
39 char *zArg
; /* argument usable by xOp */
40 int opnum
; /* Operation number */
41 int busy
; /* True if this thread is in use */
43 /* The next group of fields are writable by the thread but read-only to the
45 int completed
; /* Number of operations completed */
46 sqlite3
*db
; /* Open database */
47 sqlite3_stmt
*pStmt
; /* Pending operation */
48 char *zErr
; /* operation error */
49 char *zStaticErr
; /* Static error message */
50 int rc
; /* operation return code */
51 int argc
; /* number of columns in result */
52 const char *argv
[100]; /* result columns */
53 const char *colv
[100]; /* result column names */
57 ** There can be as many as 26 threads running at once. Each is named
58 ** by a capital letter: A, B, C, ..., Y, Z.
61 static Thread threadset
[N_THREAD
];
65 ** The main loop for a thread. Threads use busy waiting.
67 static void *test_thread_main(void *pArg
){
68 Thread
*p
= (Thread
*)pArg
;
72 sqlite3_open(p
->zFilename
, &p
->db
);
73 if( SQLITE_OK
!=sqlite3_errcode(p
->db
) ){
74 p
->zErr
= strdup(sqlite3_errmsg(p
->db
));
80 while( p
->opnum
<=p
->completed
) sched_yield();
82 if( p
->zErr
&& p
->zErr
!=p
->zStaticErr
){
83 sqlite3_free(p
->zErr
);
88 while( p
->opnum
<=p
->completed
) sched_yield();
91 sqlite3_finalize(p
->pStmt
);
98 if( p
->zErr
&& p
->zErr
!=p
->zStaticErr
){
99 sqlite3_free(p
->zErr
);
103 #ifndef SQLITE_OMIT_DEPRECATED
104 sqlite3_thread_cleanup();
110 ** Get a thread ID which is an upper case letter. Return the index.
111 ** If the argument is not a valid thread ID put an error message in
112 ** the interpreter and return -1.
114 static int parse_thread_id(Tcl_Interp
*interp
, const char *zArg
){
115 if( zArg
==0 || zArg
[0]==0 || zArg
[1]!=0 || !isupper((unsigned char)zArg
[0]) ){
116 Tcl_AppendResult(interp
, "thread ID must be an upper case letter", 0);
119 return zArg
[0] - 'A';
123 ** Usage: thread_create NAME FILENAME
125 ** NAME should be an upper case letter. Start the thread running with
126 ** an open connection to the given database.
128 static int SQLITE_TCLAPI
tcl_thread_create(
130 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
131 int argc
, /* Number of arguments */
132 const char **argv
/* Text of each argument */
139 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
143 i
= parse_thread_id(interp
, argv
[1]);
144 if( i
<0 ) return TCL_ERROR
;
145 if( threadset
[i
].busy
){
146 Tcl_AppendResult(interp
, "thread ", argv
[1], " is already running", 0);
149 threadset
[i
].busy
= 1;
150 sqlite3_free(threadset
[i
].zFilename
);
151 threadset
[i
].zFilename
= sqlite3_mprintf("%s", argv
[2]);
152 threadset
[i
].opnum
= 1;
153 threadset
[i
].completed
= 0;
154 rc
= pthread_create(&x
, 0, test_thread_main
, &threadset
[i
]);
156 Tcl_AppendResult(interp
, "failed to create the thread", 0);
157 sqlite3_free(threadset
[i
].zFilename
);
158 threadset
[i
].busy
= 0;
166 ** Wait for a thread to reach its idle state.
168 static void test_thread_wait(Thread
*p
){
169 while( p
->opnum
>p
->completed
) sched_yield();
173 ** Usage: thread_wait ID
175 ** Wait on thread ID to reach its idle state.
177 static int SQLITE_TCLAPI
tcl_thread_wait(
179 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
180 int argc
, /* Number of arguments */
181 const char **argv
/* Text of each argument */
186 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
190 i
= parse_thread_id(interp
, argv
[1]);
191 if( i
<0 ) return TCL_ERROR
;
192 if( !threadset
[i
].busy
){
193 Tcl_AppendResult(interp
, "no such thread", 0);
196 test_thread_wait(&threadset
[i
]);
203 static void test_stop_thread(Thread
*p
){
208 sqlite3_free(p
->zArg
);
210 sqlite3_free(p
->zFilename
);
216 ** Usage: thread_halt ID
218 ** Cause a thread to shut itself down. Wait for the shutdown to be
219 ** completed. If ID is "*" then stop all threads.
221 static int SQLITE_TCLAPI
tcl_thread_halt(
223 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
224 int argc
, /* Number of arguments */
225 const char **argv
/* Text of each argument */
230 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
234 if( argv
[1][0]=='*' && argv
[1][1]==0 ){
235 for(i
=0; i
<N_THREAD
; i
++){
236 if( threadset
[i
].busy
) test_stop_thread(&threadset
[i
]);
239 i
= parse_thread_id(interp
, argv
[1]);
240 if( i
<0 ) return TCL_ERROR
;
241 if( !threadset
[i
].busy
){
242 Tcl_AppendResult(interp
, "no such thread", 0);
245 test_stop_thread(&threadset
[i
]);
251 ** Usage: thread_argc ID
253 ** Wait on the most recent thread_step to complete, then return the
254 ** number of columns in the result set.
256 static int SQLITE_TCLAPI
tcl_thread_argc(
258 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
259 int argc
, /* Number of arguments */
260 const char **argv
/* Text of each argument */
266 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
270 i
= parse_thread_id(interp
, argv
[1]);
271 if( i
<0 ) return TCL_ERROR
;
272 if( !threadset
[i
].busy
){
273 Tcl_AppendResult(interp
, "no such thread", 0);
276 test_thread_wait(&threadset
[i
]);
277 sqlite3_snprintf(sizeof(zBuf
), zBuf
, "%d", threadset
[i
].argc
);
278 Tcl_AppendResult(interp
, zBuf
, 0);
283 ** Usage: thread_argv ID N
285 ** Wait on the most recent thread_step to complete, then return the
286 ** value of the N-th columns in the result set.
288 static int SQLITE_TCLAPI
tcl_thread_argv(
290 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
291 int argc
, /* Number of arguments */
292 const char **argv
/* Text of each argument */
298 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
302 i
= parse_thread_id(interp
, argv
[1]);
303 if( i
<0 ) return TCL_ERROR
;
304 if( !threadset
[i
].busy
){
305 Tcl_AppendResult(interp
, "no such thread", 0);
308 if( Tcl_GetInt(interp
, argv
[2], &n
) ) return TCL_ERROR
;
309 test_thread_wait(&threadset
[i
]);
310 if( n
<0 || n
>=threadset
[i
].argc
){
311 Tcl_AppendResult(interp
, "column number out of range", 0);
314 Tcl_AppendResult(interp
, threadset
[i
].argv
[n
], 0);
319 ** Usage: thread_colname ID N
321 ** Wait on the most recent thread_step to complete, then return the
322 ** name of the N-th columns in the result set.
324 static int SQLITE_TCLAPI
tcl_thread_colname(
326 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
327 int argc
, /* Number of arguments */
328 const char **argv
/* Text of each argument */
334 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
338 i
= parse_thread_id(interp
, argv
[1]);
339 if( i
<0 ) return TCL_ERROR
;
340 if( !threadset
[i
].busy
){
341 Tcl_AppendResult(interp
, "no such thread", 0);
344 if( Tcl_GetInt(interp
, argv
[2], &n
) ) return TCL_ERROR
;
345 test_thread_wait(&threadset
[i
]);
346 if( n
<0 || n
>=threadset
[i
].argc
){
347 Tcl_AppendResult(interp
, "column number out of range", 0);
350 Tcl_AppendResult(interp
, threadset
[i
].colv
[n
], 0);
355 ** Usage: thread_result ID
357 ** Wait on the most recent operation to complete, then return the
358 ** result code from that operation.
360 static int SQLITE_TCLAPI
tcl_thread_result(
362 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
363 int argc
, /* Number of arguments */
364 const char **argv
/* Text of each argument */
370 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
374 i
= parse_thread_id(interp
, argv
[1]);
375 if( i
<0 ) return TCL_ERROR
;
376 if( !threadset
[i
].busy
){
377 Tcl_AppendResult(interp
, "no such thread", 0);
380 test_thread_wait(&threadset
[i
]);
381 zName
= sqlite3ErrName(threadset
[i
].rc
);
382 Tcl_AppendResult(interp
, zName
, 0);
387 ** Usage: thread_error ID
389 ** Wait on the most recent operation to complete, then return the
392 static int SQLITE_TCLAPI
tcl_thread_error(
394 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
395 int argc
, /* Number of arguments */
396 const char **argv
/* Text of each argument */
401 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
405 i
= parse_thread_id(interp
, argv
[1]);
406 if( i
<0 ) return TCL_ERROR
;
407 if( !threadset
[i
].busy
){
408 Tcl_AppendResult(interp
, "no such thread", 0);
411 test_thread_wait(&threadset
[i
]);
412 Tcl_AppendResult(interp
, threadset
[i
].zErr
, 0);
417 ** This procedure runs in the thread to compile an SQL statement.
419 static void do_compile(Thread
*p
){
421 p
->zErr
= p
->zStaticErr
= "no database is open";
422 p
->rc
= SQLITE_ERROR
;
426 sqlite3_finalize(p
->pStmt
);
429 p
->rc
= sqlite3_prepare(p
->db
, p
->zArg
, -1, &p
->pStmt
, 0);
433 ** Usage: thread_compile ID SQL
435 ** Compile a new virtual machine.
437 static int SQLITE_TCLAPI
tcl_thread_compile(
439 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
440 int argc
, /* Number of arguments */
441 const char **argv
/* Text of each argument */
445 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
449 i
= parse_thread_id(interp
, argv
[1]);
450 if( i
<0 ) return TCL_ERROR
;
451 if( !threadset
[i
].busy
){
452 Tcl_AppendResult(interp
, "no such thread", 0);
455 test_thread_wait(&threadset
[i
]);
456 threadset
[i
].xOp
= do_compile
;
457 sqlite3_free(threadset
[i
].zArg
);
458 threadset
[i
].zArg
= sqlite3_mprintf("%s", argv
[2]);
459 threadset
[i
].opnum
++;
464 ** This procedure runs in the thread to step the virtual machine.
466 static void do_step(Thread
*p
){
469 p
->zErr
= p
->zStaticErr
= "no virtual machine available";
470 p
->rc
= SQLITE_ERROR
;
473 p
->rc
= sqlite3_step(p
->pStmt
);
474 if( p
->rc
==SQLITE_ROW
){
475 p
->argc
= sqlite3_column_count(p
->pStmt
);
476 for(i
=0; i
<sqlite3_data_count(p
->pStmt
); i
++){
477 p
->argv
[i
] = (char*)sqlite3_column_text(p
->pStmt
, i
);
479 for(i
=0; i
<p
->argc
; i
++){
480 p
->colv
[i
] = sqlite3_column_name(p
->pStmt
, i
);
486 ** Usage: thread_step ID
488 ** Advance the virtual machine by one step
490 static int SQLITE_TCLAPI
tcl_thread_step(
492 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
493 int argc
, /* Number of arguments */
494 const char **argv
/* Text of each argument */
498 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
502 i
= parse_thread_id(interp
, argv
[1]);
503 if( i
<0 ) return TCL_ERROR
;
504 if( !threadset
[i
].busy
){
505 Tcl_AppendResult(interp
, "no such thread", 0);
508 test_thread_wait(&threadset
[i
]);
509 threadset
[i
].xOp
= do_step
;
510 threadset
[i
].opnum
++;
515 ** This procedure runs in the thread to finalize a virtual machine.
517 static void do_finalize(Thread
*p
){
519 p
->zErr
= p
->zStaticErr
= "no virtual machine available";
520 p
->rc
= SQLITE_ERROR
;
523 p
->rc
= sqlite3_finalize(p
->pStmt
);
528 ** Usage: thread_finalize ID
530 ** Finalize the virtual machine.
532 static int SQLITE_TCLAPI
tcl_thread_finalize(
534 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
535 int argc
, /* Number of arguments */
536 const char **argv
/* Text of each argument */
540 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
544 i
= parse_thread_id(interp
, argv
[1]);
545 if( i
<0 ) return TCL_ERROR
;
546 if( !threadset
[i
].busy
){
547 Tcl_AppendResult(interp
, "no such thread", 0);
550 test_thread_wait(&threadset
[i
]);
551 threadset
[i
].xOp
= do_finalize
;
552 sqlite3_free(threadset
[i
].zArg
);
553 threadset
[i
].zArg
= 0;
554 threadset
[i
].opnum
++;
559 ** Usage: thread_swap ID ID
561 ** Interchange the sqlite* pointer between two threads.
563 static int SQLITE_TCLAPI
tcl_thread_swap(
565 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
566 int argc
, /* Number of arguments */
567 const char **argv
/* Text of each argument */
572 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
576 i
= parse_thread_id(interp
, argv
[1]);
577 if( i
<0 ) return TCL_ERROR
;
578 if( !threadset
[i
].busy
){
579 Tcl_AppendResult(interp
, "no such thread", 0);
582 test_thread_wait(&threadset
[i
]);
583 j
= parse_thread_id(interp
, argv
[2]);
584 if( j
<0 ) return TCL_ERROR
;
585 if( !threadset
[j
].busy
){
586 Tcl_AppendResult(interp
, "no such thread", 0);
589 test_thread_wait(&threadset
[j
]);
590 temp
= threadset
[i
].db
;
591 threadset
[i
].db
= threadset
[j
].db
;
592 threadset
[j
].db
= temp
;
597 ** Usage: thread_db_get ID
599 ** Return the database connection pointer for the given thread. Then
600 ** remove the pointer from the thread itself. Afterwards, the thread
601 ** can be stopped and the connection can be used by the main thread.
603 static int SQLITE_TCLAPI
tcl_thread_db_get(
605 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
606 int argc
, /* Number of arguments */
607 const char **argv
/* Text of each argument */
611 extern int sqlite3TestMakePointerStr(Tcl_Interp
*, char*, void*);
613 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
617 i
= parse_thread_id(interp
, argv
[1]);
618 if( i
<0 ) return TCL_ERROR
;
619 if( !threadset
[i
].busy
){
620 Tcl_AppendResult(interp
, "no such thread", 0);
623 test_thread_wait(&threadset
[i
]);
624 sqlite3TestMakePointerStr(interp
, zBuf
, threadset
[i
].db
);
626 Tcl_AppendResult(interp
, zBuf
, (char*)0);
631 ** Usage: thread_db_put ID DB
634 static int SQLITE_TCLAPI
tcl_thread_db_put(
636 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
637 int argc
, /* Number of arguments */
638 const char **argv
/* Text of each argument */
641 extern int sqlite3TestMakePointerStr(Tcl_Interp
*, char*, void*);
642 extern void *sqlite3TestTextToPtr(const char *);
644 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
648 i
= parse_thread_id(interp
, argv
[1]);
649 if( i
<0 ) return TCL_ERROR
;
650 if( !threadset
[i
].busy
){
651 Tcl_AppendResult(interp
, "no such thread", 0);
654 test_thread_wait(&threadset
[i
]);
655 assert( !threadset
[i
].db
);
656 threadset
[i
].db
= (sqlite3
*)sqlite3TestTextToPtr(argv
[2]);
661 ** Usage: thread_stmt_get ID
663 ** Return the database stmt pointer for the given thread. Then
664 ** remove the pointer from the thread itself.
666 static int SQLITE_TCLAPI
tcl_thread_stmt_get(
668 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
669 int argc
, /* Number of arguments */
670 const char **argv
/* Text of each argument */
674 extern int sqlite3TestMakePointerStr(Tcl_Interp
*, char*, void*);
676 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
680 i
= parse_thread_id(interp
, argv
[1]);
681 if( i
<0 ) return TCL_ERROR
;
682 if( !threadset
[i
].busy
){
683 Tcl_AppendResult(interp
, "no such thread", 0);
686 test_thread_wait(&threadset
[i
]);
687 sqlite3TestMakePointerStr(interp
, zBuf
, threadset
[i
].pStmt
);
688 threadset
[i
].pStmt
= 0;
689 Tcl_AppendResult(interp
, zBuf
, (char*)0);
694 ** Register commands with the TCL interpreter.
696 int Sqlitetest4_Init(Tcl_Interp
*interp
){
701 { "thread_create", (Tcl_CmdProc
*)tcl_thread_create
},
702 { "thread_wait", (Tcl_CmdProc
*)tcl_thread_wait
},
703 { "thread_halt", (Tcl_CmdProc
*)tcl_thread_halt
},
704 { "thread_argc", (Tcl_CmdProc
*)tcl_thread_argc
},
705 { "thread_argv", (Tcl_CmdProc
*)tcl_thread_argv
},
706 { "thread_colname", (Tcl_CmdProc
*)tcl_thread_colname
},
707 { "thread_result", (Tcl_CmdProc
*)tcl_thread_result
},
708 { "thread_error", (Tcl_CmdProc
*)tcl_thread_error
},
709 { "thread_compile", (Tcl_CmdProc
*)tcl_thread_compile
},
710 { "thread_step", (Tcl_CmdProc
*)tcl_thread_step
},
711 { "thread_finalize", (Tcl_CmdProc
*)tcl_thread_finalize
},
712 { "thread_swap", (Tcl_CmdProc
*)tcl_thread_swap
},
713 { "thread_db_get", (Tcl_CmdProc
*)tcl_thread_db_get
},
714 { "thread_db_put", (Tcl_CmdProc
*)tcl_thread_db_put
},
715 { "thread_stmt_get", (Tcl_CmdProc
*)tcl_thread_stmt_get
},
719 for(i
=0; i
<sizeof(aCmd
)/sizeof(aCmd
[0]); i
++){
720 Tcl_CreateCommand(interp
, aCmd
[i
].zName
, aCmd
[i
].xProc
, 0, 0);
725 int Sqlitetest4_Init(Tcl_Interp
*interp
){ return TCL_OK
; }
726 #endif /* SQLITE_OS_UNIX */