/*** 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.*/#include "sqliteInt.h"#if defined(INCLUDE_SQLITE_TCL_H)# include "sqlite_tcl.h"#else# include "tcl.h"#endif#if SQLITE_THREADSAFE#include <errno.h>#if !defined(_MSC_VER)#include <unistd.h>#endif/*** One of these is allocated for each thread created by [sqlthread spawn].*/typedef struct SqlThread SqlThread;struct SqlThread {Tcl_ThreadId parent; /* Thread id of parent thread */Tcl_Interp *interp; /* Parent interpreter */char *zScript; /* The script to execute. */char *zVarname; /* Varname in parent script */};/*** A custom Tcl_Event type used by this module. When the event is** handled, script zScript is evaluated in interpreter interp. If** the evaluation throws an exception (returns TCL_ERROR), then the** error is handled by Tcl_BackgroundError(). If no error occurs,** the result is simply discarded.*/typedef struct EvalEvent EvalEvent;struct EvalEvent {Tcl_Event base; /* Base class of type Tcl_Event */char *zScript; /* The script to execute. */Tcl_Interp *interp; /* The interpreter to execute it in. */};static Tcl_ObjCmdProc sqlthread_proc;static Tcl_ObjCmdProc clock_seconds_proc;#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)static Tcl_ObjCmdProc blocking_step_proc;static Tcl_ObjCmdProc blocking_prepare_v2_proc;#endifint Sqlitetest1_Init(Tcl_Interp *);int Sqlite3_Init(Tcl_Interp *);/* Functions from main.c */extern const char *sqlite3ErrName(int);/* Functions from test1.c */extern void *sqlite3TestTextToPtr(const char *);extern int getDbPointer(Tcl_Interp *, const char *, sqlite3 **);extern int sqlite3TestMakePointerStr(Tcl_Interp *, char *, void *);extern int sqlite3TestErrCode(Tcl_Interp *, sqlite3 *, int);/*** Handler for events of type EvalEvent.*/static int SQLITE_TCLAPI tclScriptEvent(Tcl_Event *evPtr, int flags){int rc;EvalEvent *p = (EvalEvent *)evPtr;rc = Tcl_Eval(p->interp, p->zScript);if( rc!=TCL_OK ){Tcl_BackgroundError(p->interp);}UNUSED_PARAMETER(flags);return 1;}/*** Register an EvalEvent to evaluate the script pScript in the** parent interpreter/thread of SqlThread p.*/static void postToParent(SqlThread *p, Tcl_Obj *pScript){EvalEvent *pEvent;char *zMsg;int nMsg;zMsg = Tcl_GetStringFromObj(pScript, &nMsg);pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1);pEvent->base.nextPtr = 0;pEvent->base.proc = tclScriptEvent;pEvent->zScript = (char *)&pEvent[1];memcpy(pEvent->zScript, zMsg, nMsg+1);pEvent->interp = p->interp;Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL);Tcl_ThreadAlert(p->parent);}/*** The main function for threads created with [sqlthread spawn].*/static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){Tcl_Interp *interp;Tcl_Obj *pRes;Tcl_Obj *pList;int rc;SqlThread *p = (SqlThread *)pSqlThread;extern int Sqlitetest_mutex_Init(Tcl_Interp*);interp = Tcl_CreateInterp();Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0);#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0);Tcl_CreateObjCommand(interp,"sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0);Tcl_CreateObjCommand(interp,"sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0);#endifSqlitetest1_Init(interp);Sqlitetest_mutex_Init(interp);Sqlite3_Init(interp);rc = Tcl_Eval(interp, p->zScript);pRes = Tcl_GetObjResult(interp);pList = Tcl_NewObj();Tcl_IncrRefCount(pList);Tcl_IncrRefCount(pRes);if( rc!=TCL_OK ){Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("error", -1));Tcl_ListObjAppendElement(interp, pList, pRes);postToParent(p, pList);Tcl_DecrRefCount(pList);pList = Tcl_NewObj();}Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("set", -1));Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(p->zVarname, -1));Tcl_ListObjAppendElement(interp, pList, pRes);postToParent(p, pList);ckfree((void *)p);Tcl_DecrRefCount(pList);Tcl_DecrRefCount(pRes);Tcl_DeleteInterp(interp);while( Tcl_DoOneEvent(TCL_ALL_EVENTS|TCL_DONT_WAIT) );Tcl_ExitThread(0);TCL_THREAD_CREATE_RETURN;}/*** sqlthread spawn VARNAME SCRIPT**** Spawn a new thread with its 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 SQLITE_TCLAPI sqlthread_spawn(ClientData clientData,Tcl_Interp *interp,int objc,Tcl_Obj *CONST objv[]){Tcl_ThreadId x;SqlThread *pNew;int rc;int nVarname; char *zVarname;int nScript; char *zScript;/* Parameters for thread creation */const int nStack = TCL_THREAD_STACK_DEFAULT;const int flags = TCL_THREAD_NOFLAGS;assert(objc==4);UNUSED_PARAMETER(clientData);UNUSED_PARAMETER(objc);zVarname = Tcl_GetStringFromObj(objv[2], &nVarname);zScript = Tcl_GetStringFromObj(objv[3], &nScript);pNew = (SqlThread *)ckalloc(sizeof(SqlThread)+nVarname+nScript+2);pNew->zVarname = (char *)&pNew[1];pNew->zScript = (char *)&pNew->zVarname[nVarname+1];memcpy(pNew->zVarname, zVarname, nVarname+1);memcpy(pNew->zScript, zScript, nScript+1);pNew->parent = Tcl_GetCurrentThread();pNew->interp = interp;rc = Tcl_CreateThread(&x, tclScriptThread, (void *)pNew, nStack, flags);if( rc!=TCL_OK ){Tcl_AppendResult(interp, "Error in Tcl_CreateThread()", 0);ckfree((char *)pNew);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.*/static int SQLITE_TCLAPI sqlthread_parent(ClientData clientData,Tcl_Interp *interp,int objc,Tcl_Obj *CONST objv[]){EvalEvent *pEvent;char *zMsg;int nMsg;SqlThread *p = (SqlThread *)clientData;assert(objc==3);UNUSED_PARAMETER(objc);if( p==0 ){Tcl_AppendResult(interp, "no parent thread", 0);return TCL_ERROR;}zMsg = Tcl_GetStringFromObj(objv[2], &nMsg);pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1);pEvent->base.nextPtr = 0;pEvent->base.proc = tclScriptEvent;pEvent->zScript = (char *)&pEvent[1];memcpy(pEvent->zScript, zMsg, nMsg+1);pEvent->interp = p->interp;Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL);Tcl_ThreadAlert(p->parent);return TCL_OK;}static int xBusy(void *pArg, int nBusy){UNUSED_PARAMETER(pArg);UNUSED_PARAMETER(nBusy);sqlite3_sleep(50);return 1; /* Try again... */}/*** sqlthread open**** Open a database handle and return the string representation of** the pointer value.*/static int SQLITE_TCLAPI sqlthread_open(ClientData clientData,Tcl_Interp *interp,int objc,Tcl_Obj *CONST objv[]){int sqlite3TestMakePointerStr(Tcl_Interp *interp, char *zPtr, void *p);const char *zFilename;sqlite3 *db;char zBuf[100];extern int Md5_Register(sqlite3*,char**,const sqlite3_api_routines*);UNUSED_PARAMETER(clientData);UNUSED_PARAMETER(objc);zFilename = Tcl_GetString(objv[2]);sqlite3_open(zFilename, &db);Md5_Register(db, 0, 0);sqlite3_busy_handler(db, xBusy, 0);if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR;Tcl_AppendResult(interp, zBuf, 0);return TCL_OK;}/*** sqlthread open**** Return the current thread-id (Tcl_GetCurrentThread()) cast to** an integer.*/static int SQLITE_TCLAPI sqlthread_id(ClientData clientData,Tcl_Interp *interp,int objc,Tcl_Obj *CONST objv[]){Tcl_ThreadId id = Tcl_GetCurrentThread();Tcl_SetObjResult(interp, Tcl_NewIntObj(SQLITE_PTR_TO_INT(id)));UNUSED_PARAMETER(clientData);UNUSED_PARAMETER(objc);UNUSED_PARAMETER(objv);return TCL_OK;}/*** Dispatch routine for the sub-commands of [sqlthread].*/static int SQLITE_TCLAPI 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[] = {{"parent", sqlthread_parent, 1, "SCRIPT"},{"spawn", sqlthread_spawn, 2, "VARNAME SCRIPT"},{"open", sqlthread_open, 1, "DBNAME"},{"id", sqlthread_id, 0, ""},{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);}/*** The [clock_seconds] command. This is more or less the same as the** regular tcl [clock seconds], except that it is available in testfixture** when linked against both Tcl 8.4 and 8.5. Because [clock seconds] is** implemented as a script in Tcl 8.5, it is not usually available to** testfixture.*/static int SQLITE_TCLAPI clock_seconds_proc(ClientData clientData,Tcl_Interp *interp,int objc,Tcl_Obj *CONST objv[]){Tcl_Time now;Tcl_GetTime(&now);Tcl_SetObjResult(interp, Tcl_NewIntObj(now.sec));UNUSED_PARAMETER(clientData);UNUSED_PARAMETER(objc);UNUSED_PARAMETER(objv);return TCL_OK;}/*** The [clock_milliseconds] command. This is more or less the same as the** regular tcl [clock milliseconds].*/static int SQLITE_TCLAPI clock_milliseconds_proc(ClientData clientData,Tcl_Interp *interp,int objc,Tcl_Obj *CONST objv[]){Tcl_Time now;Tcl_GetTime(&now);Tcl_SetObjResult(interp, Tcl_NewWideIntObj(((Tcl_WideInt)now.sec * 1000) + (now.usec / 1000)));UNUSED_PARAMETER(clientData);UNUSED_PARAMETER(objc);UNUSED_PARAMETER(objv);return TCL_OK;}/*************************************************************************** This block contains the implementation of the [sqlite3_blocking_step]** command available to threads created by [sqlthread spawn] commands. It** is only available on UNIX for now. This is because pthread condition** variables are used.**** The source code for the C functions sqlite3_blocking_step(),** blocking_step_notify() and the structure UnlockNotification is** automatically extracted from this file and used as part of the** documentation for the sqlite3_unlock_notify() API function. This** should be considered if these functions are to be extended (i.e. to** support windows) in the future.*/#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)/* BEGIN_SQLITE_BLOCKING_STEP *//* This example uses the pthreads API */#include <pthread.h>/*** A pointer to an instance of this structure is passed as the user-context** pointer when registering for an unlock-notify callback.*/typedef struct UnlockNotification UnlockNotification;struct UnlockNotification {int fired; /* True after unlock event has occurred */pthread_cond_t cond; /* Condition variable to wait on */pthread_mutex_t mutex; /* Mutex to protect structure */};/*** This function is an unlock-notify callback registered with SQLite.*/static void unlock_notify_cb(void **apArg, int nArg){int i;for(i=0; i<nArg; i++){UnlockNotification *p = (UnlockNotification *)apArg[i];pthread_mutex_lock(&p->mutex);p->fired = 1;pthread_cond_signal(&p->cond);pthread_mutex_unlock(&p->mutex);}}/*** This function assumes that an SQLite API call (either sqlite3_prepare_v2()** or sqlite3_step()) has just returned SQLITE_LOCKED. The argument is the** associated database connection.**** This function calls sqlite3_unlock_notify() to register for an** unlock-notify callback, then blocks until that callback is delivered** and returns SQLITE_OK. The caller should then retry the failed operation.**** Or, if sqlite3_unlock_notify() indicates that to block would deadlock** the system, then this function returns SQLITE_LOCKED immediately. In** this case the caller should not retry the operation and should roll** back the current transaction (if any).*/static int wait_for_unlock_notify(sqlite3 *db){int rc;UnlockNotification un;/* Initialize the UnlockNotification structure. */un.fired = 0;pthread_mutex_init(&un.mutex, 0);pthread_cond_init(&un.cond, 0);/* Register for an unlock-notify callback. */rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un);assert( rc==SQLITE_LOCKED || rc==SQLITE_OK );/* The call to sqlite3_unlock_notify() always returns either SQLITE_LOCKED** or SQLITE_OK.**** If SQLITE_LOCKED was returned, then the system is deadlocked. In this** case this function needs to return SQLITE_LOCKED to the caller so** that the current transaction can be rolled back. Otherwise, block** until the unlock-notify callback is invoked, then return SQLITE_OK.*/if( rc==SQLITE_OK ){pthread_mutex_lock(&un.mutex);if( !un.fired ){pthread_cond_wait(&un.cond, &un.mutex);}pthread_mutex_unlock(&un.mutex);}/* Destroy the mutex and condition variables. */pthread_cond_destroy(&un.cond);pthread_mutex_destroy(&un.mutex);return rc;}/*** This function is a wrapper around the SQLite function sqlite3_step().** It functions in the same way as step(), except that if a required** shared-cache lock cannot be obtained, this function may block waiting for** the lock to become available. In this scenario the normal API step()** function always returns SQLITE_LOCKED.**** If this function returns SQLITE_LOCKED, the caller should rollback** the current transaction (if any) and try again later. Otherwise, the** system may become deadlocked.*/int sqlite3_blocking_step(sqlite3_stmt *pStmt){int rc;while( SQLITE_LOCKED==(rc = sqlite3_step(pStmt)) ){rc = wait_for_unlock_notify(sqlite3_db_handle(pStmt));if( rc!=SQLITE_OK ) break;sqlite3_reset(pStmt);}return rc;}/*** This function is a wrapper around the SQLite function sqlite3_prepare_v2().** It functions in the same way as prepare_v2(), except that if a required** shared-cache lock cannot be obtained, this function may block waiting for** the lock to become available. In this scenario the normal API prepare_v2()** function always returns SQLITE_LOCKED.**** If this function returns SQLITE_LOCKED, the caller should rollback** the current transaction (if any) and try again later. Otherwise, the** system may become deadlocked.*/int sqlite3_blocking_prepare_v2(sqlite3 *db, /* Database handle. */const char *zSql, /* UTF-8 encoded SQL statement. */int nSql, /* Length of zSql in bytes. */sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */const char **pz /* OUT: End of parsed string */){int rc;while( SQLITE_LOCKED==(rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, pz)) ){rc = wait_for_unlock_notify(db);if( rc!=SQLITE_OK ) break;}return rc;}/* END_SQLITE_BLOCKING_STEP *//*** Usage: sqlite3_blocking_step STMT**** Advance the statement to the next row.*/static int SQLITE_TCLAPI blocking_step_proc(void * clientData,Tcl_Interp *interp,int objc,Tcl_Obj *CONST objv[]){sqlite3_stmt *pStmt;int rc;if( objc!=2 ){Tcl_WrongNumArgs(interp, 1, objv, "STMT");return TCL_ERROR;}pStmt = (sqlite3_stmt*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));rc = sqlite3_blocking_step(pStmt);Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), 0);return TCL_OK;}/*** Usage: sqlite3_blocking_prepare_v2 DB sql bytes ?tailvar?** Usage: sqlite3_nonblocking_prepare_v2 DB sql bytes ?tailvar?*/static int SQLITE_TCLAPI blocking_prepare_v2_proc(void * clientData,Tcl_Interp *interp,int objc,Tcl_Obj *CONST objv[]){sqlite3 *db;const char *zSql;int bytes;const char *zTail = 0;sqlite3_stmt *pStmt = 0;char zBuf[50];int rc;int isBlocking = !(clientData==0);if( objc!=5 && objc!=4 ){Tcl_AppendResult(interp, "wrong # args: should be \"",Tcl_GetString(objv[0]), " DB sql bytes tailvar", 0);return TCL_ERROR;}if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;zSql = Tcl_GetString(objv[2]);if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR;if( isBlocking ){rc = sqlite3_blocking_prepare_v2(db, zSql, bytes, &pStmt, &zTail);}else{rc = sqlite3_prepare_v2(db, zSql, bytes, &pStmt, &zTail);}assert(rc==SQLITE_OK || pStmt==0);if( zTail && objc>=5 ){if( bytes>=0 ){bytes = bytes - (zTail-zSql);}Tcl_ObjSetVar2(interp, objv[4], 0, Tcl_NewStringObj(zTail, bytes), 0);}if( rc!=SQLITE_OK ){assert( pStmt==0 );sqlite3_snprintf(sizeof(zBuf), zBuf, "%s ", (char *)sqlite3ErrName(rc));Tcl_AppendResult(interp, zBuf, sqlite3_errmsg(db), 0);return TCL_ERROR;}if( pStmt ){if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;Tcl_AppendResult(interp, zBuf, 0);}return TCL_OK;}#endif /* SQLITE_OS_UNIX && SQLITE_ENABLE_UNLOCK_NOTIFY *//*** End of implementation of [sqlite3_blocking_step].************************************************************************//*** Register commands with the TCL interpreter.*/int SqlitetestThread_Init(Tcl_Interp *interp){struct TclCmd {int (*xProc)(void*, Tcl_Interp*, int, Tcl_Obj*const*);const char *zName;int iCtx;} aCmd[] = {{ sqlthread_proc, "sqlthread", 0 },{ clock_seconds_proc, "clock_second", 0 },{ clock_milliseconds_proc, "clock_milliseconds", 0 },#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY){ blocking_step_proc, "sqlite3_blocking_step", 0 },{ blocking_prepare_v2_proc, "sqlite3_blocking_prepare_v2", 1 },{ blocking_prepare_v2_proc, "sqlite3_nonblocking_prepare_v2", 0 },#endif};int ii;for(ii=0; ii<sizeof(aCmd)/sizeof(aCmd[0]); ii++){void *p = SQLITE_INT_TO_PTR(aCmd[ii].iCtx);Tcl_CreateObjCommand(interp, aCmd[ii].zName, aCmd[ii].xProc, p, 0);}return TCL_OK;}#elseint SqlitetestThread_Init(Tcl_Interp *interp){return TCL_OK;}#endif
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。