xref: /sqlite-3.40.0/test/threadtest4.c (revision 9bd3cc46)
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 **
19ef15c6e9Sdrh **     gcc -g -Wall -I. threadtest4.c sqlite3.c -ldl -lpthread
201e57430eSdrh **
211e57430eSdrh ** To run:
221e57430eSdrh **
23ef15c6e9Sdrh **     ./a.out 10
241e57430eSdrh **
25ef15c6e9Sdrh ** The argument is the number of threads.  There are also options, such
26ef15c6e9Sdrh ** as -wal and -multithread and -serialized.
27ef15c6e9Sdrh **
28ef15c6e9Sdrh ** Consider also compiling with clang instead of gcc and adding the
29ef15c6e9Sdrh ** -fsanitize=thread option.
301e57430eSdrh */
311e57430eSdrh #include "sqlite3.h"
321e57430eSdrh #include <pthread.h>
331e57430eSdrh #include <sched.h>
341e57430eSdrh #include <stdio.h>
351e57430eSdrh #include <stdlib.h>
361e57430eSdrh #include <string.h>
371e57430eSdrh #include <unistd.h>
381e57430eSdrh #include <stdarg.h>
391e57430eSdrh 
401e57430eSdrh /*
411e57430eSdrh ** An instance of the following structure is passed into each worker
421e57430eSdrh ** thread.
431e57430eSdrh */
441e57430eSdrh typedef struct WorkerInfo WorkerInfo;
451e57430eSdrh struct WorkerInfo {
461e57430eSdrh   int tid;                    /* Thread ID */
47ef15c6e9Sdrh   int nWorker;                /* Total number of workers */
481e57430eSdrh   unsigned wkrFlags;          /* Flags */
491e57430eSdrh   sqlite3 *mainDb;            /* Database connection of the main thread */
501e57430eSdrh   sqlite3 *db;                /* Database connection of this thread */
511e57430eSdrh   int nErr;                   /* Number of errors seen by this thread */
521e57430eSdrh   int nTest;                  /* Number of tests run by this thread */
531e57430eSdrh   char *zMsg;                 /* Message returned by this thread */
541e57430eSdrh   pthread_t id;               /* Thread id */
551e57430eSdrh   pthread_mutex_t *pWrMutex;  /* Hold this mutex while writing */
561e57430eSdrh };
571e57430eSdrh 
581e57430eSdrh /*
591e57430eSdrh ** Allowed values for WorkerInfo.wkrFlags
601e57430eSdrh */
611e57430eSdrh #define TT4_SERIALIZED    0x0000001   /* The --serialized option is used */
621e57430eSdrh #define TT4_WAL           0x0000002   /* WAL mode in use */
631e57430eSdrh #define TT4_TRACE         0x0000004   /* Trace activity */
641e57430eSdrh 
651e57430eSdrh 
661e57430eSdrh /*
671e57430eSdrh ** Report an OOM error and die if the argument is NULL
681e57430eSdrh */
check_oom(void * x)691e57430eSdrh static void check_oom(void *x){
701e57430eSdrh   if( x==0 ){
711e57430eSdrh     fprintf(stderr, "out of memory\n");
721e57430eSdrh     exit(1);
731e57430eSdrh   }
741e57430eSdrh }
751e57430eSdrh 
761e57430eSdrh /*
771e57430eSdrh ** Allocate memory.  If the allocation fails, print an error message and
781e57430eSdrh ** kill the process.
791e57430eSdrh */
safe_malloc(int sz)801e57430eSdrh static void *safe_malloc(int sz){
811e57430eSdrh   void *x = sqlite3_malloc(sz>0?sz:1);
821e57430eSdrh   check_oom(x);
831e57430eSdrh   return x;
841e57430eSdrh }
851e57430eSdrh 
861e57430eSdrh /*
871e57430eSdrh ** Print a trace message for a worker
881e57430eSdrh */
worker_trace(WorkerInfo * p,const char * zFormat,...)891e57430eSdrh static void worker_trace(WorkerInfo *p, const char *zFormat, ...){
901e57430eSdrh   va_list ap;
911e57430eSdrh   char *zMsg;
921e57430eSdrh   if( (p->wkrFlags & TT4_TRACE)==0 ) return;
931e57430eSdrh   va_start(ap, zFormat);
941e57430eSdrh   zMsg = sqlite3_vmprintf(zFormat, ap);
951e57430eSdrh   check_oom(zMsg);
961e57430eSdrh   va_end(ap);
971e57430eSdrh   fprintf(stderr, "TRACE(%02d): %s\n", p->tid, zMsg);
981e57430eSdrh   sqlite3_free(zMsg);
991e57430eSdrh }
1001e57430eSdrh 
1011e57430eSdrh /*
1021e57430eSdrh ** Prepare a single SQL query
1031e57430eSdrh */
prep_sql(sqlite3 * db,const char * zFormat,...)1041e57430eSdrh static sqlite3_stmt *prep_sql(sqlite3 *db, const char *zFormat, ...){
1051e57430eSdrh   va_list ap;
1061e57430eSdrh   char *zSql;
1071e57430eSdrh   int rc;
1081e57430eSdrh   sqlite3_stmt *pStmt = 0;
1091e57430eSdrh 
1101e57430eSdrh   va_start(ap, zFormat);
1111e57430eSdrh   zSql = sqlite3_vmprintf(zFormat, ap);
1121e57430eSdrh   va_end(ap);
1131e57430eSdrh   check_oom(zSql);
1141e57430eSdrh   rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
1151e57430eSdrh   if( rc!=SQLITE_OK ){
1161e57430eSdrh     fprintf(stderr, "SQL error (%d,%d): %s\nWhile preparing: [%s]\n",
1171e57430eSdrh             rc, sqlite3_extended_errcode(db), sqlite3_errmsg(db), zSql);
1181e57430eSdrh     exit(1);
1191e57430eSdrh   }
1201e57430eSdrh   sqlite3_free(zSql);
1211e57430eSdrh   return pStmt;
1221e57430eSdrh }
1231e57430eSdrh 
1241e57430eSdrh /*
1251e57430eSdrh ** Run a SQL statements.  Panic if unable.
1261e57430eSdrh */
run_sql(WorkerInfo * p,const char * zFormat,...)1271e57430eSdrh static void run_sql(WorkerInfo *p, const char *zFormat, ...){
1281e57430eSdrh   va_list ap;
1291e57430eSdrh   char *zSql;
1301e57430eSdrh   int rc;
1311e57430eSdrh   sqlite3_stmt *pStmt = 0;
1321e57430eSdrh   int nRetry = 0;
1331e57430eSdrh 
1341e57430eSdrh   va_start(ap, zFormat);
1351e57430eSdrh   zSql = sqlite3_vmprintf(zFormat, ap);
1361e57430eSdrh   va_end(ap);
1371e57430eSdrh   check_oom(zSql);
1381e57430eSdrh   rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
1391e57430eSdrh   if( rc!=SQLITE_OK ){
1401e57430eSdrh     fprintf(stderr, "SQL error (%d,%d): %s\nWhile preparing: [%s]\n",
1411e57430eSdrh             rc, sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zSql);
1421e57430eSdrh     exit(1);
1431e57430eSdrh   }
1441e57430eSdrh   worker_trace(p, "running [%s]", zSql);
1451e57430eSdrh   while( (rc = sqlite3_step(pStmt))!=SQLITE_DONE ){
1461e57430eSdrh     if( (rc&0xff)==SQLITE_BUSY || (rc&0xff)==SQLITE_LOCKED ){
1471e57430eSdrh       sqlite3_reset(pStmt);
1481e57430eSdrh       nRetry++;
1491e57430eSdrh       if( nRetry<10 ){
1501e57430eSdrh         worker_trace(p, "retry %d for [%s]", nRetry, zSql);
1511e57430eSdrh         sched_yield();
1521e57430eSdrh         continue;
1531e57430eSdrh       }else{
1541e57430eSdrh         fprintf(stderr, "Deadlock in thread %d while running [%s]\n",
1551e57430eSdrh                 p->tid, zSql);
1561e57430eSdrh         exit(1);
1571e57430eSdrh       }
1581e57430eSdrh     }
1591e57430eSdrh     if( rc!=SQLITE_ROW ){
1601e57430eSdrh       fprintf(stderr, "SQL error (%d,%d): %s\nWhile running [%s]\n",
1611e57430eSdrh               rc, sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zSql);
1621e57430eSdrh       exit(1);
1631e57430eSdrh     }
1641e57430eSdrh   }
1651e57430eSdrh   sqlite3_free(zSql);
1661e57430eSdrh   sqlite3_finalize(pStmt);
1671e57430eSdrh }
1681e57430eSdrh 
1691e57430eSdrh 
1701e57430eSdrh /*
1711e57430eSdrh ** Open the database connection for WorkerInfo.  The order in which
1721e57430eSdrh ** the files are opened is a function of the tid value.
1731e57430eSdrh */
worker_open_connection(WorkerInfo * p,int iCnt)1741e57430eSdrh static void worker_open_connection(WorkerInfo *p, int iCnt){
1751e57430eSdrh   char *zFile;
1761e57430eSdrh   int x;
1771e57430eSdrh   int rc;
1781e57430eSdrh   static const unsigned char aOrder[6][3] = {
1791e57430eSdrh     { 1, 2, 3},
1801e57430eSdrh     { 1, 3, 2},
1811e57430eSdrh     { 2, 1, 3},
1821e57430eSdrh     { 2, 3, 1},
1831e57430eSdrh     { 3, 1, 2},
1841e57430eSdrh     { 3, 2, 1}
1851e57430eSdrh   };
1861e57430eSdrh   x = (p->tid + iCnt) % 6;
1871e57430eSdrh   zFile = sqlite3_mprintf("tt4-test%d.db", aOrder[x][0]);
1881e57430eSdrh   check_oom(zFile);
1891e57430eSdrh   worker_trace(p, "open %s", zFile);
1901e57430eSdrh   rc = sqlite3_open_v2(zFile, &p->db,
1911e57430eSdrh                        SQLITE_OPEN_READWRITE|SQLITE_OPEN_SHAREDCACHE, 0);
1921e57430eSdrh   if( rc!=SQLITE_OK ){
1931e57430eSdrh     fprintf(stderr, "sqlite_open_v2(%s) failed on thread %d\n",
1941e57430eSdrh             zFile, p->tid);
1951e57430eSdrh     exit(1);
1961e57430eSdrh   }
1971e57430eSdrh   sqlite3_free(zFile);
1981e57430eSdrh   run_sql(p, "PRAGMA read_uncommitted=ON;");
1991e57430eSdrh   sqlite3_busy_timeout(p->db, 10000);
2001e57430eSdrh   run_sql(p, "PRAGMA synchronous=OFF;");
2011e57430eSdrh   run_sql(p, "ATTACH 'tt4-test%d.db' AS aux1", aOrder[x][1]);
2021e57430eSdrh   run_sql(p, "ATTACH 'tt4-test%d.db' AS aux2", aOrder[x][2]);
2031e57430eSdrh }
2041e57430eSdrh 
2051e57430eSdrh /*
2061e57430eSdrh ** Close the worker database connection
2071e57430eSdrh */
worker_close_connection(WorkerInfo * p)2081e57430eSdrh static void worker_close_connection(WorkerInfo *p){
2091e57430eSdrh   if( p->db ){
2101e57430eSdrh     worker_trace(p, "close");
2111e57430eSdrh     sqlite3_close(p->db);
2121e57430eSdrh     p->db = 0;
2131e57430eSdrh   }
2141e57430eSdrh }
2151e57430eSdrh 
2161e57430eSdrh /*
2171e57430eSdrh ** Delete all content in the three databases associated with a
2181e57430eSdrh ** single thread.  Make this happen all in a single transaction if
2191e57430eSdrh ** inTrans is true, or separately for each database if inTrans is
2201e57430eSdrh ** false.
2211e57430eSdrh */
worker_delete_all_content(WorkerInfo * p,int inTrans)2221e57430eSdrh static void worker_delete_all_content(WorkerInfo *p, int inTrans){
2231e57430eSdrh   if( inTrans ){
2241e57430eSdrh     pthread_mutex_lock(p->pWrMutex);
2251e57430eSdrh     run_sql(p, "BEGIN");
2261e57430eSdrh     run_sql(p, "DELETE FROM t1 WHERE tid=%d", p->tid);
2271e57430eSdrh     run_sql(p, "DELETE FROM t2 WHERE tid=%d", p->tid);
2281e57430eSdrh     run_sql(p, "DELETE FROM t3 WHERE tid=%d", p->tid);
2291e57430eSdrh     run_sql(p, "COMMIT");
2301e57430eSdrh     pthread_mutex_unlock(p->pWrMutex);
2311e57430eSdrh     p->nTest++;
2321e57430eSdrh   }else{
2331e57430eSdrh     pthread_mutex_lock(p->pWrMutex);
2341e57430eSdrh     run_sql(p, "DELETE FROM t1 WHERE tid=%d", p->tid);
2351e57430eSdrh     pthread_mutex_unlock(p->pWrMutex);
2361e57430eSdrh     p->nTest++;
2371e57430eSdrh     pthread_mutex_lock(p->pWrMutex);
2381e57430eSdrh     run_sql(p, "DELETE FROM t2 WHERE tid=%d", p->tid);
2391e57430eSdrh     pthread_mutex_unlock(p->pWrMutex);
2401e57430eSdrh     p->nTest++;
2411e57430eSdrh     pthread_mutex_lock(p->pWrMutex);
2421e57430eSdrh     run_sql(p, "DELETE FROM t3 WHERE tid=%d", p->tid);
2431e57430eSdrh     pthread_mutex_unlock(p->pWrMutex);
2441e57430eSdrh     p->nTest++;
2451e57430eSdrh   }
2461e57430eSdrh }
2471e57430eSdrh 
2481e57430eSdrh /*
2491e57430eSdrh ** Create rows mn through mx in table iTab for the given worker
2501e57430eSdrh */
worker_add_content(WorkerInfo * p,int mn,int mx,int iTab)2511e57430eSdrh static void worker_add_content(WorkerInfo *p, int mn, int mx, int iTab){
2521e57430eSdrh   char *zTabDef;
2531e57430eSdrh   switch( iTab ){
2541e57430eSdrh     case 1:  zTabDef = "t1(tid,sp,a,b,c)";  break;
2551e57430eSdrh     case 2:  zTabDef = "t2(tid,sp,d,e,f)";  break;
2561e57430eSdrh     case 3:  zTabDef = "t3(tid,sp,x,y,z)";  break;
2571e57430eSdrh   }
2581e57430eSdrh   pthread_mutex_lock(p->pWrMutex);
2591e57430eSdrh   run_sql(p,
2601e57430eSdrh      "WITH RECURSIVE\n"
2611e57430eSdrh      " c(i) AS (VALUES(%d) UNION ALL SELECT i+1 FROM c WHERE i<%d)\n"
2621e57430eSdrh      "INSERT INTO %s SELECT %d, zeroblob(3000), i, printf('%%d',i), i FROM c;",
2631e57430eSdrh      mn, mx, zTabDef, p->tid
2641e57430eSdrh   );
2651e57430eSdrh   pthread_mutex_unlock(p->pWrMutex);
2661e57430eSdrh   p->nTest++;
2671e57430eSdrh }
2681e57430eSdrh 
2691e57430eSdrh /*
2701e57430eSdrh ** Set an error message on a worker
2711e57430eSdrh */
worker_error(WorkerInfo * p,const char * zFormat,...)2721e57430eSdrh static void worker_error(WorkerInfo *p, const char *zFormat, ...){
2731e57430eSdrh   va_list ap;
2741e57430eSdrh   p->nErr++;
2751e57430eSdrh   sqlite3_free(p->zMsg);
2761e57430eSdrh   va_start(ap, zFormat);
2771e57430eSdrh   p->zMsg = sqlite3_vmprintf(zFormat, ap);
2781e57430eSdrh   va_end(ap);
2791e57430eSdrh }
2801e57430eSdrh 
2811e57430eSdrh /*
2821e57430eSdrh ** Each thread runs the following function.
2831e57430eSdrh */
worker_thread(void * pArg)2841e57430eSdrh static void *worker_thread(void *pArg){
2851e57430eSdrh   WorkerInfo *p = (WorkerInfo*)pArg;
2861e57430eSdrh   int iOuter;
2871e57430eSdrh   int i;
2881e57430eSdrh   int rc;
2891e57430eSdrh   sqlite3_stmt *pStmt;
2901e57430eSdrh 
2911e57430eSdrh   printf("worker %d startup\n", p->tid);  fflush(stdout);
292ef15c6e9Sdrh   for(iOuter=1; iOuter<=p->nWorker; iOuter++){
2931e57430eSdrh     worker_open_connection(p, iOuter);
2941e57430eSdrh     for(i=0; i<4; i++){
2951e57430eSdrh       worker_add_content(p, i*100+1, (i+1)*100, (p->tid+iOuter)%3 + 1);
2961e57430eSdrh       worker_add_content(p, i*100+1, (i+1)*100, (p->tid+iOuter+1)%3 + 1);
2971e57430eSdrh       worker_add_content(p, i*100+1, (i+1)*100, (p->tid+iOuter+2)%3 + 1);
2981e57430eSdrh     }
2991e57430eSdrh 
3001e57430eSdrh     pStmt = prep_sql(p->db, "SELECT count(a) FROM t1 WHERE tid=%d", p->tid);
3011e57430eSdrh     worker_trace(p, "query [%s]", sqlite3_sql(pStmt));
3021e57430eSdrh     rc = sqlite3_step(pStmt);
3031e57430eSdrh     if( rc!=SQLITE_ROW ){
3041e57430eSdrh       worker_error(p, "Failed to step: %s", sqlite3_sql(pStmt));
3051e57430eSdrh     }else if( sqlite3_column_int(pStmt, 0)!=400 ){
3061e57430eSdrh       worker_error(p, "Wrong result: %d", sqlite3_column_int(pStmt,0));
3071e57430eSdrh     }
30818b67f3fSdrh     sqlite3_finalize(pStmt);
309*9bd3cc46Sdrh     if( p->nErr ) break;
3101e57430eSdrh 
311ef15c6e9Sdrh     if( ((iOuter+p->tid)%3)==0 ){
312ef15c6e9Sdrh       sqlite3_db_release_memory(p->db);
313ef15c6e9Sdrh       p->nTest++;
314ef15c6e9Sdrh     }
315ef15c6e9Sdrh 
316*9bd3cc46Sdrh     pthread_mutex_lock(p->pWrMutex);
317*9bd3cc46Sdrh     run_sql(p, "BEGIN;");
318*9bd3cc46Sdrh     run_sql(p, "UPDATE t1 SET c=NULL WHERE a=55");
319*9bd3cc46Sdrh     run_sql(p, "UPDATE t2 SET f=NULL WHERE d=42");
320*9bd3cc46Sdrh     run_sql(p, "UPDATE t3 SET z=NULL WHERE x=31");
321*9bd3cc46Sdrh     run_sql(p, "ROLLBACK;");
322*9bd3cc46Sdrh     p->nTest++;
323*9bd3cc46Sdrh     pthread_mutex_unlock(p->pWrMutex);
324*9bd3cc46Sdrh 
325*9bd3cc46Sdrh 
326ef15c6e9Sdrh     if( iOuter==p->tid ){
327ef15c6e9Sdrh       pthread_mutex_lock(p->pWrMutex);
328ef15c6e9Sdrh       run_sql(p, "VACUUM");
329ef15c6e9Sdrh       pthread_mutex_unlock(p->pWrMutex);
330ef15c6e9Sdrh     }
331ef15c6e9Sdrh 
332*9bd3cc46Sdrh     pStmt = prep_sql(p->db,
333*9bd3cc46Sdrh        "SELECT t1.rowid, t2.rowid, t3.rowid"
334*9bd3cc46Sdrh        "  FROM t1, t2, t3"
335*9bd3cc46Sdrh        " WHERE t1.tid=%d AND t2.tid=%d AND t3.tid=%d"
336*9bd3cc46Sdrh        "   AND t1.a<>t2.d AND t2.d<>t3.x"
337*9bd3cc46Sdrh        " ORDER BY 1, 2, 3"
338*9bd3cc46Sdrh        ,p->tid, p->tid, p->tid);
339*9bd3cc46Sdrh     worker_trace(p, "query [%s]", sqlite3_sql(pStmt));
340*9bd3cc46Sdrh     for(i=0; i<p->nWorker; i++){
341*9bd3cc46Sdrh       rc = sqlite3_step(pStmt);
342*9bd3cc46Sdrh       if( rc!=SQLITE_ROW ){
343*9bd3cc46Sdrh         worker_error(p, "Failed to step: %s", sqlite3_sql(pStmt));
344*9bd3cc46Sdrh         break;
345*9bd3cc46Sdrh       }
346*9bd3cc46Sdrh       sched_yield();
347*9bd3cc46Sdrh     }
348*9bd3cc46Sdrh     sqlite3_finalize(pStmt);
349*9bd3cc46Sdrh     if( p->nErr ) break;
350*9bd3cc46Sdrh 
3511e57430eSdrh     worker_delete_all_content(p, (p->tid+iOuter)%2);
3521e57430eSdrh     worker_close_connection(p);
3531e57430eSdrh     p->db = 0;
3541e57430eSdrh   }
3551e57430eSdrh   worker_close_connection(p);
3561e57430eSdrh   printf("worker %d finished\n", p->tid); fflush(stdout);
3571e57430eSdrh   return 0;
3581e57430eSdrh }
3591e57430eSdrh 
main(int argc,char ** argv)3601e57430eSdrh int main(int argc, char **argv){
3611e57430eSdrh   int nWorker = 0;         /* Number of worker threads */
3621e57430eSdrh   int i;                   /* Loop counter */
3631e57430eSdrh   WorkerInfo *aInfo;       /* Information for each worker */
3641e57430eSdrh   unsigned wkrFlags = 0;   /* Default worker flags */
3651e57430eSdrh   int nErr = 0;            /* Number of errors */
3661e57430eSdrh   int nTest = 0;           /* Number of tests */
3671e57430eSdrh   int rc;                  /* Return code */
3681e57430eSdrh   sqlite3 *db = 0;         /* Main database connection */
3691e57430eSdrh   pthread_mutex_t wrMutex; /* The write serialization mutex */
3701e57430eSdrh   WorkerInfo infoTop;      /* WorkerInfo for the main thread */
3711e57430eSdrh   WorkerInfo *p;           /* Pointer to infoTop */
3721e57430eSdrh 
3731e57430eSdrh   sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
3741e57430eSdrh   for(i=1; i<argc; i++){
3751e57430eSdrh     const char *z = argv[i];
3761e57430eSdrh     if( z[0]=='-' ){
3771e57430eSdrh       if( z[1]=='-' && z[2]!=0 ) z++;
3781e57430eSdrh       if( strcmp(z,"-multithread")==0 ){
3791e57430eSdrh         sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
3801e57430eSdrh         wkrFlags &= ~TT4_SERIALIZED;
3811e57430eSdrh       }else if( strcmp(z,"-serialized")==0 ){
3821e57430eSdrh         sqlite3_config(SQLITE_CONFIG_SERIALIZED);
3831e57430eSdrh         wkrFlags |= TT4_SERIALIZED;
3841e57430eSdrh       }else if( strcmp(z,"-wal")==0 ){
3851e57430eSdrh         wkrFlags |= TT4_WAL;
3861e57430eSdrh       }else if( strcmp(z,"-trace")==0 ){
3871e57430eSdrh         wkrFlags |= TT4_TRACE;
3881e57430eSdrh       }else{
3891e57430eSdrh         fprintf(stderr, "unknown command-line option: %s\n", argv[i]);
3901e57430eSdrh         exit(1);
3911e57430eSdrh       }
3921e57430eSdrh     }else if( z[0]>='1' && z[0]<='9' && nWorker==0 ){
3931e57430eSdrh       nWorker = atoi(z);
3941e57430eSdrh       if( nWorker<2 ){
3951e57430eSdrh         fprintf(stderr, "minimum of 2 threads\n");
3961e57430eSdrh         exit(1);
3971e57430eSdrh       }
3981e57430eSdrh     }else{
3991e57430eSdrh       fprintf(stderr, "extra command-line argument: \"%s\"\n", argv[i]);
4001e57430eSdrh       exit(1);
4011e57430eSdrh     }
4021e57430eSdrh   }
4031e57430eSdrh   if( nWorker==0 ){
4041e57430eSdrh     fprintf(stderr,
4051e57430eSdrh        "usage:  %s ?OPTIONS? N\n"
4061e57430eSdrh        "N is the number of threads and must be at least 2.\n"
4071e57430eSdrh        "Options:\n"
4081e57430eSdrh        "  --serialized\n"
4091e57430eSdrh        "  --multithread\n"
410ef15c6e9Sdrh        "  --wal\n"
411ef15c6e9Sdrh        "  --trace\n"
4121e57430eSdrh        ,argv[0]
4131e57430eSdrh     );
4141e57430eSdrh     exit(1);
4151e57430eSdrh   }
4161e57430eSdrh   if( !sqlite3_threadsafe() ){
4171e57430eSdrh     fprintf(stderr, "requires a threadsafe build of SQLite\n");
4181e57430eSdrh     exit(1);
4191e57430eSdrh   }
4201e57430eSdrh   sqlite3_initialize();
4211e57430eSdrh   sqlite3_enable_shared_cache(1);
4221e57430eSdrh   pthread_mutex_init(&wrMutex, 0);
4231e57430eSdrh 
4241e57430eSdrh   /* Initialize the test database files */
4251e57430eSdrh   (void)unlink("tt4-test1.db");
4261e57430eSdrh   (void)unlink("tt4-test2.db");
4271e57430eSdrh   (void)unlink("tt4-test3.db");
4281e57430eSdrh   rc = sqlite3_open("tt4-test1.db", &db);
4291e57430eSdrh   if( rc!=SQLITE_OK ){
4301e57430eSdrh     fprintf(stderr, "Unable to open test database: tt4-test2.db\n");
4311e57430eSdrh     exit(1);
4321e57430eSdrh   }
4331e57430eSdrh   memset(&infoTop, 0, sizeof(infoTop));
4341e57430eSdrh   infoTop.db = db;
4351e57430eSdrh   infoTop.wkrFlags = wkrFlags;
4361e57430eSdrh   p = &infoTop;
4371e57430eSdrh   if( wkrFlags & TT4_WAL ){
4381e57430eSdrh     run_sql(p, "PRAGMA journal_mode=WAL");
4391e57430eSdrh   }
4401e57430eSdrh   run_sql(p, "PRAGMA synchronous=OFF");
4411e57430eSdrh   run_sql(p, "CREATE TABLE IF NOT EXISTS t1(tid INTEGER, sp, a, b, c)");
4421e57430eSdrh   run_sql(p, "CREATE INDEX t1tid ON t1(tid)");
4431e57430eSdrh   run_sql(p, "CREATE INDEX t1ab ON t1(a,b)");
4441e57430eSdrh   run_sql(p, "ATTACH 'tt4-test2.db' AS 'test2'");
4451e57430eSdrh   run_sql(p, "CREATE TABLE IF NOT EXISTS test2.t2(tid INTEGER, sp, d, e, f)");
4461e57430eSdrh   run_sql(p, "CREATE INDEX test2.t2tid ON t2(tid)");
4471e57430eSdrh   run_sql(p, "CREATE INDEX test2.t2de ON t2(d,e)");
4481e57430eSdrh   run_sql(p, "ATTACH 'tt4-test3.db' AS 'test3'");
4491e57430eSdrh   run_sql(p, "CREATE TABLE IF NOT EXISTS test3.t3(tid INTEGER, sp, x, y, z)");
4501e57430eSdrh   run_sql(p, "CREATE INDEX test3.t3tid ON t3(tid)");
4511e57430eSdrh   run_sql(p, "CREATE INDEX test3.t3xy ON t3(x,y)");
4521e57430eSdrh   aInfo = safe_malloc( sizeof(*aInfo)*nWorker );
4531e57430eSdrh   memset(aInfo, 0, sizeof(*aInfo)*nWorker);
4541e57430eSdrh   for(i=0; i<nWorker; i++){
4551e57430eSdrh     aInfo[i].tid = i+1;
456ef15c6e9Sdrh     aInfo[i].nWorker = nWorker;
4571e57430eSdrh     aInfo[i].wkrFlags = wkrFlags;
4581e57430eSdrh     aInfo[i].mainDb = db;
4591e57430eSdrh     aInfo[i].pWrMutex = &wrMutex;
4601e57430eSdrh     rc = pthread_create(&aInfo[i].id, 0, worker_thread, &aInfo[i]);
4611e57430eSdrh     if( rc!=0 ){
4621e57430eSdrh       fprintf(stderr, "thread creation failed for thread %d\n", i+1);
4631e57430eSdrh       exit(1);
4641e57430eSdrh     }
4651e57430eSdrh     sched_yield();
4661e57430eSdrh   }
4671e57430eSdrh   for(i=0; i<nWorker; i++){
4681e57430eSdrh     pthread_join(aInfo[i].id, 0);
4691e57430eSdrh     printf("Joined thread %d: %d errors in %d tests",
4701e57430eSdrh            aInfo[i].tid, aInfo[i].nErr, aInfo[i].nTest);
4711e57430eSdrh     if( aInfo[i].zMsg ){
4721e57430eSdrh       printf(": %s\n", aInfo[i].zMsg);
4731e57430eSdrh     }else{
4741e57430eSdrh       printf("\n");
4751e57430eSdrh     }
4761e57430eSdrh     nErr += aInfo[i].nErr;
4771e57430eSdrh     nTest += aInfo[i].nTest;
4781e57430eSdrh     fflush(stdout);
4791e57430eSdrh   }
4801e57430eSdrh   sqlite3_close(db);
4811e57430eSdrh   sqlite3_free(aInfo);
4821e57430eSdrh   printf("Total %d errors in %d tests\n", nErr, nTest);
4831e57430eSdrh   return nErr;
4841e57430eSdrh }
485