xref: /sqlite-3.40.0/test/threadtest4.c (revision 18b67f3f)
11e57430eSdrh /*
21e57430eSdrh ** 2014-12-11
31e57430eSdrh **
41e57430eSdrh ** The author disclaims copyright to this source code.  In place of
51e57430eSdrh ** a legal notice, here is a blessing:
61e57430eSdrh **
71e57430eSdrh **    May you do good and not evil.
81e57430eSdrh **    May you find forgiveness for yourself and forgive others.
91e57430eSdrh **    May you share freely, never taking more than you give.
101e57430eSdrh **
111e57430eSdrh *************************************************************************
121e57430eSdrh ** This file implements a simple standalone program used to stress the
131e57430eSdrh ** SQLite library when accessing the same set of databases simultaneously
141e57430eSdrh ** from multiple threads in shared-cache mode.
151e57430eSdrh **
161e57430eSdrh ** This test program runs on unix-like systems only.  It uses pthreads.
171e57430eSdrh ** To compile:
181e57430eSdrh **
191e57430eSdrh **     gcc -o tt4 -I. threadtest4.c sqlite3.c -ldl -lpthread
201e57430eSdrh **
211e57430eSdrh ** To run:
221e57430eSdrh **
231e57430eSdrh **     ./tt4 10
241e57430eSdrh **
251e57430eSdrh ** The argument is the number of threads.
261e57430eSdrh */
271e57430eSdrh #include "sqlite3.h"
281e57430eSdrh #include <pthread.h>
291e57430eSdrh #include <sched.h>
301e57430eSdrh #include <stdio.h>
311e57430eSdrh #include <stdlib.h>
321e57430eSdrh #include <string.h>
331e57430eSdrh #include <unistd.h>
341e57430eSdrh #include <stdarg.h>
351e57430eSdrh 
361e57430eSdrh /*
371e57430eSdrh ** An instance of the following structure is passed into each worker
381e57430eSdrh ** thread.
391e57430eSdrh */
401e57430eSdrh typedef struct WorkerInfo WorkerInfo;
411e57430eSdrh struct WorkerInfo {
421e57430eSdrh   int tid;                    /* Thread ID */
431e57430eSdrh   unsigned wkrFlags;          /* Flags */
441e57430eSdrh   sqlite3 *mainDb;            /* Database connection of the main thread */
451e57430eSdrh   sqlite3 *db;                /* Database connection of this thread */
461e57430eSdrh   int nErr;                   /* Number of errors seen by this thread */
471e57430eSdrh   int nTest;                  /* Number of tests run by this thread */
481e57430eSdrh   char *zMsg;                 /* Message returned by this thread */
491e57430eSdrh   pthread_t id;               /* Thread id */
501e57430eSdrh   pthread_mutex_t *pWrMutex;  /* Hold this mutex while writing */
511e57430eSdrh };
521e57430eSdrh 
531e57430eSdrh /*
541e57430eSdrh ** Allowed values for WorkerInfo.wkrFlags
551e57430eSdrh */
561e57430eSdrh #define TT4_SERIALIZED    0x0000001   /* The --serialized option is used */
571e57430eSdrh #define TT4_WAL           0x0000002   /* WAL mode in use */
581e57430eSdrh #define TT4_TRACE         0x0000004   /* Trace activity */
591e57430eSdrh 
601e57430eSdrh 
611e57430eSdrh /*
621e57430eSdrh ** Report an OOM error and die if the argument is NULL
631e57430eSdrh */
641e57430eSdrh static void check_oom(void *x){
651e57430eSdrh   if( x==0 ){
661e57430eSdrh     fprintf(stderr, "out of memory\n");
671e57430eSdrh     exit(1);
681e57430eSdrh   }
691e57430eSdrh }
701e57430eSdrh 
711e57430eSdrh /*
721e57430eSdrh ** Allocate memory.  If the allocation fails, print an error message and
731e57430eSdrh ** kill the process.
741e57430eSdrh */
751e57430eSdrh static void *safe_malloc(int sz){
761e57430eSdrh   void *x = sqlite3_malloc(sz>0?sz:1);
771e57430eSdrh   check_oom(x);
781e57430eSdrh   return x;
791e57430eSdrh }
801e57430eSdrh 
811e57430eSdrh /*
821e57430eSdrh ** Print a trace message for a worker
831e57430eSdrh */
841e57430eSdrh static void worker_trace(WorkerInfo *p, const char *zFormat, ...){
851e57430eSdrh   va_list ap;
861e57430eSdrh   char *zMsg;
871e57430eSdrh   if( (p->wkrFlags & TT4_TRACE)==0 ) return;
881e57430eSdrh   va_start(ap, zFormat);
891e57430eSdrh   zMsg = sqlite3_vmprintf(zFormat, ap);
901e57430eSdrh   check_oom(zMsg);
911e57430eSdrh   va_end(ap);
921e57430eSdrh   fprintf(stderr, "TRACE(%02d): %s\n", p->tid, zMsg);
931e57430eSdrh   sqlite3_free(zMsg);
941e57430eSdrh }
951e57430eSdrh 
961e57430eSdrh /*
971e57430eSdrh ** Prepare a single SQL query
981e57430eSdrh */
991e57430eSdrh static sqlite3_stmt *prep_sql(sqlite3 *db, const char *zFormat, ...){
1001e57430eSdrh   va_list ap;
1011e57430eSdrh   char *zSql;
1021e57430eSdrh   int rc;
1031e57430eSdrh   sqlite3_stmt *pStmt = 0;
1041e57430eSdrh 
1051e57430eSdrh   va_start(ap, zFormat);
1061e57430eSdrh   zSql = sqlite3_vmprintf(zFormat, ap);
1071e57430eSdrh   va_end(ap);
1081e57430eSdrh   check_oom(zSql);
1091e57430eSdrh   rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
1101e57430eSdrh   if( rc!=SQLITE_OK ){
1111e57430eSdrh     fprintf(stderr, "SQL error (%d,%d): %s\nWhile preparing: [%s]\n",
1121e57430eSdrh             rc, sqlite3_extended_errcode(db), sqlite3_errmsg(db), zSql);
1131e57430eSdrh     exit(1);
1141e57430eSdrh   }
1151e57430eSdrh   sqlite3_free(zSql);
1161e57430eSdrh   return pStmt;
1171e57430eSdrh }
1181e57430eSdrh 
1191e57430eSdrh /*
1201e57430eSdrh ** Run a SQL statements.  Panic if unable.
1211e57430eSdrh */
1221e57430eSdrh static void run_sql(WorkerInfo *p, const char *zFormat, ...){
1231e57430eSdrh   va_list ap;
1241e57430eSdrh   char *zSql;
1251e57430eSdrh   int rc;
1261e57430eSdrh   sqlite3_stmt *pStmt = 0;
1271e57430eSdrh   int nRetry = 0;
1281e57430eSdrh 
1291e57430eSdrh   va_start(ap, zFormat);
1301e57430eSdrh   zSql = sqlite3_vmprintf(zFormat, ap);
1311e57430eSdrh   va_end(ap);
1321e57430eSdrh   check_oom(zSql);
1331e57430eSdrh   rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
1341e57430eSdrh   if( rc!=SQLITE_OK ){
1351e57430eSdrh     fprintf(stderr, "SQL error (%d,%d): %s\nWhile preparing: [%s]\n",
1361e57430eSdrh             rc, sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zSql);
1371e57430eSdrh     exit(1);
1381e57430eSdrh   }
1391e57430eSdrh   worker_trace(p, "running [%s]", zSql);
1401e57430eSdrh   while( (rc = sqlite3_step(pStmt))!=SQLITE_DONE ){
1411e57430eSdrh     if( (rc&0xff)==SQLITE_BUSY || (rc&0xff)==SQLITE_LOCKED ){
1421e57430eSdrh       sqlite3_reset(pStmt);
1431e57430eSdrh       nRetry++;
1441e57430eSdrh       if( nRetry<10 ){
1451e57430eSdrh         worker_trace(p, "retry %d for [%s]", nRetry, zSql);
1461e57430eSdrh         sched_yield();
1471e57430eSdrh         continue;
1481e57430eSdrh       }else{
1491e57430eSdrh         fprintf(stderr, "Deadlock in thread %d while running [%s]\n",
1501e57430eSdrh                 p->tid, zSql);
1511e57430eSdrh         exit(1);
1521e57430eSdrh       }
1531e57430eSdrh     }
1541e57430eSdrh     if( rc!=SQLITE_ROW ){
1551e57430eSdrh       fprintf(stderr, "SQL error (%d,%d): %s\nWhile running [%s]\n",
1561e57430eSdrh               rc, sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zSql);
1571e57430eSdrh       exit(1);
1581e57430eSdrh     }
1591e57430eSdrh   }
1601e57430eSdrh   sqlite3_free(zSql);
1611e57430eSdrh   sqlite3_finalize(pStmt);
1621e57430eSdrh }
1631e57430eSdrh 
1641e57430eSdrh 
1651e57430eSdrh /*
1661e57430eSdrh ** Open the database connection for WorkerInfo.  The order in which
1671e57430eSdrh ** the files are opened is a function of the tid value.
1681e57430eSdrh */
1691e57430eSdrh static void worker_open_connection(WorkerInfo *p, int iCnt){
1701e57430eSdrh   char *zFile;
1711e57430eSdrh   int x;
1721e57430eSdrh   int rc;
1731e57430eSdrh   static const unsigned char aOrder[6][3] = {
1741e57430eSdrh     { 1, 2, 3},
1751e57430eSdrh     { 1, 3, 2},
1761e57430eSdrh     { 2, 1, 3},
1771e57430eSdrh     { 2, 3, 1},
1781e57430eSdrh     { 3, 1, 2},
1791e57430eSdrh     { 3, 2, 1}
1801e57430eSdrh   };
1811e57430eSdrh   x = (p->tid + iCnt) % 6;
1821e57430eSdrh   zFile = sqlite3_mprintf("tt4-test%d.db", aOrder[x][0]);
1831e57430eSdrh   check_oom(zFile);
1841e57430eSdrh   worker_trace(p, "open %s", zFile);
1851e57430eSdrh   rc = sqlite3_open_v2(zFile, &p->db,
1861e57430eSdrh                        SQLITE_OPEN_READWRITE|SQLITE_OPEN_SHAREDCACHE, 0);
1871e57430eSdrh   if( rc!=SQLITE_OK ){
1881e57430eSdrh     fprintf(stderr, "sqlite_open_v2(%s) failed on thread %d\n",
1891e57430eSdrh             zFile, p->tid);
1901e57430eSdrh     exit(1);
1911e57430eSdrh   }
1921e57430eSdrh   sqlite3_free(zFile);
1931e57430eSdrh   run_sql(p, "PRAGMA read_uncommitted=ON;");
1941e57430eSdrh   sqlite3_busy_timeout(p->db, 10000);
1951e57430eSdrh   run_sql(p, "PRAGMA synchronous=OFF;");
1961e57430eSdrh   run_sql(p, "ATTACH 'tt4-test%d.db' AS aux1", aOrder[x][1]);
1971e57430eSdrh   run_sql(p, "ATTACH 'tt4-test%d.db' AS aux2", aOrder[x][2]);
1981e57430eSdrh }
1991e57430eSdrh 
2001e57430eSdrh /*
2011e57430eSdrh ** Close the worker database connection
2021e57430eSdrh */
2031e57430eSdrh static void worker_close_connection(WorkerInfo *p){
2041e57430eSdrh   if( p->db ){
2051e57430eSdrh     worker_trace(p, "close");
2061e57430eSdrh     sqlite3_close(p->db);
2071e57430eSdrh     p->db = 0;
2081e57430eSdrh   }
2091e57430eSdrh }
2101e57430eSdrh 
2111e57430eSdrh /*
2121e57430eSdrh ** Delete all content in the three databases associated with a
2131e57430eSdrh ** single thread.  Make this happen all in a single transaction if
2141e57430eSdrh ** inTrans is true, or separately for each database if inTrans is
2151e57430eSdrh ** false.
2161e57430eSdrh */
2171e57430eSdrh static void worker_delete_all_content(WorkerInfo *p, int inTrans){
2181e57430eSdrh   if( inTrans ){
2191e57430eSdrh     pthread_mutex_lock(p->pWrMutex);
2201e57430eSdrh     run_sql(p, "BEGIN");
2211e57430eSdrh     run_sql(p, "DELETE FROM t1 WHERE tid=%d", p->tid);
2221e57430eSdrh     run_sql(p, "DELETE FROM t2 WHERE tid=%d", p->tid);
2231e57430eSdrh     run_sql(p, "DELETE FROM t3 WHERE tid=%d", p->tid);
2241e57430eSdrh     run_sql(p, "COMMIT");
2251e57430eSdrh     pthread_mutex_unlock(p->pWrMutex);
2261e57430eSdrh     p->nTest++;
2271e57430eSdrh   }else{
2281e57430eSdrh     pthread_mutex_lock(p->pWrMutex);
2291e57430eSdrh     run_sql(p, "DELETE FROM t1 WHERE tid=%d", p->tid);
2301e57430eSdrh     pthread_mutex_unlock(p->pWrMutex);
2311e57430eSdrh     p->nTest++;
2321e57430eSdrh     pthread_mutex_lock(p->pWrMutex);
2331e57430eSdrh     run_sql(p, "DELETE FROM t2 WHERE tid=%d", p->tid);
2341e57430eSdrh     pthread_mutex_unlock(p->pWrMutex);
2351e57430eSdrh     p->nTest++;
2361e57430eSdrh     pthread_mutex_lock(p->pWrMutex);
2371e57430eSdrh     run_sql(p, "DELETE FROM t3 WHERE tid=%d", p->tid);
2381e57430eSdrh     pthread_mutex_unlock(p->pWrMutex);
2391e57430eSdrh     p->nTest++;
2401e57430eSdrh   }
2411e57430eSdrh }
2421e57430eSdrh 
2431e57430eSdrh /*
2441e57430eSdrh ** Create rows mn through mx in table iTab for the given worker
2451e57430eSdrh */
2461e57430eSdrh static void worker_add_content(WorkerInfo *p, int mn, int mx, int iTab){
2471e57430eSdrh   char *zTabDef;
2481e57430eSdrh   switch( iTab ){
2491e57430eSdrh     case 1:  zTabDef = "t1(tid,sp,a,b,c)";  break;
2501e57430eSdrh     case 2:  zTabDef = "t2(tid,sp,d,e,f)";  break;
2511e57430eSdrh     case 3:  zTabDef = "t3(tid,sp,x,y,z)";  break;
2521e57430eSdrh   }
2531e57430eSdrh   pthread_mutex_lock(p->pWrMutex);
2541e57430eSdrh   run_sql(p,
2551e57430eSdrh      "WITH RECURSIVE\n"
2561e57430eSdrh      " c(i) AS (VALUES(%d) UNION ALL SELECT i+1 FROM c WHERE i<%d)\n"
2571e57430eSdrh      "INSERT INTO %s SELECT %d, zeroblob(3000), i, printf('%%d',i), i FROM c;",
2581e57430eSdrh      mn, mx, zTabDef, p->tid
2591e57430eSdrh   );
2601e57430eSdrh   pthread_mutex_unlock(p->pWrMutex);
2611e57430eSdrh   p->nTest++;
2621e57430eSdrh }
2631e57430eSdrh 
2641e57430eSdrh /*
2651e57430eSdrh ** Set an error message on a worker
2661e57430eSdrh */
2671e57430eSdrh static void worker_error(WorkerInfo *p, const char *zFormat, ...){
2681e57430eSdrh   va_list ap;
2691e57430eSdrh   p->nErr++;
2701e57430eSdrh   sqlite3_free(p->zMsg);
2711e57430eSdrh   va_start(ap, zFormat);
2721e57430eSdrh   p->zMsg = sqlite3_vmprintf(zFormat, ap);
2731e57430eSdrh   va_end(ap);
2741e57430eSdrh }
2751e57430eSdrh 
2761e57430eSdrh /*
2771e57430eSdrh ** Each thread runs the following function.
2781e57430eSdrh */
2791e57430eSdrh static void *worker_thread(void *pArg){
2801e57430eSdrh   WorkerInfo *p = (WorkerInfo*)pArg;
2811e57430eSdrh   int iOuter;
2821e57430eSdrh   int i;
2831e57430eSdrh   int rc;
2841e57430eSdrh   sqlite3_stmt *pStmt;
2851e57430eSdrh 
2861e57430eSdrh   printf("worker %d startup\n", p->tid);  fflush(stdout);
2871e57430eSdrh   for(iOuter=1; iOuter<=4; iOuter++){
2881e57430eSdrh     worker_open_connection(p, iOuter);
2891e57430eSdrh     for(i=0; i<4; i++){
2901e57430eSdrh       worker_add_content(p, i*100+1, (i+1)*100, (p->tid+iOuter)%3 + 1);
2911e57430eSdrh       worker_add_content(p, i*100+1, (i+1)*100, (p->tid+iOuter+1)%3 + 1);
2921e57430eSdrh       worker_add_content(p, i*100+1, (i+1)*100, (p->tid+iOuter+2)%3 + 1);
2931e57430eSdrh     }
2941e57430eSdrh 
2951e57430eSdrh     pStmt = prep_sql(p->db, "SELECT count(a) FROM t1 WHERE tid=%d", p->tid);
2961e57430eSdrh     worker_trace(p, "query [%s]", sqlite3_sql(pStmt));
2971e57430eSdrh     rc = sqlite3_step(pStmt);
2981e57430eSdrh     if( rc!=SQLITE_ROW ){
2991e57430eSdrh       worker_error(p, "Failed to step: %s", sqlite3_sql(pStmt));
3001e57430eSdrh     }else if( sqlite3_column_int(pStmt, 0)!=400 ){
3011e57430eSdrh       worker_error(p, "Wrong result: %d", sqlite3_column_int(pStmt,0));
3021e57430eSdrh     }
3031e57430eSdrh     if( p->nErr ) break;
304*18b67f3fSdrh     sqlite3_finalize(pStmt);
3051e57430eSdrh 
3061e57430eSdrh     worker_delete_all_content(p, (p->tid+iOuter)%2);
3071e57430eSdrh     worker_close_connection(p);
3081e57430eSdrh     p->db = 0;
3091e57430eSdrh   }
3101e57430eSdrh   worker_close_connection(p);
3111e57430eSdrh   printf("worker %d finished\n", p->tid); fflush(stdout);
3121e57430eSdrh   return 0;
3131e57430eSdrh }
3141e57430eSdrh 
3151e57430eSdrh int main(int argc, char **argv){
3161e57430eSdrh   int nWorker = 0;         /* Number of worker threads */
3171e57430eSdrh   int i;                   /* Loop counter */
3181e57430eSdrh   WorkerInfo *aInfo;       /* Information for each worker */
3191e57430eSdrh   unsigned wkrFlags = 0;   /* Default worker flags */
3201e57430eSdrh   int nErr = 0;            /* Number of errors */
3211e57430eSdrh   int nTest = 0;           /* Number of tests */
3221e57430eSdrh   int rc;                  /* Return code */
3231e57430eSdrh   sqlite3 *db = 0;         /* Main database connection */
3241e57430eSdrh   pthread_mutex_t wrMutex; /* The write serialization mutex */
3251e57430eSdrh   WorkerInfo infoTop;      /* WorkerInfo for the main thread */
3261e57430eSdrh   WorkerInfo *p;           /* Pointer to infoTop */
3271e57430eSdrh 
3281e57430eSdrh   sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
3291e57430eSdrh   for(i=1; i<argc; i++){
3301e57430eSdrh     const char *z = argv[i];
3311e57430eSdrh     if( z[0]=='-' ){
3321e57430eSdrh       if( z[1]=='-' && z[2]!=0 ) z++;
3331e57430eSdrh       if( strcmp(z,"-multithread")==0 ){
3341e57430eSdrh         sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
3351e57430eSdrh         wkrFlags &= ~TT4_SERIALIZED;
3361e57430eSdrh       }else if( strcmp(z,"-serialized")==0 ){
3371e57430eSdrh         sqlite3_config(SQLITE_CONFIG_SERIALIZED);
3381e57430eSdrh         wkrFlags |= TT4_SERIALIZED;
3391e57430eSdrh       }else if( strcmp(z,"-wal")==0 ){
3401e57430eSdrh         wkrFlags |= TT4_WAL;
3411e57430eSdrh       }else if( strcmp(z,"-trace")==0 ){
3421e57430eSdrh         wkrFlags |= TT4_TRACE;
3431e57430eSdrh       }else{
3441e57430eSdrh         fprintf(stderr, "unknown command-line option: %s\n", argv[i]);
3451e57430eSdrh         exit(1);
3461e57430eSdrh       }
3471e57430eSdrh     }else if( z[0]>='1' && z[0]<='9' && nWorker==0 ){
3481e57430eSdrh       nWorker = atoi(z);
3491e57430eSdrh       if( nWorker<2 ){
3501e57430eSdrh         fprintf(stderr, "minimum of 2 threads\n");
3511e57430eSdrh         exit(1);
3521e57430eSdrh       }
3531e57430eSdrh     }else{
3541e57430eSdrh       fprintf(stderr, "extra command-line argument: \"%s\"\n", argv[i]);
3551e57430eSdrh       exit(1);
3561e57430eSdrh     }
3571e57430eSdrh   }
3581e57430eSdrh   if( nWorker==0 ){
3591e57430eSdrh     fprintf(stderr,
3601e57430eSdrh        "usage:  %s ?OPTIONS? N\n"
3611e57430eSdrh        "N is the number of threads and must be at least 2.\n"
3621e57430eSdrh        "Options:\n"
3631e57430eSdrh        "  --serialized\n"
3641e57430eSdrh        "  --multithread\n"
3651e57430eSdrh        ,argv[0]
3661e57430eSdrh     );
3671e57430eSdrh     exit(1);
3681e57430eSdrh   }
3691e57430eSdrh   if( !sqlite3_threadsafe() ){
3701e57430eSdrh     fprintf(stderr, "requires a threadsafe build of SQLite\n");
3711e57430eSdrh     exit(1);
3721e57430eSdrh   }
3731e57430eSdrh   sqlite3_initialize();
3741e57430eSdrh   sqlite3_enable_shared_cache(1);
3751e57430eSdrh   pthread_mutex_init(&wrMutex, 0);
3761e57430eSdrh 
3771e57430eSdrh   /* Initialize the test database files */
3781e57430eSdrh   (void)unlink("tt4-test1.db");
3791e57430eSdrh   (void)unlink("tt4-test2.db");
3801e57430eSdrh   (void)unlink("tt4-test3.db");
3811e57430eSdrh   rc = sqlite3_open("tt4-test1.db", &db);
3821e57430eSdrh   if( rc!=SQLITE_OK ){
3831e57430eSdrh     fprintf(stderr, "Unable to open test database: tt4-test2.db\n");
3841e57430eSdrh     exit(1);
3851e57430eSdrh   }
3861e57430eSdrh   memset(&infoTop, 0, sizeof(infoTop));
3871e57430eSdrh   infoTop.db = db;
3881e57430eSdrh   infoTop.wkrFlags = wkrFlags;
3891e57430eSdrh   p = &infoTop;
3901e57430eSdrh   if( wkrFlags & TT4_WAL ){
3911e57430eSdrh     run_sql(p, "PRAGMA journal_mode=WAL");
3921e57430eSdrh   }
3931e57430eSdrh   run_sql(p, "PRAGMA synchronous=OFF");
3941e57430eSdrh   run_sql(p, "CREATE TABLE IF NOT EXISTS t1(tid INTEGER, sp, a, b, c)");
3951e57430eSdrh   run_sql(p, "CREATE INDEX t1tid ON t1(tid)");
3961e57430eSdrh   run_sql(p, "CREATE INDEX t1ab ON t1(a,b)");
3971e57430eSdrh   run_sql(p, "ATTACH 'tt4-test2.db' AS 'test2'");
3981e57430eSdrh   run_sql(p, "CREATE TABLE IF NOT EXISTS test2.t2(tid INTEGER, sp, d, e, f)");
3991e57430eSdrh   run_sql(p, "CREATE INDEX test2.t2tid ON t2(tid)");
4001e57430eSdrh   run_sql(p, "CREATE INDEX test2.t2de ON t2(d,e)");
4011e57430eSdrh   run_sql(p, "ATTACH 'tt4-test3.db' AS 'test3'");
4021e57430eSdrh   run_sql(p, "CREATE TABLE IF NOT EXISTS test3.t3(tid INTEGER, sp, x, y, z)");
4031e57430eSdrh   run_sql(p, "CREATE INDEX test3.t3tid ON t3(tid)");
4041e57430eSdrh   run_sql(p, "CREATE INDEX test3.t3xy ON t3(x,y)");
4051e57430eSdrh   aInfo = safe_malloc( sizeof(*aInfo)*nWorker );
4061e57430eSdrh   memset(aInfo, 0, sizeof(*aInfo)*nWorker);
4071e57430eSdrh   for(i=0; i<nWorker; i++){
4081e57430eSdrh     aInfo[i].tid = i+1;
4091e57430eSdrh     aInfo[i].wkrFlags = wkrFlags;
4101e57430eSdrh     aInfo[i].mainDb = db;
4111e57430eSdrh     aInfo[i].pWrMutex = &wrMutex;
4121e57430eSdrh     rc = pthread_create(&aInfo[i].id, 0, worker_thread, &aInfo[i]);
4131e57430eSdrh     if( rc!=0 ){
4141e57430eSdrh       fprintf(stderr, "thread creation failed for thread %d\n", i+1);
4151e57430eSdrh       exit(1);
4161e57430eSdrh     }
4171e57430eSdrh     sched_yield();
4181e57430eSdrh   }
4191e57430eSdrh   for(i=0; i<nWorker; i++){
4201e57430eSdrh     pthread_join(aInfo[i].id, 0);
4211e57430eSdrh     printf("Joined thread %d: %d errors in %d tests",
4221e57430eSdrh            aInfo[i].tid, aInfo[i].nErr, aInfo[i].nTest);
4231e57430eSdrh     if( aInfo[i].zMsg ){
4241e57430eSdrh       printf(": %s\n", aInfo[i].zMsg);
4251e57430eSdrh     }else{
4261e57430eSdrh       printf("\n");
4271e57430eSdrh     }
4281e57430eSdrh     nErr += aInfo[i].nErr;
4291e57430eSdrh     nTest += aInfo[i].nTest;
4301e57430eSdrh     fflush(stdout);
4311e57430eSdrh   }
4321e57430eSdrh   sqlite3_close(db);
4331e57430eSdrh   sqlite3_free(aInfo);
4341e57430eSdrh   printf("Total %d errors in %d tests\n", nErr, nTest);
4351e57430eSdrh   return nErr;
4361e57430eSdrh }
437