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