|
1 /* |
|
2 ** 2002 January 15 |
|
3 ** |
|
4 ** The author disclaims copyright to this source code. In place of |
|
5 ** a legal notice, here is a blessing: |
|
6 ** |
|
7 ** May you do good and not evil. |
|
8 ** May you find forgiveness for yourself and forgive others. |
|
9 ** May you share freely, never taking more than you give. |
|
10 ** |
|
11 ************************************************************************* |
|
12 ** This file implements a simple standalone program used to test whether |
|
13 ** or not the SQLite library is threadsafe. |
|
14 ** |
|
15 ** Testing the thread safety of SQLite is difficult because there are very |
|
16 ** few places in the code that are even potentially unsafe, and those |
|
17 ** places execute for very short periods of time. So even if the library |
|
18 ** is compiled with its mutexes disabled, it is likely to work correctly |
|
19 ** in a multi-threaded program most of the time. |
|
20 ** |
|
21 ** This file is NOT part of the standard SQLite library. It is used for |
|
22 ** testing only. |
|
23 */ |
|
24 #include "sqlite.h" |
|
25 #include <pthread.h> |
|
26 #include <sched.h> |
|
27 #include <stdio.h> |
|
28 #include <stdlib.h> |
|
29 #include <string.h> |
|
30 #include <unistd.h> |
|
31 |
|
32 /* |
|
33 ** Enable for tracing |
|
34 */ |
|
35 static int verbose = 0; |
|
36 |
|
37 /* |
|
38 ** Come here to die. |
|
39 */ |
|
40 static void Exit(int rc){ |
|
41 exit(rc); |
|
42 } |
|
43 |
|
44 extern char *sqlite3_mprintf(const char *zFormat, ...); |
|
45 extern char *sqlite3_vmprintf(const char *zFormat, va_list); |
|
46 |
|
47 /* |
|
48 ** When a lock occurs, yield. |
|
49 */ |
|
50 static int db_is_locked(void *NotUsed, int iCount){ |
|
51 /* sched_yield(); */ |
|
52 if( verbose ) printf("BUSY %s #%d\n", (char*)NotUsed, iCount); |
|
53 usleep(100); |
|
54 return iCount<25; |
|
55 } |
|
56 |
|
57 /* |
|
58 ** Used to accumulate query results by db_query() |
|
59 */ |
|
60 struct QueryResult { |
|
61 const char *zFile; /* Filename - used for error reporting */ |
|
62 int nElem; /* Number of used entries in azElem[] */ |
|
63 int nAlloc; /* Number of slots allocated for azElem[] */ |
|
64 char **azElem; /* The result of the query */ |
|
65 }; |
|
66 |
|
67 /* |
|
68 ** The callback function for db_query |
|
69 */ |
|
70 static int db_query_callback( |
|
71 void *pUser, /* Pointer to the QueryResult structure */ |
|
72 int nArg, /* Number of columns in this result row */ |
|
73 char **azArg, /* Text of data in all columns */ |
|
74 char **NotUsed /* Names of the columns */ |
|
75 ){ |
|
76 struct QueryResult *pResult = (struct QueryResult*)pUser; |
|
77 int i; |
|
78 if( pResult->nElem + nArg >= pResult->nAlloc ){ |
|
79 if( pResult->nAlloc==0 ){ |
|
80 pResult->nAlloc = nArg+1; |
|
81 }else{ |
|
82 pResult->nAlloc = pResult->nAlloc*2 + nArg + 1; |
|
83 } |
|
84 pResult->azElem = realloc( pResult->azElem, pResult->nAlloc*sizeof(char*)); |
|
85 if( pResult->azElem==0 ){ |
|
86 fprintf(stdout,"%s: malloc failed\n", pResult->zFile); |
|
87 return 1; |
|
88 } |
|
89 } |
|
90 if( azArg==0 ) return 0; |
|
91 for(i=0; i<nArg; i++){ |
|
92 pResult->azElem[pResult->nElem++] = |
|
93 sqlite3_mprintf("%s",azArg[i] ? azArg[i] : ""); |
|
94 } |
|
95 return 0; |
|
96 } |
|
97 |
|
98 /* |
|
99 ** Execute a query against the database. NULL values are returned |
|
100 ** as an empty string. The list is terminated by a single NULL pointer. |
|
101 */ |
|
102 char **db_query(sqlite *db, const char *zFile, const char *zFormat, ...){ |
|
103 char *zSql; |
|
104 int rc; |
|
105 char *zErrMsg = 0; |
|
106 va_list ap; |
|
107 struct QueryResult sResult; |
|
108 va_start(ap, zFormat); |
|
109 zSql = sqlite3_vmprintf(zFormat, ap); |
|
110 va_end(ap); |
|
111 memset(&sResult, 0, sizeof(sResult)); |
|
112 sResult.zFile = zFile; |
|
113 if( verbose ) printf("QUERY %s: %s\n", zFile, zSql); |
|
114 rc = sqlite3_exec(db, zSql, db_query_callback, &sResult, &zErrMsg); |
|
115 if( rc==SQLITE_SCHEMA ){ |
|
116 if( zErrMsg ) free(zErrMsg); |
|
117 rc = sqlite3_exec(db, zSql, db_query_callback, &sResult, &zErrMsg); |
|
118 } |
|
119 if( verbose ) printf("DONE %s %s\n", zFile, zSql); |
|
120 if( zErrMsg ){ |
|
121 fprintf(stdout,"%s: query failed: %s - %s\n", zFile, zSql, zErrMsg); |
|
122 free(zErrMsg); |
|
123 free(zSql); |
|
124 Exit(1); |
|
125 } |
|
126 sqlite3_free(zSql); |
|
127 if( sResult.azElem==0 ){ |
|
128 db_query_callback(&sResult, 0, 0, 0); |
|
129 } |
|
130 sResult.azElem[sResult.nElem] = 0; |
|
131 return sResult.azElem; |
|
132 } |
|
133 |
|
134 /* |
|
135 ** Execute an SQL statement. |
|
136 */ |
|
137 void db_execute(sqlite *db, const char *zFile, const char *zFormat, ...){ |
|
138 char *zSql; |
|
139 int rc; |
|
140 char *zErrMsg = 0; |
|
141 va_list ap; |
|
142 va_start(ap, zFormat); |
|
143 zSql = sqlite3_vmprintf(zFormat, ap); |
|
144 va_end(ap); |
|
145 if( verbose ) printf("EXEC %s: %s\n", zFile, zSql); |
|
146 do{ |
|
147 rc = sqlite3_exec(db, zSql, 0, 0, &zErrMsg); |
|
148 }while( rc==SQLITE_BUSY ); |
|
149 if( verbose ) printf("DONE %s: %s\n", zFile, zSql); |
|
150 if( zErrMsg ){ |
|
151 fprintf(stdout,"%s: command failed: %s - %s\n", zFile, zSql, zErrMsg); |
|
152 free(zErrMsg); |
|
153 sqlite3_free(zSql); |
|
154 Exit(1); |
|
155 } |
|
156 sqlite3_free(zSql); |
|
157 } |
|
158 |
|
159 /* |
|
160 ** Free the results of a db_query() call. |
|
161 */ |
|
162 void db_query_free(char **az){ |
|
163 int i; |
|
164 for(i=0; az[i]; i++){ |
|
165 sqlite3_free(az[i]); |
|
166 } |
|
167 free(az); |
|
168 } |
|
169 |
|
170 /* |
|
171 ** Check results |
|
172 */ |
|
173 void db_check(const char *zFile, const char *zMsg, char **az, ...){ |
|
174 va_list ap; |
|
175 int i; |
|
176 char *z; |
|
177 va_start(ap, az); |
|
178 for(i=0; (z = va_arg(ap, char*))!=0; i++){ |
|
179 if( az[i]==0 || strcmp(az[i],z)!=0 ){ |
|
180 fprintf(stdout,"%s: %s: bad result in column %d: %s\n", |
|
181 zFile, zMsg, i+1, az[i]); |
|
182 db_query_free(az); |
|
183 Exit(1); |
|
184 } |
|
185 } |
|
186 va_end(ap); |
|
187 db_query_free(az); |
|
188 } |
|
189 |
|
190 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; |
|
191 pthread_cond_t sig = PTHREAD_COND_INITIALIZER; |
|
192 int thread_cnt = 0; |
|
193 |
|
194 static void *worker_bee(void *pArg){ |
|
195 const char *zFilename = (char*)pArg; |
|
196 char *azErr; |
|
197 int i, cnt; |
|
198 int t = atoi(zFilename); |
|
199 char **az; |
|
200 sqlite *db; |
|
201 |
|
202 pthread_mutex_lock(&lock); |
|
203 thread_cnt++; |
|
204 pthread_mutex_unlock(&lock); |
|
205 printf("%s: START\n", zFilename); |
|
206 fflush(stdout); |
|
207 for(cnt=0; cnt<10; cnt++){ |
|
208 sqlite3_open(&zFilename[2], &db); |
|
209 if( db==0 ){ |
|
210 fprintf(stdout,"%s: can't open\n", zFilename); |
|
211 Exit(1); |
|
212 } |
|
213 sqlite3_busy_handler(db, db_is_locked, zFilename); |
|
214 db_execute(db, zFilename, "CREATE TABLE t%d(a,b,c);", t); |
|
215 for(i=1; i<=100; i++){ |
|
216 db_execute(db, zFilename, "INSERT INTO t%d VALUES(%d,%d,%d);", |
|
217 t, i, i*2, i*i); |
|
218 } |
|
219 az = db_query(db, zFilename, "SELECT count(*) FROM t%d", t); |
|
220 db_check(zFilename, "tX size", az, "100", 0); |
|
221 az = db_query(db, zFilename, "SELECT avg(b) FROM t%d", t); |
|
222 db_check(zFilename, "tX avg", az, "101", 0); |
|
223 db_execute(db, zFilename, "DELETE FROM t%d WHERE a>50", t); |
|
224 az = db_query(db, zFilename, "SELECT avg(b) FROM t%d", t); |
|
225 db_check(zFilename, "tX avg2", az, "51", 0); |
|
226 for(i=1; i<=50; i++){ |
|
227 char z1[30], z2[30]; |
|
228 az = db_query(db, zFilename, "SELECT b, c FROM t%d WHERE a=%d", t, i); |
|
229 sprintf(z1, "%d", i*2); |
|
230 sprintf(z2, "%d", i*i); |
|
231 db_check(zFilename, "readback", az, z1, z2, 0); |
|
232 } |
|
233 db_execute(db, zFilename, "DROP TABLE t%d;", t); |
|
234 sqlite3_close(db); |
|
235 } |
|
236 printf("%s: END\n", zFilename); |
|
237 /* unlink(zFilename); */ |
|
238 fflush(stdout); |
|
239 pthread_mutex_lock(&lock); |
|
240 thread_cnt--; |
|
241 if( thread_cnt<=0 ){ |
|
242 pthread_cond_signal(&sig); |
|
243 } |
|
244 pthread_mutex_unlock(&lock); |
|
245 return 0; |
|
246 } |
|
247 |
|
248 int main(int argc, char **argv){ |
|
249 char *zFile; |
|
250 int i, n; |
|
251 pthread_t id; |
|
252 if( argc>2 && strcmp(argv[1], "-v")==0 ){ |
|
253 verbose = 1; |
|
254 argc--; |
|
255 argv++; |
|
256 } |
|
257 if( argc<2 || (n=atoi(argv[1]))<1 ) n = 10; |
|
258 for(i=0; i<n; i++){ |
|
259 char zBuf[200]; |
|
260 sprintf(zBuf, "testdb-%d", (i+1)/2); |
|
261 unlink(zBuf); |
|
262 } |
|
263 for(i=0; i<n; i++){ |
|
264 zFile = sqlite3_mprintf("%d.testdb-%d", i%2+1, (i+2)/2); |
|
265 if( (i%2)==0 ){ |
|
266 /* Remove both the database file and any old journal for the file |
|
267 ** being used by this thread and the next one. */ |
|
268 char *zDb = &zFile[2]; |
|
269 char *zJournal = sqlite3_mprintf("%s-journal", zDb); |
|
270 unlink(zDb); |
|
271 unlink(zJournal); |
|
272 free(zJournal); |
|
273 } |
|
274 |
|
275 pthread_create(&id, 0, worker_bee, (void*)zFile); |
|
276 pthread_detach(id); |
|
277 } |
|
278 pthread_mutex_lock(&lock); |
|
279 while( thread_cnt>0 ){ |
|
280 pthread_cond_wait(&sig, &lock); |
|
281 } |
|
282 pthread_mutex_unlock(&lock); |
|
283 for(i=0; i<n; i++){ |
|
284 char zBuf[200]; |
|
285 sprintf(zBuf, "testdb-%d", (i+1)/2); |
|
286 unlink(zBuf); |
|
287 } |
|
288 return 0; |
|
289 } |