Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Add the beginning of the thread-safety tests. There are more to come. (CVS 4413) |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
753908e8411024abd5c3da1b8c62f70e |
User & Date: | danielk1977 2007-09-07 11:29:25.000 |
Context
2007-09-07
| ||
14:32 | Fix a segfault that could occur while attempting to add new pages to the freelist in a corrupt database. (CVS 4414) (check-in: c8e85fff7e user: drh tags: trunk) | |
11:29 | Add the beginning of the thread-safety tests. There are more to come. (CVS 4413) (check-in: 753908e841 user: danielk1977 tags: trunk) | |
01:12 | Modify the CLI so that it will ignore whitespace at the end of lines. Ticket #2631 (CVS 4412) (check-in: f780a17f4b user: drh tags: trunk) | |
Changes
Changes to main.mk.
︙ | ︙ | |||
212 213 214 215 216 217 218 219 220 221 222 223 224 225 | $(TOP)/src/test_config.c \ $(TOP)/src/test_hexio.c \ $(TOP)/src/test_malloc.c \ $(TOP)/src/test_md5.c \ $(TOP)/src/test_schema.c \ $(TOP)/src/test_server.c \ $(TOP)/src/test_tclvar.c \ TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c TESTSRC += $(TOP)/ext/fts3/fts3_tokenizer.c TESTSRC2 = \ $(TOP)/src/attach.c $(TOP)/src/btree.c $(TOP)/src/build.c $(TOP)/src/date.c \ $(TOP)/src/expr.c $(TOP)/src/func.c $(TOP)/src/insert.c $(TOP)/src/os.c \ | > | 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | $(TOP)/src/test_config.c \ $(TOP)/src/test_hexio.c \ $(TOP)/src/test_malloc.c \ $(TOP)/src/test_md5.c \ $(TOP)/src/test_schema.c \ $(TOP)/src/test_server.c \ $(TOP)/src/test_tclvar.c \ $(TOP)/src/test_thread.c \ TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c TESTSRC += $(TOP)/ext/fts3/fts3_tokenizer.c TESTSRC2 = \ $(TOP)/src/attach.c $(TOP)/src/btree.c $(TOP)/src/build.c $(TOP)/src/date.c \ $(TOP)/src/expr.c $(TOP)/src/func.c $(TOP)/src/insert.c $(TOP)/src/os.c \ |
︙ | ︙ |
Changes to src/tclsqlite.c.
︙ | ︙ | |||
8 9 10 11 12 13 14 | ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** A TCL Interface to SQLite. Append this file to sqlite3.c and ** compile the whole thing to build a TCL-enabled version of SQLite. ** | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** A TCL Interface to SQLite. Append this file to sqlite3.c and ** compile the whole thing to build a TCL-enabled version of SQLite. ** ** $Id: tclsqlite.c,v 1.202 2007/09/07 11:29:25 danielk1977 Exp $ */ #include "tcl.h" #include <errno.h> /* ** Some additional include files are needed if this file is not ** appended to the amalgamation. |
︙ | ︙ | |||
2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 | extern int Sqlitetestasync_Init(Tcl_Interp*); extern int Sqlitetest_autoext_Init(Tcl_Interp*); extern int Sqlitetest_hexio_Init(Tcl_Interp*); extern int Sqlitetest_malloc_Init(Tcl_Interp*); extern int Sqlitetestschema_Init(Tcl_Interp*); extern int Sqlitetestsse_Init(Tcl_Interp*); extern int Sqlitetesttclvar_Init(Tcl_Interp*); Md5_Init(interp); Sqliteconfig_Init(interp); Sqlitetest1_Init(interp); Sqlitetest2_Init(interp); Sqlitetest3_Init(interp); Sqlitetest4_Init(interp); Sqlitetest5_Init(interp); Sqlitetest6_Init(interp); Sqlitetest7_Init(interp); Sqlitetest8_Init(interp); Sqlitetest9_Init(interp); Sqlitetestasync_Init(interp); Sqlitetest_autoext_Init(interp); Sqlitetest_hexio_Init(interp); Sqlitetest_malloc_Init(interp); Sqlitetestschema_Init(interp); Sqlitetesttclvar_Init(interp); #ifdef SQLITE_SSE Sqlitetestsse_Init(interp); #endif } #endif if( argc>=2 || TCLSH==2 ){ int i; | > > | 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 | extern int Sqlitetestasync_Init(Tcl_Interp*); extern int Sqlitetest_autoext_Init(Tcl_Interp*); extern int Sqlitetest_hexio_Init(Tcl_Interp*); extern int Sqlitetest_malloc_Init(Tcl_Interp*); extern int Sqlitetestschema_Init(Tcl_Interp*); extern int Sqlitetestsse_Init(Tcl_Interp*); extern int Sqlitetesttclvar_Init(Tcl_Interp*); extern int SqlitetestThread_Init(Tcl_Interp*); Md5_Init(interp); Sqliteconfig_Init(interp); Sqlitetest1_Init(interp); Sqlitetest2_Init(interp); Sqlitetest3_Init(interp); Sqlitetest4_Init(interp); Sqlitetest5_Init(interp); Sqlitetest6_Init(interp); Sqlitetest7_Init(interp); Sqlitetest8_Init(interp); Sqlitetest9_Init(interp); Sqlitetestasync_Init(interp); Sqlitetest_autoext_Init(interp); Sqlitetest_hexio_Init(interp); Sqlitetest_malloc_Init(interp); Sqlitetestschema_Init(interp); Sqlitetesttclvar_Init(interp); SqlitetestThread_Init(interp); #ifdef SQLITE_SSE Sqlitetestsse_Init(interp); #endif } #endif if( argc>=2 || TCLSH==2 ){ int i; |
︙ | ︙ |
Added src/test_thread.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | /* ** 2007 September 9 ** ** 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 contains the implementation of some Tcl commands used to ** test that sqlite3 database handles may be concurrently accessed by ** multiple threads. Right now this only works on unix. ** ** $Id: test_thread.c,v 1.1 2007/09/07 11:29:25 danielk1977 Exp $ */ #include "sqliteInt.h" #if defined(OS_UNIX) && SQLITE_THREADSAFE #include <tcl.h> #include <pthread.h> #include <errno.h> #include <unistd.h> /* ** One of these is allocated for each thread created by [sqlthread spawn]. */ typedef struct SqlThread SqlThread; struct SqlThread { int fd; /* The pipe to send commands to the parent */ char *zScript; /* The script to execute. */ char *zVarname; /* Varname in parent script */ }; typedef struct SqlParent SqlParent; struct SqlParent { Tcl_Interp *interp; int fd; }; static Tcl_ObjCmdProc sqlthread_proc; static void *tclScriptThread(void *pSqlThread){ Tcl_Interp *interp; Tcl_Obj *pRes; Tcl_Obj *pList; char *zMsg; int nMsg; int rc; SqlThread *p = (SqlThread *)pSqlThread; interp = Tcl_CreateInterp(); Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0); Sqlitetest1_Init(interp); rc = Tcl_Eval(interp, p->zScript); pRes = Tcl_GetObjResult(interp); pList = Tcl_NewObj(); Tcl_IncrRefCount(pList); if( rc==TCL_OK ){ Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("set", -1)); Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(p->zVarname, -1)); }else{ Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("error", -1)); } Tcl_ListObjAppendElement(interp, pList, pRes); zMsg = Tcl_GetStringFromObj(pList, &nMsg); write(p->fd, zMsg, nMsg+1); close(p->fd); sqlite3_free(p); Tcl_DecrRefCount(pList); Tcl_DeleteInterp(interp); return 0; } void pipe_callback(ClientData clientData, int flags){ SqlParent *p = (SqlParent *)clientData; char zBuf[1024]; int nChar; nChar = read(p->fd, zBuf, 1023); if( nChar<=0 ){ /* Other end has been closed */ Tcl_DeleteFileHandler(p->fd); sqlite3_free(p); }else{ zBuf[1023] = '\0'; if( TCL_OK!=Tcl_Eval(p->interp, zBuf) ){ Tcl_BackgroundError(p->interp); } } } /* ** sqlthread spawn VARNAME SCRIPT ** ** Spawn a new thread with it's own Tcl interpreter and run the ** specified SCRIPT(s) in it. The thread terminates after running ** the script. The result of the script is stored in the variable ** VARNAME. ** ** The caller can wait for the script to terminate using [vwait VARNAME]. */ static int sqlthread_spawn( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ pthread_t x; SqlThread *pNew; SqlParent *pParent; int fds[2]; int rc; int nVarname; char *zVarname; int nScript; char *zScript; assert(objc==4); zVarname = Tcl_GetStringFromObj(objv[2], &nVarname); zScript = Tcl_GetStringFromObj(objv[3], &nScript); pNew = (SqlThread *)sqlite3_malloc(sizeof(SqlThread)+nVarname+nScript+2); if( pNew==0 ){ Tcl_AppendResult(interp, "Malloc failure", 0); return TCL_ERROR; } pNew->zVarname = (char *)&pNew[1]; pNew->zScript = (char *)&pNew->zVarname[nVarname+1]; memcpy(pNew->zVarname, zVarname, nVarname+1); memcpy(pNew->zScript, zScript, nScript+1); pParent = (SqlParent *)sqlite3_malloc(sizeof(SqlParent)); if( pParent==0 ){ Tcl_AppendResult(interp, "Malloc failure", 0); sqlite3_free(pNew); return TCL_ERROR; } rc = pipe(fds); if( rc!=0 ){ Tcl_AppendResult(interp, "Error in pipe(): ", strerror(errno), 0); sqlite3_free(pNew); sqlite3_free(pParent); return TCL_ERROR; } pParent->fd = fds[0]; pParent->interp = interp; Tcl_CreateFileHandler( fds[0], TCL_READABLE|TCL_EXCEPTION, pipe_callback, (void *)pParent ); pNew->fd = fds[1]; rc = pthread_create(&x, 0, tclScriptThread, (void *)pNew); if( rc!=0 ){ Tcl_AppendResult(interp, "Error in pthread_create(): ", strerror(errno), 0); Tcl_DeleteFileHandler(fds[0]); sqlite3_free(pNew); sqlite3_free(pParent); close(fds[0]); close(fds[1]); return TCL_ERROR; } return TCL_OK; } /* ** sqlthread parent SCRIPT ** ** This can be called by spawned threads only. It sends the specified ** script back to the parent thread for execution. The result of ** evaluating the SCRIPT is returned. The parent thread must enter ** the event loop for this to work - otherwise the caller will ** block indefinitely. ** ** NOTE: At the moment, this doesn't work. FIXME. */ #if 0 static int sqlthread_parent( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ char *zMsg; int nMsg; SqlThread *p = (SqlThread *)clientData; assert(objc==3); if( p==0 ){ Tcl_AppendResult(interp, "no parent thread", 0); return TCL_ERROR; } zMsg = Tcl_GetStringFromObj(objv[2], &nMsg); write(p->fd, zMsg, nMsg+1); return TCL_OK; } #endif /* ** Dispatch routine for the sub-commands of [sqlthread]. */ static int sqlthread_proc( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ struct SubCommand { char *zName; Tcl_ObjCmdProc *xProc; int nArg; char *zUsage; } aSub[] = { #if 0 {"parent", sqlthread_parent, 1, "SCRIPT"}, #endif {"spawn", sqlthread_spawn, 2, "VARNAME SCRIPT"}, {0, 0, 0} }; struct SubCommand *pSub; int rc; int iIndex; if( objc<2 ){ Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND"); return TCL_ERROR; } rc = Tcl_GetIndexFromObjStruct( interp, objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iIndex ); if( rc!=TCL_OK ) return rc; pSub = &aSub[iIndex]; if( objc!=(pSub->nArg+2) ){ Tcl_WrongNumArgs(interp, 2, objv, pSub->zUsage); return TCL_ERROR; } return pSub->xProc(clientData, interp, objc, objv); } /* ** Register commands with the TCL interpreter. */ int SqlitetestThread_Init(Tcl_Interp *interp){ Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0); return TCL_OK; } #else int SqlitetestThread_Init(Tcl_Interp *interp){ return TCL_OK; } #endif |
Added test/thread001.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | # 2007 September 7 # # 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. # #*********************************************************************** # # $Id: thread001.test,v 1.1 2007/09/07 11:29:25 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl if {[info commands sqlthread] eq ""} { puts "Skipping thread-safety tests - not running a threadsafe unix build" finish_test return } # Set up a database and a schema. The database contains a single # table with two columns. The first column ("a") is an INTEGER PRIMARY # KEY. The second contains the md5sum of all rows in the table with # a smaller value stored in column "a". # do_test thread001.1 { execsql { CREATE TABLE ab(a INTEGER PRIMARY KEY, b); CREATE INDEX ab_i ON ab(b); INSERT INTO ab SELECT NULL, md5sum(a, b) FROM ab; SELECT count(*) FROM ab; } } {1} do_test thread001.2 { execsql { SELECT (SELECT md5sum(a, b) FROM ab WHERE a < (SELECT max(a) FROM ab)) == (SELECT b FROM ab WHERE a = (SELECT max(a) FROM ab)) } } {1} do_test thread001.3 { execsql { PRAGMA integrity_check } } {ok} set thread_program [format { set ::DB %s # Execute the supplied SQL using database handle $::DB. # proc execsql {sql} { set res [list] set ::STMT [sqlite3_prepare $::DB $sql -1 dummy_tail] while {[sqlite3_step $::STMT] eq "SQLITE_ROW"} { for {set i 0} {$i < [sqlite3_column_count $::STMT]} {incr i} { lappend res [sqlite3_column_text $::STMT 0] } } set rc [sqlite3_finalize $::STMT] if {$rc ne "SQLITE_OK"} { error [sqlite3_errmsg $::DB] } set res } for {set i 0} {$i < 100} {incr i} { # Test that the invariant is true. set val [execsql { SELECT (SELECT md5sum(a, b) FROM ab WHERE a < (SELECT max(a) FROM ab)) == (SELECT b FROM ab WHERE a = (SELECT max(a) FROM ab)) }] if {$val ne "1"} {error "Invariant test failed"} # Add another row to the database. execsql { INSERT INTO ab SELECT NULL, md5sum(a, b) FROM ab } } list OK } [sqlite3_connection_pointer db]] # Kick off 10 threads: # array unset finished for {set i 0} {$i < 10} {incr i} { sqlthread spawn finished($i) $thread_program } for {set i 0} {$i < 10} {incr i} { if {![info exists finished($i)]} { vwait finished($i) } do_test thread001.4.$i { set ::finished($i) } OK } do_test thread001.5 { execsql { SELECT count(*) FROM ab; } } {1001} do_test thread001.6 { execsql { SELECT (SELECT md5sum(a, b) FROM ab WHERE a < (SELECT max(a) FROM ab)) == (SELECT b FROM ab WHERE a = (SELECT max(a) FROM ab)) } } {1} do_test thread001.7 { execsql { PRAGMA integrity_check } } {ok} # Give the event-handlers a chance to close any open parent-child pipes. # Otherwise, the test is reported as leaking memory (it has not - it's # just that the memory is freed asynchronously). # after 250 {set abit 0} vwait abit finish_test |