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