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