sl@0: /* sl@0: ** 2002 January 15 sl@0: ** sl@0: ** The author disclaims copyright to this source code. In place of sl@0: ** a legal notice, here is a blessing: sl@0: ** sl@0: ** May you do good and not evil. sl@0: ** May you find forgiveness for yourself and forgive others. sl@0: ** May you share freely, never taking more than you give. sl@0: ** sl@0: ************************************************************************* sl@0: ** This file implements a simple standalone program used to test whether sl@0: ** or not the SQLite library is threadsafe. sl@0: ** sl@0: ** Testing the thread safety of SQLite is difficult because there are very sl@0: ** few places in the code that are even potentially unsafe, and those sl@0: ** places execute for very short periods of time. So even if the library sl@0: ** is compiled with its mutexes disabled, it is likely to work correctly sl@0: ** in a multi-threaded program most of the time. sl@0: ** sl@0: ** This file is NOT part of the standard SQLite library. It is used for sl@0: ** testing only. sl@0: */ sl@0: #include "sqlite.h" sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: sl@0: /* sl@0: ** Enable for tracing sl@0: */ sl@0: static int verbose = 0; sl@0: sl@0: /* sl@0: ** Come here to die. sl@0: */ sl@0: static void Exit(int rc){ sl@0: exit(rc); sl@0: } sl@0: sl@0: extern char *sqlite3_mprintf(const char *zFormat, ...); sl@0: extern char *sqlite3_vmprintf(const char *zFormat, va_list); sl@0: sl@0: /* sl@0: ** When a lock occurs, yield. sl@0: */ sl@0: static int db_is_locked(void *NotUsed, int iCount){ sl@0: /* sched_yield(); */ sl@0: if( verbose ) printf("BUSY %s #%d\n", (char*)NotUsed, iCount); sl@0: usleep(100); sl@0: return iCount<25; sl@0: } sl@0: sl@0: /* sl@0: ** Used to accumulate query results by db_query() sl@0: */ sl@0: struct QueryResult { sl@0: const char *zFile; /* Filename - used for error reporting */ sl@0: int nElem; /* Number of used entries in azElem[] */ sl@0: int nAlloc; /* Number of slots allocated for azElem[] */ sl@0: char **azElem; /* The result of the query */ sl@0: }; sl@0: sl@0: /* sl@0: ** The callback function for db_query sl@0: */ sl@0: static int db_query_callback( sl@0: void *pUser, /* Pointer to the QueryResult structure */ sl@0: int nArg, /* Number of columns in this result row */ sl@0: char **azArg, /* Text of data in all columns */ sl@0: char **NotUsed /* Names of the columns */ sl@0: ){ sl@0: struct QueryResult *pResult = (struct QueryResult*)pUser; sl@0: int i; sl@0: if( pResult->nElem + nArg >= pResult->nAlloc ){ sl@0: if( pResult->nAlloc==0 ){ sl@0: pResult->nAlloc = nArg+1; sl@0: }else{ sl@0: pResult->nAlloc = pResult->nAlloc*2 + nArg + 1; sl@0: } sl@0: pResult->azElem = realloc( pResult->azElem, pResult->nAlloc*sizeof(char*)); sl@0: if( pResult->azElem==0 ){ sl@0: fprintf(stdout,"%s: malloc failed\n", pResult->zFile); sl@0: return 1; sl@0: } sl@0: } sl@0: if( azArg==0 ) return 0; sl@0: for(i=0; iazElem[pResult->nElem++] = sl@0: sqlite3_mprintf("%s",azArg[i] ? azArg[i] : ""); sl@0: } sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: ** Execute a query against the database. NULL values are returned sl@0: ** as an empty string. The list is terminated by a single NULL pointer. sl@0: */ sl@0: char **db_query(sqlite *db, const char *zFile, const char *zFormat, ...){ sl@0: char *zSql; sl@0: int rc; sl@0: char *zErrMsg = 0; sl@0: va_list ap; sl@0: struct QueryResult sResult; sl@0: va_start(ap, zFormat); sl@0: zSql = sqlite3_vmprintf(zFormat, ap); sl@0: va_end(ap); sl@0: memset(&sResult, 0, sizeof(sResult)); sl@0: sResult.zFile = zFile; sl@0: if( verbose ) printf("QUERY %s: %s\n", zFile, zSql); sl@0: rc = sqlite3_exec(db, zSql, db_query_callback, &sResult, &zErrMsg); sl@0: if( rc==SQLITE_SCHEMA ){ sl@0: if( zErrMsg ) free(zErrMsg); sl@0: rc = sqlite3_exec(db, zSql, db_query_callback, &sResult, &zErrMsg); sl@0: } sl@0: if( verbose ) printf("DONE %s %s\n", zFile, zSql); sl@0: if( zErrMsg ){ sl@0: fprintf(stdout,"%s: query failed: %s - %s\n", zFile, zSql, zErrMsg); sl@0: free(zErrMsg); sl@0: free(zSql); sl@0: Exit(1); sl@0: } sl@0: sqlite3_free(zSql); sl@0: if( sResult.azElem==0 ){ sl@0: db_query_callback(&sResult, 0, 0, 0); sl@0: } sl@0: sResult.azElem[sResult.nElem] = 0; sl@0: return sResult.azElem; sl@0: } sl@0: sl@0: /* sl@0: ** Execute an SQL statement. sl@0: */ sl@0: void db_execute(sqlite *db, const char *zFile, const char *zFormat, ...){ sl@0: char *zSql; sl@0: int rc; sl@0: char *zErrMsg = 0; sl@0: va_list ap; sl@0: va_start(ap, zFormat); sl@0: zSql = sqlite3_vmprintf(zFormat, ap); sl@0: va_end(ap); sl@0: if( verbose ) printf("EXEC %s: %s\n", zFile, zSql); sl@0: do{ sl@0: rc = sqlite3_exec(db, zSql, 0, 0, &zErrMsg); sl@0: }while( rc==SQLITE_BUSY ); sl@0: if( verbose ) printf("DONE %s: %s\n", zFile, zSql); sl@0: if( zErrMsg ){ sl@0: fprintf(stdout,"%s: command failed: %s - %s\n", zFile, zSql, zErrMsg); sl@0: free(zErrMsg); sl@0: sqlite3_free(zSql); sl@0: Exit(1); sl@0: } sl@0: sqlite3_free(zSql); sl@0: } sl@0: sl@0: /* sl@0: ** Free the results of a db_query() call. sl@0: */ sl@0: void db_query_free(char **az){ sl@0: int i; sl@0: for(i=0; az[i]; i++){ sl@0: sqlite3_free(az[i]); sl@0: } sl@0: free(az); sl@0: } sl@0: sl@0: /* sl@0: ** Check results sl@0: */ sl@0: void db_check(const char *zFile, const char *zMsg, char **az, ...){ sl@0: va_list ap; sl@0: int i; sl@0: char *z; sl@0: va_start(ap, az); sl@0: for(i=0; (z = va_arg(ap, char*))!=0; i++){ sl@0: if( az[i]==0 || strcmp(az[i],z)!=0 ){ sl@0: fprintf(stdout,"%s: %s: bad result in column %d: %s\n", sl@0: zFile, zMsg, i+1, az[i]); sl@0: db_query_free(az); sl@0: Exit(1); sl@0: } sl@0: } sl@0: va_end(ap); sl@0: db_query_free(az); sl@0: } sl@0: sl@0: pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; sl@0: pthread_cond_t sig = PTHREAD_COND_INITIALIZER; sl@0: int thread_cnt = 0; sl@0: sl@0: static void *worker_bee(void *pArg){ sl@0: const char *zFilename = (char*)pArg; sl@0: char *azErr; sl@0: int i, cnt; sl@0: int t = atoi(zFilename); sl@0: char **az; sl@0: sqlite *db; sl@0: sl@0: pthread_mutex_lock(&lock); sl@0: thread_cnt++; sl@0: pthread_mutex_unlock(&lock); sl@0: printf("%s: START\n", zFilename); sl@0: fflush(stdout); sl@0: for(cnt=0; cnt<10; cnt++){ sl@0: sqlite3_open(&zFilename[2], &db); sl@0: if( db==0 ){ sl@0: fprintf(stdout,"%s: can't open\n", zFilename); sl@0: Exit(1); sl@0: } sl@0: sqlite3_busy_handler(db, db_is_locked, zFilename); sl@0: db_execute(db, zFilename, "CREATE TABLE t%d(a,b,c);", t); sl@0: for(i=1; i<=100; i++){ sl@0: db_execute(db, zFilename, "INSERT INTO t%d VALUES(%d,%d,%d);", sl@0: t, i, i*2, i*i); sl@0: } sl@0: az = db_query(db, zFilename, "SELECT count(*) FROM t%d", t); sl@0: db_check(zFilename, "tX size", az, "100", 0); sl@0: az = db_query(db, zFilename, "SELECT avg(b) FROM t%d", t); sl@0: db_check(zFilename, "tX avg", az, "101", 0); sl@0: db_execute(db, zFilename, "DELETE FROM t%d WHERE a>50", t); sl@0: az = db_query(db, zFilename, "SELECT avg(b) FROM t%d", t); sl@0: db_check(zFilename, "tX avg2", az, "51", 0); sl@0: for(i=1; i<=50; i++){ sl@0: char z1[30], z2[30]; sl@0: az = db_query(db, zFilename, "SELECT b, c FROM t%d WHERE a=%d", t, i); sl@0: sprintf(z1, "%d", i*2); sl@0: sprintf(z2, "%d", i*i); sl@0: db_check(zFilename, "readback", az, z1, z2, 0); sl@0: } sl@0: db_execute(db, zFilename, "DROP TABLE t%d;", t); sl@0: sqlite3_close(db); sl@0: } sl@0: printf("%s: END\n", zFilename); sl@0: /* unlink(zFilename); */ sl@0: fflush(stdout); sl@0: pthread_mutex_lock(&lock); sl@0: thread_cnt--; sl@0: if( thread_cnt<=0 ){ sl@0: pthread_cond_signal(&sig); sl@0: } sl@0: pthread_mutex_unlock(&lock); sl@0: return 0; sl@0: } sl@0: sl@0: int main(int argc, char **argv){ sl@0: char *zFile; sl@0: int i, n; sl@0: pthread_t id; sl@0: if( argc>2 && strcmp(argv[1], "-v")==0 ){ sl@0: verbose = 1; sl@0: argc--; sl@0: argv++; sl@0: } sl@0: if( argc<2 || (n=atoi(argv[1]))<1 ) n = 10; sl@0: for(i=0; i0 ){ sl@0: pthread_cond_wait(&sig, &lock); sl@0: } sl@0: pthread_mutex_unlock(&lock); sl@0: for(i=0; i