/* ** 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 sqlite3_mutex interfaces. */ #include "tcl.h" #include "sqlite3.h" #include "sqliteInt.h" #include #include #include /* defined in test1.c */ const char *sqlite3TestErrorName(int); /* A countable mutex */ struct sqlite3_mutex { sqlite3_mutex *pReal; int eType; }; /* State variables */ static struct test_mutex_globals { int isInstalled; /* True if installed */ int disableInit; /* True to cause sqlite3_initalize() to fail */ int disableTry; /* True to force sqlite3_mutex_try() to fail */ int isInit; /* True if initialized */ sqlite3_mutex_methods m; /* Interface to "real" mutex system */ int aCounter[8]; /* Number of grabs of each type of mutex */ sqlite3_mutex aStatic[6]; /* The six static mutexes */ } g = {0}; /* Return true if the countable mutex is currently held */ static int counterMutexHeld(sqlite3_mutex *p){ return g.m.xMutexHeld(p->pReal); } /* Return true if the countable mutex is not currently held */ static int counterMutexNotheld(sqlite3_mutex *p){ 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){ int rc; if( g.disableInit ) return g.disableInit; rc = g.m.xMutexInit(); g.isInit = 1; return rc; } /* ** Uninitialize the mutex subsystem */ static int counterMutexEnd(void){ g.isInit = 0; return g.m.xMutexEnd(); } /* ** Allocate a countable mutex */ static sqlite3_mutex *counterMutexAlloc(int eType){ sqlite3_mutex *pReal; sqlite3_mutex *pRet = 0; assert( g.isInit ); assert(eType<8 && eType>=0); pReal = g.m.xMutexAlloc(eType); if( !pReal ) return 0; if( eType==SQLITE_MUTEX_FAST || eType==SQLITE_MUTEX_RECURSIVE ){ pRet = (sqlite3_mutex *)malloc(sizeof(sqlite3_mutex)); }else{ pRet = &g.aStatic[eType-2]; } pRet->eType = eType; pRet->pReal = pReal; return pRet; } /* ** Free a countable mutex */ static void counterMutexFree(sqlite3_mutex *p){ assert( g.isInit ); g.m.xMutexFree(p->pReal); if( p->eType==SQLITE_MUTEX_FAST || p->eType==SQLITE_MUTEX_RECURSIVE ){ free(p); } } /* ** Enter a countable mutex. Block until entry is safe. */ static void counterMutexEnter(sqlite3_mutex *p){ assert( g.isInit ); g.aCounter[p->eType]++; g.m.xMutexEnter(p->pReal); } /* ** Try to enter a mutex. Return true on success. */ static int counterMutexTry(sqlite3_mutex *p){ assert( g.isInit ); g.aCounter[p->eType]++; if( g.disableTry ) return SQLITE_BUSY; return g.m.xMutexTry(p->pReal); } /* Leave a mutex */ static void counterMutexLeave(sqlite3_mutex *p){ assert( g.isInit ); g.m.xMutexLeave(p->pReal); } /* ** sqlite3_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 = sqlite3_shutdown(); Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_VOLATILE); return TCL_OK; } /* ** sqlite3_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 = sqlite3_initialize(); Tcl_SetResult(interp, (char *)sqlite3TestErrorName(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 = SQLITE_OK; int isInstall; sqlite3_mutex_methods counter_methods = { counterMutexInit, counterMutexEnd, counterMutexAlloc, counterMutexFree, counterMutexEnter, counterMutexTry, counterMutexLeave, counterMutexHeld, counterMutexNotheld }; 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 = sqlite3_config(SQLITE_CONFIG_GETMUTEX, &g.m); if( rc==SQLITE_OK ){ sqlite3_config(SQLITE_CONFIG_MUTEX, &counter_methods); } g.disableTry = 0; }else{ assert( g.m.xMutexAlloc ); rc = sqlite3_config(SQLITE_CONFIG_MUTEX, &g.m); memset(&g.m, 0, sizeof(sqlite3_mutex_methods)); } if( rc==SQLITE_OK ){ g.isInstalled = isInstall; } Tcl_SetResult(interp, (char *)sqlite3TestErrorName(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 SQLITE_THREADSAFE sqlite3_mutex *p = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); char zBuf[100]; sqlite3_mutex_free(p); sqlite3_snprintf(sizeof(zBuf), zBuf, "%p", p); Tcl_AppendResult(interp, zBuf, (char*)0); #endif return TCL_OK; } /* ** sqlite3_config OPTION ** ** OPTION can be either one of the keywords: ** ** SQLITE_CONFIG_SINGLETHREAD ** SQLITE_CONFIG_MULTITHREAD ** SQLITE_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", SQLITE_CONFIG_SINGLETHREAD}, {"multithread", SQLITE_CONFIG_MULTITHREAD}, {"serialized", SQLITE_CONFIG_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 = sqlite3_config(i); Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_VOLATILE); return TCL_OK; } static sqlite3 *getDbPointer(Tcl_Interp *pInterp, Tcl_Obj *pObj){ sqlite3 *db; Tcl_CmdInfo info; char *zCmd = Tcl_GetString(pObj); if( Tcl_GetCommandInfo(pInterp, zCmd, &info) ){ db = *((sqlite3 **)info.objClientData); }else{ db = (sqlite3*)sqlite3TestTextToPtr(zCmd); } assert( db ); return db; } static int test_enter_db_mutex( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ sqlite3 *db; if( objc!=2 ){ Tcl_WrongNumArgs(interp, 1, objv, "DB"); return TCL_ERROR; } db = getDbPointer(interp, objv[1]); if( !db ){ return TCL_ERROR; } sqlite3_mutex_enter(sqlite3_db_mutex(db)); return TCL_OK; } static int test_leave_db_mutex( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ sqlite3 *db; if( objc!=2 ){ Tcl_WrongNumArgs(interp, 1, objv, "DB"); return TCL_ERROR; } db = getDbPointer(interp, objv[1]); if( !db ){ return TCL_ERROR; } sqlite3_mutex_leave(sqlite3_db_mutex(db)); return TCL_OK; } int Sqlitetest_mutex_Init(Tcl_Interp *interp){ static struct { char *zName; Tcl_ObjCmdProc *xProc; } aCmd[] = { { "sqlite3_shutdown", (Tcl_ObjCmdProc*)test_shutdown }, { "sqlite3_initialize", (Tcl_ObjCmdProc*)test_initialize }, { "sqlite3_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