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