/* ** 2008 June 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 contains test logic for the sqlite4_mutex interfaces. */ #include "tcl.h" #include "sqlite4.h" #include "sqliteInt.h" #include #include #include /* defined in test1.c */ const char *sqlite4TestErrorName(int); /* A countable mutex */ typedef struct sqlite4CounterMutex { sqlite4_mutex base; /* Base class. Must be first */ sqlite4_mutex *pReal; int eType; } sqlite4CounterMutex; /* State variables */ static struct test_mutex_globals { int isInstalled; /* True if installed */ int disableInit; /* True to cause sqlite4_initalize() to fail */ int disableTry; /* True to force sqlite4_mutex_try() to fail */ int isInit; /* True if initialized */ sqlite4_mutex_methods m; /* Interface to "real" mutex system */ int aCounter[8]; /* Number of grabs of each type of mutex */ } g = {0}; /* Return true if the countable mutex is currently held */ static int counterMutexHeld(sqlite4_mutex *pMutex){ sqlite4CounterMutex *p = (sqlite4CounterMutex*)pMutex; return g.m.xMutexHeld(p->pReal); } /* Return true if the countable mutex is not currently held */ static int counterMutexNotheld(sqlite4_mutex *pMutex){ sqlite4CounterMutex *p = (sqlite4CounterMutex*)pMutex; return g.m.xMutexNotheld(p->pReal); } /* Initialize the countable mutex interface ** Or, if g.disableInit is non-zero, then do not initialize but instead ** return the value of g.disableInit as the result code. This can be used ** to simulate an initialization failure. */ static int counterMutexInit(void *p){ int rc; if( g.disableInit ) return g.disableInit; rc = g.m.xMutexInit(p); g.isInit = 1; return rc; } /* ** Uninitialize the mutex subsystem */ static int counterMutexEnd(void *p){ g.isInit = 0; return g.m.xMutexEnd(p); } /* ** Allocate a countable mutex */ static sqlite4_mutex *counterMutexAlloc(void *pMutexEnv, int eType){ sqlite4_mutex *pReal; sqlite4CounterMutex *pRet = 0; assert( g.isInit ); assert(eType<8 && eType>=0); pReal = g.m.xMutexAlloc(g.m.pMutexEnv, eType); if( !pReal ) return 0; pRet = (sqlite4CounterMutex *)malloc(sizeof(*pRet)); pRet->eType = eType; pRet->pReal = pReal; return (sqlite4_mutex*)pRet; } /* ** Free a countable mutex */ static void counterMutexFree(sqlite4_mutex *pMutex){ sqlite4CounterMutex *p = (sqlite4CounterMutex*)pMutex; assert( g.isInit ); g.m.xMutexFree(p->pReal); if( p->eType==SQLITE4_MUTEX_FAST || p->eType==SQLITE4_MUTEX_RECURSIVE ){ free(p); } } /* ** Enter a countable mutex. Block until entry is safe. */ static void counterMutexEnter(sqlite4_mutex *pMutex){ sqlite4CounterMutex *p = (sqlite4CounterMutex*)pMutex; assert( g.isInit ); g.aCounter[p->eType]++; g.m.xMutexEnter(p->pReal); } /* ** Try to enter a mutex. Return true on success. */ static int counterMutexTry(sqlite4_mutex *pMutex){ sqlite4CounterMutex *p = (sqlite4CounterMutex*)pMutex; assert( g.isInit ); g.aCounter[p->eType]++; if( g.disableTry ) return SQLITE4_BUSY; return g.m.xMutexTry(p->pReal); } /* Leave a mutex */ static void counterMutexLeave(sqlite4_mutex *pMutex){ sqlite4CounterMutex *p = (sqlite4CounterMutex*)pMutex; assert( g.isInit ); g.m.xMutexLeave(p->pReal); } /* ** sqlite4_shutdown */ static int test_shutdown( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ int rc; if( objc!=1 ){ Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } rc = sqlite4_shutdown(0); Tcl_SetResult(interp, (char *)sqlite4TestErrorName(rc), TCL_VOLATILE); return TCL_OK; } /* ** sqlite4_initialize */ static int test_initialize( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ int rc; if( objc!=1 ){ Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } rc = sqlite4_initialize(0); Tcl_SetResult(interp, (char *)sqlite4TestErrorName(rc), TCL_VOLATILE); return TCL_OK; } /* ** install_mutex_counters BOOLEAN */ static int test_install_mutex_counters( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ int rc = SQLITE4_OK; int isInstall; sqlite4_mutex_methods counter_methods = { counterMutexInit, counterMutexEnd, counterMutexAlloc, counterMutexFree, counterMutexEnter, counterMutexTry, counterMutexLeave, counterMutexHeld, counterMutexNotheld, 0 }; if( objc!=2 ){ Tcl_WrongNumArgs(interp, 1, objv, "BOOLEAN"); return TCL_ERROR; } if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[1], &isInstall) ){ return TCL_ERROR; } assert(isInstall==0 || isInstall==1); assert(g.isInstalled==0 || g.isInstalled==1); if( isInstall==g.isInstalled ){ Tcl_AppendResult(interp, "mutex counters are ", 0); Tcl_AppendResult(interp, isInstall?"already installed":"not installed", 0); return TCL_ERROR; } if( isInstall ){ assert( g.m.xMutexAlloc==0 ); rc = sqlite4_env_config(0, SQLITE4_ENVCONFIG_GETMUTEX, &g.m); if( rc==SQLITE4_OK ){ sqlite4_env_config(0, SQLITE4_ENVCONFIG_MUTEX, &counter_methods); } g.disableTry = 0; }else{ assert( g.m.xMutexAlloc ); rc = sqlite4_env_config(0, SQLITE4_ENVCONFIG_MUTEX, &g.m); memset(&g.m, 0, sizeof(sqlite4_mutex_methods)); } if( rc==SQLITE4_OK ){ g.isInstalled = isInstall; } Tcl_SetResult(interp, (char *)sqlite4TestErrorName(rc), TCL_VOLATILE); return TCL_OK; } /* ** read_mutex_counters */ static int test_read_mutex_counters( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ Tcl_Obj *pRet; int ii; char *aName[8] = { "fast", "recursive", "static_master", "static_mem", "static_open", "static_prng", "static_lru", "static_pmem" }; if( objc!=1 ){ Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } pRet = Tcl_NewObj(); Tcl_IncrRefCount(pRet); for(ii=0; ii<8; ii++){ Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj(aName[ii], -1)); Tcl_ListObjAppendElement(interp, pRet, Tcl_NewIntObj(g.aCounter[ii])); } Tcl_SetObjResult(interp, pRet); Tcl_DecrRefCount(pRet); return TCL_OK; } /* ** clear_mutex_counters */ static int test_clear_mutex_counters( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ int ii; if( objc!=1 ){ Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } for(ii=0; ii<8; ii++){ g.aCounter[ii] = 0; } return TCL_OK; } /* ** Create and free a mutex. Return the mutex pointer. The pointer ** will be invalid since the mutex has already been freed. The ** return pointer just checks to see if the mutex really was allocated. */ static int test_alloc_mutex( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ #if SQLITE4_THREADSAFE sqlite4_mutex *p = sqlite4_mutex_alloc(0, SQLITE4_MUTEX_FAST); char zBuf[100]; sqlite4_mutex_free(p); sqlite4_snprintf(zBuf, sizeof(zBuf), "%p", p); Tcl_AppendResult(interp, zBuf, (char*)0); #endif return TCL_OK; } /* ** sqlite4_env_config OPTION ** ** OPTION can be either one of the keywords: ** ** SQLITE4_CONFIG_SINGLETHREAD ** SQLITE4_CONFIG_MULTITHREAD ** SQLITE4_CONFIG_SERIALIZED ** ** Or OPTION can be an raw integer. */ static int test_config( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ struct ConfigOption { const char *zName; int iValue; } aOpt[] = { {"singlethread", SQLITE4_ENVCONFIG_SINGLETHREAD}, {"multithread", SQLITE4_ENVCONFIG_MULTITHREAD}, {"serialized", SQLITE4_ENVCONFIG_SERIALIZED}, {0, 0} }; int s = sizeof(struct ConfigOption); int i; int rc; if( objc!=2 ){ Tcl_WrongNumArgs(interp, 1, objv, ""); return TCL_ERROR; } if( Tcl_GetIndexFromObjStruct(interp, objv[1], aOpt, s, "flag", 0, &i) ){ if( Tcl_GetIntFromObj(interp, objv[1], &i) ){ return TCL_ERROR; } }else{ i = aOpt[i].iValue; } rc = sqlite4_env_config(0, i); Tcl_SetResult(interp, (char *)sqlite4TestErrorName(rc), TCL_VOLATILE); return TCL_OK; } static sqlite4 *getDbPointer(Tcl_Interp *pInterp, Tcl_Obj *pObj){ sqlite4 *db; Tcl_CmdInfo info; char *zCmd = Tcl_GetString(pObj); if( Tcl_GetCommandInfo(pInterp, zCmd, &info) ){ db = *((sqlite4 **)info.objClientData); }else{ db = (sqlite4*)sqlite4TestTextToPtr(zCmd); } assert( db ); return db; } static int test_enter_db_mutex( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ sqlite4 *db; if( objc!=2 ){ Tcl_WrongNumArgs(interp, 1, objv, "DB"); return TCL_ERROR; } db = getDbPointer(interp, objv[1]); if( !db ){ return TCL_ERROR; } sqlite4_mutex_enter(sqlite4_db_mutex(db)); return TCL_OK; } static int test_leave_db_mutex( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ sqlite4 *db; if( objc!=2 ){ Tcl_WrongNumArgs(interp, 1, objv, "DB"); return TCL_ERROR; } db = getDbPointer(interp, objv[1]); if( !db ){ return TCL_ERROR; } sqlite4_mutex_leave(sqlite4_db_mutex(db)); return TCL_OK; } int Sqlitetest_mutex_Init(Tcl_Interp *interp){ static struct { char *zName; Tcl_ObjCmdProc *xProc; } aCmd[] = { { "sqlite4_shutdown", (Tcl_ObjCmdProc*)test_shutdown }, { "sqlite4_initialize", (Tcl_ObjCmdProc*)test_initialize }, { "sqlite4_env_config", (Tcl_ObjCmdProc*)test_config }, { "enter_db_mutex", (Tcl_ObjCmdProc*)test_enter_db_mutex }, { "leave_db_mutex", (Tcl_ObjCmdProc*)test_leave_db_mutex }, { "alloc_dealloc_mutex", (Tcl_ObjCmdProc*)test_alloc_mutex }, { "install_mutex_counters", (Tcl_ObjCmdProc*)test_install_mutex_counters }, { "read_mutex_counters", (Tcl_ObjCmdProc*)test_read_mutex_counters }, { "clear_mutex_counters", (Tcl_ObjCmdProc*)test_clear_mutex_counters }, }; int i; for(i=0; i