xref: /sqlite-3.40.0/src/test_thread.c (revision f78fbded)
144918fa0Sdanielk1977 /*
244918fa0Sdanielk1977 ** 2007 September 9
344918fa0Sdanielk1977 **
444918fa0Sdanielk1977 ** The author disclaims copyright to this source code.  In place of
544918fa0Sdanielk1977 ** a legal notice, here is a blessing:
644918fa0Sdanielk1977 **
744918fa0Sdanielk1977 **    May you do good and not evil.
844918fa0Sdanielk1977 **    May you find forgiveness for yourself and forgive others.
944918fa0Sdanielk1977 **    May you share freely, never taking more than you give.
1044918fa0Sdanielk1977 **
1144918fa0Sdanielk1977 *************************************************************************
1244918fa0Sdanielk1977 **
1344918fa0Sdanielk1977 ** This file contains the implementation of some Tcl commands used to
1444918fa0Sdanielk1977 ** test that sqlite3 database handles may be concurrently accessed by
1544918fa0Sdanielk1977 ** multiple threads. Right now this only works on unix.
1644918fa0Sdanielk1977 **
17*f78fbdedSdrh ** $Id: test_thread.c,v 1.5 2007/12/13 18:29:36 drh Exp $
1844918fa0Sdanielk1977 */
1944918fa0Sdanielk1977 
2044918fa0Sdanielk1977 #include "sqliteInt.h"
21*f78fbdedSdrh #include <tcl.h>
22570f7e25Sdanielk1977 
23570f7e25Sdanielk1977 #if SQLITE_THREADSAFE && defined(TCL_THREADS)
2444918fa0Sdanielk1977 
2544918fa0Sdanielk1977 #include <errno.h>
2644918fa0Sdanielk1977 #include <unistd.h>
2744918fa0Sdanielk1977 
2844918fa0Sdanielk1977 /*
2944918fa0Sdanielk1977 ** One of these is allocated for each thread created by [sqlthread spawn].
3044918fa0Sdanielk1977 */
3144918fa0Sdanielk1977 typedef struct SqlThread SqlThread;
3244918fa0Sdanielk1977 struct SqlThread {
33570f7e25Sdanielk1977   Tcl_ThreadId parent;     /* Thread id of parent thread */
34570f7e25Sdanielk1977   Tcl_Interp *interp;      /* Parent interpreter */
3544918fa0Sdanielk1977   char *zScript;           /* The script to execute. */
3644918fa0Sdanielk1977   char *zVarname;          /* Varname in parent script */
3744918fa0Sdanielk1977 };
3844918fa0Sdanielk1977 
39570f7e25Sdanielk1977 /*
40570f7e25Sdanielk1977 ** A custom Tcl_Event type used by this module. When the event is
41570f7e25Sdanielk1977 ** handled, script zScript is evaluated in interpreter interp. If
42570f7e25Sdanielk1977 ** the evaluation throws an exception (returns TCL_ERROR), then the
43570f7e25Sdanielk1977 ** error is handled by Tcl_BackgroundError(). If no error occurs,
44570f7e25Sdanielk1977 ** the result is simply discarded.
45570f7e25Sdanielk1977 */
46570f7e25Sdanielk1977 typedef struct EvalEvent EvalEvent;
47570f7e25Sdanielk1977 struct EvalEvent {
48570f7e25Sdanielk1977   Tcl_Event base;          /* Base class of type Tcl_Event */
49570f7e25Sdanielk1977   char *zScript;           /* The script to execute. */
50570f7e25Sdanielk1977   Tcl_Interp *interp;      /* The interpreter to execute it in. */
5144918fa0Sdanielk1977 };
5244918fa0Sdanielk1977 
5344918fa0Sdanielk1977 static Tcl_ObjCmdProc sqlthread_proc;
54570f7e25Sdanielk1977 int Sqlitetest1_Init(Tcl_Interp *);
5544918fa0Sdanielk1977 
56570f7e25Sdanielk1977 /*
57570f7e25Sdanielk1977 ** Handler for events of type EvalEvent.
58570f7e25Sdanielk1977 */
59570f7e25Sdanielk1977 static int tclScriptEvent(Tcl_Event *evPtr, int flags){
60570f7e25Sdanielk1977   int rc;
61570f7e25Sdanielk1977   EvalEvent *p = (EvalEvent *)evPtr;
62570f7e25Sdanielk1977   rc = Tcl_Eval(p->interp, p->zScript);
63570f7e25Sdanielk1977   if( rc!=TCL_OK ){
64570f7e25Sdanielk1977     Tcl_BackgroundError(p->interp);
65570f7e25Sdanielk1977   }
66570f7e25Sdanielk1977   return 1;
67570f7e25Sdanielk1977 }
68570f7e25Sdanielk1977 
69570f7e25Sdanielk1977 /*
70570f7e25Sdanielk1977 ** Register an EvalEvent to evaluate the script pScript in the
71570f7e25Sdanielk1977 ** parent interpreter/thread of SqlThread p.
72570f7e25Sdanielk1977 */
73570f7e25Sdanielk1977 static void postToParent(SqlThread *p, Tcl_Obj *pScript){
74570f7e25Sdanielk1977   EvalEvent *pEvent;
75570f7e25Sdanielk1977   char *zMsg;
76570f7e25Sdanielk1977   int nMsg;
77570f7e25Sdanielk1977 
78570f7e25Sdanielk1977   zMsg = Tcl_GetStringFromObj(pScript, &nMsg);
79570f7e25Sdanielk1977   pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1);
80570f7e25Sdanielk1977   pEvent->base.nextPtr = 0;
81570f7e25Sdanielk1977   pEvent->base.proc = tclScriptEvent;
82570f7e25Sdanielk1977   pEvent->zScript = (char *)&pEvent[1];
83570f7e25Sdanielk1977   memcpy(pEvent->zScript, zMsg, nMsg+1);
84570f7e25Sdanielk1977   pEvent->interp = p->interp;
85570f7e25Sdanielk1977 
86570f7e25Sdanielk1977   Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL);
87570f7e25Sdanielk1977   Tcl_ThreadAlert(p->parent);
88570f7e25Sdanielk1977 }
89570f7e25Sdanielk1977 
90570f7e25Sdanielk1977 /*
91570f7e25Sdanielk1977 ** The main function for threads created with [sqlthread spawn].
92570f7e25Sdanielk1977 */
93570f7e25Sdanielk1977 static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){
9444918fa0Sdanielk1977   Tcl_Interp *interp;
9544918fa0Sdanielk1977   Tcl_Obj *pRes;
9644918fa0Sdanielk1977   Tcl_Obj *pList;
9744918fa0Sdanielk1977   int rc;
9844918fa0Sdanielk1977 
9944918fa0Sdanielk1977   SqlThread *p = (SqlThread *)pSqlThread;
10044918fa0Sdanielk1977 
10144918fa0Sdanielk1977   interp = Tcl_CreateInterp();
10244918fa0Sdanielk1977   Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0);
10344918fa0Sdanielk1977   Sqlitetest1_Init(interp);
10444918fa0Sdanielk1977 
10544918fa0Sdanielk1977   rc = Tcl_Eval(interp, p->zScript);
10644918fa0Sdanielk1977   pRes = Tcl_GetObjResult(interp);
10744918fa0Sdanielk1977   pList = Tcl_NewObj();
10844918fa0Sdanielk1977   Tcl_IncrRefCount(pList);
109570f7e25Sdanielk1977   Tcl_IncrRefCount(pRes);
11044918fa0Sdanielk1977 
111d9b5b117Sdanielk1977   if( rc!=TCL_OK ){
112d9b5b117Sdanielk1977     Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("error", -1));
113d9b5b117Sdanielk1977     Tcl_ListObjAppendElement(interp, pList, pRes);
114d9b5b117Sdanielk1977     postToParent(p, pList);
115d9b5b117Sdanielk1977     Tcl_DecrRefCount(pList);
116d9b5b117Sdanielk1977     pList = Tcl_NewObj();
117d9b5b117Sdanielk1977   }
118d9b5b117Sdanielk1977 
11944918fa0Sdanielk1977   Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("set", -1));
12044918fa0Sdanielk1977   Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(p->zVarname, -1));
12144918fa0Sdanielk1977   Tcl_ListObjAppendElement(interp, pList, pRes);
122570f7e25Sdanielk1977   postToParent(p, pList);
123570f7e25Sdanielk1977 
124570f7e25Sdanielk1977   ckfree((void *)p);
12544918fa0Sdanielk1977   Tcl_DecrRefCount(pList);
126570f7e25Sdanielk1977   Tcl_DecrRefCount(pRes);
12744918fa0Sdanielk1977   Tcl_DeleteInterp(interp);
128570f7e25Sdanielk1977   return;
12944918fa0Sdanielk1977 }
13044918fa0Sdanielk1977 
13144918fa0Sdanielk1977 /*
13244918fa0Sdanielk1977 ** sqlthread spawn VARNAME SCRIPT
13344918fa0Sdanielk1977 **
13444918fa0Sdanielk1977 **     Spawn a new thread with it's own Tcl interpreter and run the
13544918fa0Sdanielk1977 **     specified SCRIPT(s) in it. The thread terminates after running
13644918fa0Sdanielk1977 **     the script. The result of the script is stored in the variable
13744918fa0Sdanielk1977 **     VARNAME.
13844918fa0Sdanielk1977 **
13944918fa0Sdanielk1977 **     The caller can wait for the script to terminate using [vwait VARNAME].
14044918fa0Sdanielk1977 */
14144918fa0Sdanielk1977 static int sqlthread_spawn(
14244918fa0Sdanielk1977   ClientData clientData,
14344918fa0Sdanielk1977   Tcl_Interp *interp,
14444918fa0Sdanielk1977   int objc,
14544918fa0Sdanielk1977   Tcl_Obj *CONST objv[]
14644918fa0Sdanielk1977 ){
147570f7e25Sdanielk1977   Tcl_ThreadId x;
14844918fa0Sdanielk1977   SqlThread *pNew;
14944918fa0Sdanielk1977   int rc;
15044918fa0Sdanielk1977 
15144918fa0Sdanielk1977   int nVarname; char *zVarname;
15244918fa0Sdanielk1977   int nScript; char *zScript;
15344918fa0Sdanielk1977 
154570f7e25Sdanielk1977   /* Parameters for thread creation */
155570f7e25Sdanielk1977   const int nStack = TCL_THREAD_STACK_DEFAULT;
156570f7e25Sdanielk1977   const int flags = TCL_THREAD_NOFLAGS;
157570f7e25Sdanielk1977 
15844918fa0Sdanielk1977   assert(objc==4);
15944918fa0Sdanielk1977 
16044918fa0Sdanielk1977   zVarname = Tcl_GetStringFromObj(objv[2], &nVarname);
16144918fa0Sdanielk1977   zScript = Tcl_GetStringFromObj(objv[3], &nScript);
162570f7e25Sdanielk1977 
163570f7e25Sdanielk1977   pNew = (SqlThread *)ckalloc(sizeof(SqlThread)+nVarname+nScript+2);
16444918fa0Sdanielk1977   pNew->zVarname = (char *)&pNew[1];
16544918fa0Sdanielk1977   pNew->zScript = (char *)&pNew->zVarname[nVarname+1];
16644918fa0Sdanielk1977   memcpy(pNew->zVarname, zVarname, nVarname+1);
16744918fa0Sdanielk1977   memcpy(pNew->zScript, zScript, nScript+1);
168570f7e25Sdanielk1977   pNew->parent = Tcl_GetCurrentThread();
169570f7e25Sdanielk1977   pNew->interp = interp;
17044918fa0Sdanielk1977 
171570f7e25Sdanielk1977   rc = Tcl_CreateThread(&x, tclScriptThread, (void *)pNew, nStack, flags);
172570f7e25Sdanielk1977   if( rc!=TCL_OK ){
173570f7e25Sdanielk1977     Tcl_AppendResult(interp, "Error in Tcl_CreateThread()", 0);
17444918fa0Sdanielk1977     sqlite3_free(pNew);
17544918fa0Sdanielk1977     return TCL_ERROR;
17644918fa0Sdanielk1977   }
17744918fa0Sdanielk1977 
17844918fa0Sdanielk1977   return TCL_OK;
17944918fa0Sdanielk1977 }
18044918fa0Sdanielk1977 
18144918fa0Sdanielk1977 /*
18244918fa0Sdanielk1977 ** sqlthread parent SCRIPT
18344918fa0Sdanielk1977 **
18444918fa0Sdanielk1977 **     This can be called by spawned threads only. It sends the specified
18544918fa0Sdanielk1977 **     script back to the parent thread for execution. The result of
18644918fa0Sdanielk1977 **     evaluating the SCRIPT is returned. The parent thread must enter
18744918fa0Sdanielk1977 **     the event loop for this to work - otherwise the caller will
18844918fa0Sdanielk1977 **     block indefinitely.
18944918fa0Sdanielk1977 **
19044918fa0Sdanielk1977 **     NOTE: At the moment, this doesn't work. FIXME.
19144918fa0Sdanielk1977 */
19244918fa0Sdanielk1977 static int sqlthread_parent(
19344918fa0Sdanielk1977   ClientData clientData,
19444918fa0Sdanielk1977   Tcl_Interp *interp,
19544918fa0Sdanielk1977   int objc,
19644918fa0Sdanielk1977   Tcl_Obj *CONST objv[]
19744918fa0Sdanielk1977 ){
198570f7e25Sdanielk1977   EvalEvent *pEvent;
19944918fa0Sdanielk1977   char *zMsg;
20044918fa0Sdanielk1977   int nMsg;
20144918fa0Sdanielk1977   SqlThread *p = (SqlThread *)clientData;
20244918fa0Sdanielk1977 
20344918fa0Sdanielk1977   assert(objc==3);
20444918fa0Sdanielk1977   if( p==0 ){
20544918fa0Sdanielk1977     Tcl_AppendResult(interp, "no parent thread", 0);
20644918fa0Sdanielk1977     return TCL_ERROR;
20744918fa0Sdanielk1977   }
20844918fa0Sdanielk1977 
20944918fa0Sdanielk1977   zMsg = Tcl_GetStringFromObj(objv[2], &nMsg);
210570f7e25Sdanielk1977   pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1);
211570f7e25Sdanielk1977   pEvent->base.nextPtr = 0;
212570f7e25Sdanielk1977   pEvent->base.proc = tclScriptEvent;
213570f7e25Sdanielk1977   pEvent->zScript = (char *)&pEvent[1];
214570f7e25Sdanielk1977   memcpy(pEvent->zScript, zMsg, nMsg+1);
215570f7e25Sdanielk1977   pEvent->interp = p->interp;
216570f7e25Sdanielk1977   Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL);
217570f7e25Sdanielk1977   Tcl_ThreadAlert(p->parent);
21844918fa0Sdanielk1977 
21944918fa0Sdanielk1977   return TCL_OK;
22044918fa0Sdanielk1977 }
22144918fa0Sdanielk1977 
222d9b5b117Sdanielk1977 static int xBusy(void *pArg, int nBusy){
223d9b5b117Sdanielk1977   sqlite3_sleep(50);
224d9b5b117Sdanielk1977   return 1;             /* Try again... */
225d9b5b117Sdanielk1977 }
226d9b5b117Sdanielk1977 
227e9dcd5e6Sdanielk1977 /*
228e9dcd5e6Sdanielk1977 ** sqlthread open
229e9dcd5e6Sdanielk1977 **
230e9dcd5e6Sdanielk1977 **     Open a database handle and return the string representation of
231e9dcd5e6Sdanielk1977 **     the pointer value.
232e9dcd5e6Sdanielk1977 */
233d9b5b117Sdanielk1977 static int sqlthread_open(
234d9b5b117Sdanielk1977   ClientData clientData,
235d9b5b117Sdanielk1977   Tcl_Interp *interp,
236d9b5b117Sdanielk1977   int objc,
237d9b5b117Sdanielk1977   Tcl_Obj *CONST objv[]
238d9b5b117Sdanielk1977 ){
239d9b5b117Sdanielk1977   int sqlite3TestMakePointerStr(Tcl_Interp *interp, char *zPtr, void *p);
240d9b5b117Sdanielk1977 
241d9b5b117Sdanielk1977   const char *zFilename;
242d9b5b117Sdanielk1977   sqlite3 *db;
243d9b5b117Sdanielk1977   int rc;
244d9b5b117Sdanielk1977   char zBuf[100];
245d9b5b117Sdanielk1977   extern void Md5_Register(sqlite3*);
246d9b5b117Sdanielk1977 
247d9b5b117Sdanielk1977   zFilename = Tcl_GetString(objv[2]);
248d9b5b117Sdanielk1977   rc = sqlite3_open(zFilename, &db);
249d9b5b117Sdanielk1977   Md5_Register(db);
250d9b5b117Sdanielk1977   sqlite3_busy_handler(db, xBusy, 0);
251d9b5b117Sdanielk1977 
252d9b5b117Sdanielk1977   if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR;
253d9b5b117Sdanielk1977   Tcl_AppendResult(interp, zBuf, 0);
254d9b5b117Sdanielk1977 
255d9b5b117Sdanielk1977   return TCL_OK;
256d9b5b117Sdanielk1977 }
257d9b5b117Sdanielk1977 
258d9b5b117Sdanielk1977 
25944918fa0Sdanielk1977 /*
260e9dcd5e6Sdanielk1977 ** sqlthread open
261e9dcd5e6Sdanielk1977 **
262e9dcd5e6Sdanielk1977 **     Return the current thread-id (Tcl_GetCurrentThread()) cast to
263e9dcd5e6Sdanielk1977 **     an integer.
264e9dcd5e6Sdanielk1977 */
265e9dcd5e6Sdanielk1977 static int sqlthread_id(
266e9dcd5e6Sdanielk1977   ClientData clientData,
267e9dcd5e6Sdanielk1977   Tcl_Interp *interp,
268e9dcd5e6Sdanielk1977   int objc,
269e9dcd5e6Sdanielk1977   Tcl_Obj *CONST objv[]
270e9dcd5e6Sdanielk1977 ){
271e9dcd5e6Sdanielk1977   Tcl_ThreadId id = Tcl_GetCurrentThread();
272e9dcd5e6Sdanielk1977   Tcl_SetObjResult(interp, Tcl_NewIntObj((int)id));
273e9dcd5e6Sdanielk1977   return TCL_OK;
274e9dcd5e6Sdanielk1977 }
275e9dcd5e6Sdanielk1977 
276e9dcd5e6Sdanielk1977 
277e9dcd5e6Sdanielk1977 /*
27844918fa0Sdanielk1977 ** Dispatch routine for the sub-commands of [sqlthread].
27944918fa0Sdanielk1977 */
28044918fa0Sdanielk1977 static int sqlthread_proc(
28144918fa0Sdanielk1977   ClientData clientData,
28244918fa0Sdanielk1977   Tcl_Interp *interp,
28344918fa0Sdanielk1977   int objc,
28444918fa0Sdanielk1977   Tcl_Obj *CONST objv[]
28544918fa0Sdanielk1977 ){
28644918fa0Sdanielk1977   struct SubCommand {
28744918fa0Sdanielk1977     char *zName;
28844918fa0Sdanielk1977     Tcl_ObjCmdProc *xProc;
28944918fa0Sdanielk1977     int nArg;
29044918fa0Sdanielk1977     char *zUsage;
29144918fa0Sdanielk1977   } aSub[] = {
29244918fa0Sdanielk1977     {"parent", sqlthread_parent, 1, "SCRIPT"},
29344918fa0Sdanielk1977     {"spawn",  sqlthread_spawn,  2, "VARNAME SCRIPT"},
294d9b5b117Sdanielk1977     {"open",   sqlthread_open,   1, "DBNAME"},
295e9dcd5e6Sdanielk1977     {"id",     sqlthread_id,     0, ""},
29644918fa0Sdanielk1977     {0, 0, 0}
29744918fa0Sdanielk1977   };
29844918fa0Sdanielk1977   struct SubCommand *pSub;
29944918fa0Sdanielk1977   int rc;
30044918fa0Sdanielk1977   int iIndex;
30144918fa0Sdanielk1977 
30244918fa0Sdanielk1977   if( objc<2 ){
30344918fa0Sdanielk1977     Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND");
30444918fa0Sdanielk1977     return TCL_ERROR;
30544918fa0Sdanielk1977   }
30644918fa0Sdanielk1977 
30744918fa0Sdanielk1977   rc = Tcl_GetIndexFromObjStruct(
30844918fa0Sdanielk1977       interp, objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iIndex
30944918fa0Sdanielk1977   );
31044918fa0Sdanielk1977   if( rc!=TCL_OK ) return rc;
31144918fa0Sdanielk1977   pSub = &aSub[iIndex];
31244918fa0Sdanielk1977 
31344918fa0Sdanielk1977   if( objc!=(pSub->nArg+2) ){
31444918fa0Sdanielk1977     Tcl_WrongNumArgs(interp, 2, objv, pSub->zUsage);
31544918fa0Sdanielk1977     return TCL_ERROR;
31644918fa0Sdanielk1977   }
31744918fa0Sdanielk1977 
31844918fa0Sdanielk1977   return pSub->xProc(clientData, interp, objc, objv);
31944918fa0Sdanielk1977 }
32044918fa0Sdanielk1977 
32144918fa0Sdanielk1977 /*
32244918fa0Sdanielk1977 ** Register commands with the TCL interpreter.
32344918fa0Sdanielk1977 */
32444918fa0Sdanielk1977 int SqlitetestThread_Init(Tcl_Interp *interp){
32544918fa0Sdanielk1977   Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0);
32644918fa0Sdanielk1977   return TCL_OK;
32744918fa0Sdanielk1977 }
32844918fa0Sdanielk1977 #else
32944918fa0Sdanielk1977 int SqlitetestThread_Init(Tcl_Interp *interp){
33044918fa0Sdanielk1977   return TCL_OK;
33144918fa0Sdanielk1977 }
33244918fa0Sdanielk1977 #endif
333