Artifact 1f52c6187d4a87a78abf10be089791f63e689fbb:

  • File test/test_mutex.c — part of check-in [da13c446be] at 2012-06-29 15:39:25 on branch trunk — Change all "SQLITE_" prefixes on preprocessor macros to "SQLITE4_" to avoid name collisions with SQLite3. (user: drh size: 10754)

/*
** 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 <stdlib.h>
#include <assert.h>
#include <string.h>

/* 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<sizeof(aCmd)/sizeof(aCmd[0]); i++){
    Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
  }

  Tcl_LinkVar(interp, "disable_mutex_init", 
              (char*)&g.disableInit, TCL_LINK_INT);
  Tcl_LinkVar(interp, "disable_mutex_try", 
              (char*)&g.disableTry, TCL_LINK_INT);
  return SQLITE4_OK;
}