xref: /sqlite-3.40.0/src/test_thread.c (revision 81fa193a)
1 /*
2 ** 2007 September 9
3 **
4 ** The author disclaims copyright to this source code.  In place of
5 ** 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 **
13 ** This file contains the implementation of some Tcl commands used to
14 ** test that sqlite3 database handles may be concurrently accessed by
15 ** multiple threads. Right now this only works on unix.
16 **
17 ** $Id: test_thread.c,v 1.8 2008/08/28 13:55:10 danielk1977 Exp $
18 */
19 
20 #include "sqliteInt.h"
21 #include <tcl.h>
22 
23 #if SQLITE_THREADSAFE && defined(TCL_THREADS)
24 
25 #include <errno.h>
26 #include <unistd.h>
27 
28 /*
29 ** One of these is allocated for each thread created by [sqlthread spawn].
30 */
31 typedef struct SqlThread SqlThread;
32 struct SqlThread {
33   Tcl_ThreadId parent;     /* Thread id of parent thread */
34   Tcl_Interp *interp;      /* Parent interpreter */
35   char *zScript;           /* The script to execute. */
36   char *zVarname;          /* Varname in parent script */
37 };
38 
39 /*
40 ** A custom Tcl_Event type used by this module. When the event is
41 ** handled, script zScript is evaluated in interpreter interp. If
42 ** the evaluation throws an exception (returns TCL_ERROR), then the
43 ** error is handled by Tcl_BackgroundError(). If no error occurs,
44 ** the result is simply discarded.
45 */
46 typedef struct EvalEvent EvalEvent;
47 struct EvalEvent {
48   Tcl_Event base;          /* Base class of type Tcl_Event */
49   char *zScript;           /* The script to execute. */
50   Tcl_Interp *interp;      /* The interpreter to execute it in. */
51 };
52 
53 static Tcl_ObjCmdProc sqlthread_proc;
54 static Tcl_ObjCmdProc clock_seconds_proc;
55 int Sqlitetest1_Init(Tcl_Interp *);
56 
57 /*
58 ** Handler for events of type EvalEvent.
59 */
60 static int tclScriptEvent(Tcl_Event *evPtr, int flags){
61   int rc;
62   EvalEvent *p = (EvalEvent *)evPtr;
63   rc = Tcl_Eval(p->interp, p->zScript);
64   if( rc!=TCL_OK ){
65     Tcl_BackgroundError(p->interp);
66   }
67   return 1;
68 }
69 
70 /*
71 ** Register an EvalEvent to evaluate the script pScript in the
72 ** parent interpreter/thread of SqlThread p.
73 */
74 static void postToParent(SqlThread *p, Tcl_Obj *pScript){
75   EvalEvent *pEvent;
76   char *zMsg;
77   int nMsg;
78 
79   zMsg = Tcl_GetStringFromObj(pScript, &nMsg);
80   pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1);
81   pEvent->base.nextPtr = 0;
82   pEvent->base.proc = tclScriptEvent;
83   pEvent->zScript = (char *)&pEvent[1];
84   memcpy(pEvent->zScript, zMsg, nMsg+1);
85   pEvent->interp = p->interp;
86 
87   Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL);
88   Tcl_ThreadAlert(p->parent);
89 }
90 
91 /*
92 ** The main function for threads created with [sqlthread spawn].
93 */
94 static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){
95   Tcl_Interp *interp;
96   Tcl_Obj *pRes;
97   Tcl_Obj *pList;
98   int rc;
99 
100   SqlThread *p = (SqlThread *)pSqlThread;
101 
102   interp = Tcl_CreateInterp();
103   Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);
104   Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0);
105   Sqlitetest1_Init(interp);
106 
107   rc = Tcl_Eval(interp, p->zScript);
108   pRes = Tcl_GetObjResult(interp);
109   pList = Tcl_NewObj();
110   Tcl_IncrRefCount(pList);
111   Tcl_IncrRefCount(pRes);
112 
113   if( rc!=TCL_OK ){
114     Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("error", -1));
115     Tcl_ListObjAppendElement(interp, pList, pRes);
116     postToParent(p, pList);
117     Tcl_DecrRefCount(pList);
118     pList = Tcl_NewObj();
119   }
120 
121   Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("set", -1));
122   Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(p->zVarname, -1));
123   Tcl_ListObjAppendElement(interp, pList, pRes);
124   postToParent(p, pList);
125 
126   ckfree((void *)p);
127   Tcl_DecrRefCount(pList);
128   Tcl_DecrRefCount(pRes);
129   Tcl_DeleteInterp(interp);
130   return;
131 }
132 
133 /*
134 ** sqlthread spawn VARNAME SCRIPT
135 **
136 **     Spawn a new thread with its own Tcl interpreter and run the
137 **     specified SCRIPT(s) in it. The thread terminates after running
138 **     the script. The result of the script is stored in the variable
139 **     VARNAME.
140 **
141 **     The caller can wait for the script to terminate using [vwait VARNAME].
142 */
143 static int sqlthread_spawn(
144   ClientData clientData,
145   Tcl_Interp *interp,
146   int objc,
147   Tcl_Obj *CONST objv[]
148 ){
149   Tcl_ThreadId x;
150   SqlThread *pNew;
151   int rc;
152 
153   int nVarname; char *zVarname;
154   int nScript; char *zScript;
155 
156   /* Parameters for thread creation */
157   const int nStack = TCL_THREAD_STACK_DEFAULT;
158   const int flags = TCL_THREAD_NOFLAGS;
159 
160   assert(objc==4);
161 
162   zVarname = Tcl_GetStringFromObj(objv[2], &nVarname);
163   zScript = Tcl_GetStringFromObj(objv[3], &nScript);
164 
165   pNew = (SqlThread *)ckalloc(sizeof(SqlThread)+nVarname+nScript+2);
166   pNew->zVarname = (char *)&pNew[1];
167   pNew->zScript = (char *)&pNew->zVarname[nVarname+1];
168   memcpy(pNew->zVarname, zVarname, nVarname+1);
169   memcpy(pNew->zScript, zScript, nScript+1);
170   pNew->parent = Tcl_GetCurrentThread();
171   pNew->interp = interp;
172 
173   rc = Tcl_CreateThread(&x, tclScriptThread, (void *)pNew, nStack, flags);
174   if( rc!=TCL_OK ){
175     Tcl_AppendResult(interp, "Error in Tcl_CreateThread()", 0);
176     ckfree((char *)pNew);
177     return TCL_ERROR;
178   }
179 
180   return TCL_OK;
181 }
182 
183 /*
184 ** sqlthread parent SCRIPT
185 **
186 **     This can be called by spawned threads only. It sends the specified
187 **     script back to the parent thread for execution. The result of
188 **     evaluating the SCRIPT is returned. The parent thread must enter
189 **     the event loop for this to work - otherwise the caller will
190 **     block indefinitely.
191 **
192 **     NOTE: At the moment, this doesn't work. FIXME.
193 */
194 static int sqlthread_parent(
195   ClientData clientData,
196   Tcl_Interp *interp,
197   int objc,
198   Tcl_Obj *CONST objv[]
199 ){
200   EvalEvent *pEvent;
201   char *zMsg;
202   int nMsg;
203   SqlThread *p = (SqlThread *)clientData;
204 
205   assert(objc==3);
206   if( p==0 ){
207     Tcl_AppendResult(interp, "no parent thread", 0);
208     return TCL_ERROR;
209   }
210 
211   zMsg = Tcl_GetStringFromObj(objv[2], &nMsg);
212   pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1);
213   pEvent->base.nextPtr = 0;
214   pEvent->base.proc = tclScriptEvent;
215   pEvent->zScript = (char *)&pEvent[1];
216   memcpy(pEvent->zScript, zMsg, nMsg+1);
217   pEvent->interp = p->interp;
218   Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL);
219   Tcl_ThreadAlert(p->parent);
220 
221   return TCL_OK;
222 }
223 
224 static int xBusy(void *pArg, int nBusy){
225   sqlite3_sleep(50);
226   return 1;             /* Try again... */
227 }
228 
229 /*
230 ** sqlthread open
231 **
232 **     Open a database handle and return the string representation of
233 **     the pointer value.
234 */
235 static int sqlthread_open(
236   ClientData clientData,
237   Tcl_Interp *interp,
238   int objc,
239   Tcl_Obj *CONST objv[]
240 ){
241   int sqlite3TestMakePointerStr(Tcl_Interp *interp, char *zPtr, void *p);
242 
243   const char *zFilename;
244   sqlite3 *db;
245   int rc;
246   char zBuf[100];
247   extern void Md5_Register(sqlite3*);
248 
249   zFilename = Tcl_GetString(objv[2]);
250   rc = sqlite3_open(zFilename, &db);
251   Md5_Register(db);
252   sqlite3_busy_handler(db, xBusy, 0);
253 
254   if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR;
255   Tcl_AppendResult(interp, zBuf, 0);
256 
257   return TCL_OK;
258 }
259 
260 
261 /*
262 ** sqlthread open
263 **
264 **     Return the current thread-id (Tcl_GetCurrentThread()) cast to
265 **     an integer.
266 */
267 static int sqlthread_id(
268   ClientData clientData,
269   Tcl_Interp *interp,
270   int objc,
271   Tcl_Obj *CONST objv[]
272 ){
273   Tcl_ThreadId id = Tcl_GetCurrentThread();
274   Tcl_SetObjResult(interp, Tcl_NewIntObj((int)id));
275   return TCL_OK;
276 }
277 
278 
279 /*
280 ** Dispatch routine for the sub-commands of [sqlthread].
281 */
282 static int sqlthread_proc(
283   ClientData clientData,
284   Tcl_Interp *interp,
285   int objc,
286   Tcl_Obj *CONST objv[]
287 ){
288   struct SubCommand {
289     char *zName;
290     Tcl_ObjCmdProc *xProc;
291     int nArg;
292     char *zUsage;
293   } aSub[] = {
294     {"parent", sqlthread_parent, 1, "SCRIPT"},
295     {"spawn",  sqlthread_spawn,  2, "VARNAME SCRIPT"},
296     {"open",   sqlthread_open,   1, "DBNAME"},
297     {"id",     sqlthread_id,     0, ""},
298     {0, 0, 0}
299   };
300   struct SubCommand *pSub;
301   int rc;
302   int iIndex;
303 
304   if( objc<2 ){
305     Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND");
306     return TCL_ERROR;
307   }
308 
309   rc = Tcl_GetIndexFromObjStruct(
310       interp, objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iIndex
311   );
312   if( rc!=TCL_OK ) return rc;
313   pSub = &aSub[iIndex];
314 
315   if( objc!=(pSub->nArg+2) ){
316     Tcl_WrongNumArgs(interp, 2, objv, pSub->zUsage);
317     return TCL_ERROR;
318   }
319 
320   return pSub->xProc(clientData, interp, objc, objv);
321 }
322 
323 /*
324 ** The [clock_seconds] command. This is more or less the same as the
325 ** regular tcl [clock seconds], except that it is available in testfixture
326 ** when linked against both Tcl 8.4 and 8.5. Because [clock seconds] is
327 ** implemented as a script in Tcl 8.5, it is not usually available to
328 ** testfixture.
329 */
330 static int clock_seconds_proc(
331   ClientData clientData,
332   Tcl_Interp *interp,
333   int objc,
334   Tcl_Obj *CONST objv[]
335 ){
336   Tcl_Time now;
337   Tcl_GetTime(&now);
338   Tcl_SetObjResult(interp, Tcl_NewIntObj(now.sec));
339   return TCL_OK;
340 }
341 
342 /*
343 ** Register commands with the TCL interpreter.
344 */
345 int SqlitetestThread_Init(Tcl_Interp *interp){
346   Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0);
347   Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);
348   return TCL_OK;
349 }
350 #else
351 int SqlitetestThread_Init(Tcl_Interp *interp){
352   return TCL_OK;
353 }
354 #endif
355