Line data Source code
1 : /* this tests tdb by doing lots of ops from several simultaneous
2 : writers - that stresses the locking code.
3 : */
4 :
5 : #include "replace.h"
6 : #include "system/time.h"
7 : #include "system/wait.h"
8 : #include "system/filesys.h"
9 : #include "tdb.h"
10 :
11 : #ifdef HAVE_GETOPT_H
12 : #include <getopt.h>
13 : #endif
14 :
15 :
16 : #define REOPEN_PROB 30
17 : #define DELETE_PROB 8
18 : #define STORE_PROB 4
19 : #define APPEND_PROB 6
20 : #define TRANSACTION_PROB 10
21 : #define TRANSACTION_PREPARE_PROB 2
22 : #define LOCKSTORE_PROB 5
23 : #define TRAVERSE_PROB 20
24 : #define TRAVERSE_READ_PROB 20
25 : #define CULL_PROB 100
26 : #define KEYLEN 3
27 : #define DATALEN 100
28 :
29 : static struct tdb_context *db;
30 : static int in_transaction;
31 : static int error_count;
32 : static int always_transaction = 0;
33 : static int hash_size = 2;
34 : static unsigned loopnum;
35 : static int count_pipe;
36 : static bool mutex = false;
37 : static struct tdb_logging_context log_ctx;
38 :
39 : #ifdef PRINTF_ATTRIBUTE
40 : static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
41 : #endif
42 0 : static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...)
43 : {
44 : va_list ap;
45 :
46 : /* trace level messages do not indicate an error */
47 0 : if (level != TDB_DEBUG_TRACE) {
48 0 : error_count++;
49 : }
50 :
51 0 : va_start(ap, format);
52 0 : vfprintf(stdout, format, ap);
53 0 : va_end(ap);
54 0 : fflush(stdout);
55 : #if 0
56 : if (level != TDB_DEBUG_TRACE) {
57 : char *ptr;
58 : signal(SIGUSR1, SIG_IGN);
59 : asprintf(&ptr,"xterm -e gdb /proc/%d/exe %d", getpid(), getpid());
60 : system(ptr);
61 : free(ptr);
62 : }
63 : #endif
64 0 : }
65 :
66 0 : static void fatal(const char *why)
67 : {
68 0 : perror(why);
69 0 : error_count++;
70 0 : }
71 :
72 90000 : static char *randbuf(int len)
73 : {
74 : char *buf;
75 : int i;
76 90000 : buf = (char *)malloc(len+1);
77 :
78 2451609 : for (i=0;i<len;i++) {
79 2361609 : buf[i] = 'a' + (rand() % 26);
80 : }
81 90000 : buf[i] = 0;
82 90000 : return buf;
83 : }
84 :
85 512352 : static int cull_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
86 : void *state)
87 : {
88 : #if CULL_PROB
89 512352 : if (random() % CULL_PROB == 0) {
90 5184 : tdb_delete(tdb, key);
91 : }
92 : #endif
93 512352 : return 0;
94 : }
95 :
96 57408 : static bool do_transaction(void)
97 : {
98 : #if TRANSACTION_PROB
99 57408 : if (mutex) {
100 0 : return false;
101 : }
102 57408 : if (random() % TRANSACTION_PROB == 0) {
103 5782 : return true;
104 : }
105 : #endif
106 51626 : return false;
107 : }
108 :
109 45000 : static void addrec_db(void)
110 : {
111 : int klen, dlen;
112 : char *k, *d;
113 : TDB_DATA key, data;
114 :
115 45000 : klen = 1 + (rand() % KEYLEN);
116 45000 : dlen = 1 + (rand() % DATALEN);
117 :
118 45000 : k = randbuf(klen);
119 45000 : d = randbuf(dlen);
120 :
121 45000 : key.dptr = (unsigned char *)k;
122 45000 : key.dsize = klen+1;
123 :
124 45000 : data.dptr = (unsigned char *)d;
125 45000 : data.dsize = dlen+1;
126 :
127 : #if REOPEN_PROB
128 45000 : if (in_transaction == 0 && random() % REOPEN_PROB == 0) {
129 980 : tdb_reopen_all(0);
130 980 : goto next;
131 : }
132 : #endif
133 :
134 44020 : if (in_transaction == 0 &&
135 29130 : (always_transaction || do_transaction())) {
136 2893 : if (tdb_transaction_start(db) != 0) {
137 0 : fatal("tdb_transaction_start failed");
138 : }
139 2893 : in_transaction++;
140 2893 : goto next;
141 : }
142 41127 : if (in_transaction && do_transaction()) {
143 1502 : if (random() % TRANSACTION_PREPARE_PROB == 0) {
144 733 : if (tdb_transaction_prepare_commit(db) != 0) {
145 0 : fatal("tdb_transaction_prepare_commit failed");
146 : }
147 : }
148 1502 : if (tdb_transaction_commit(db) != 0) {
149 0 : fatal("tdb_transaction_commit failed");
150 : }
151 1502 : in_transaction--;
152 1502 : goto next;
153 : }
154 39625 : if (in_transaction && do_transaction()) {
155 1387 : if (tdb_transaction_cancel(db) != 0) {
156 0 : fatal("tdb_transaction_cancel failed");
157 : }
158 1387 : in_transaction--;
159 1387 : goto next;
160 : }
161 :
162 : #if DELETE_PROB
163 38238 : if (random() % DELETE_PROB == 0) {
164 4682 : tdb_delete(db, key);
165 4682 : goto next;
166 : }
167 : #endif
168 :
169 : #if STORE_PROB
170 33556 : if (random() % STORE_PROB == 0) {
171 8329 : if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
172 0 : fatal("tdb_store failed");
173 : }
174 8329 : goto next;
175 : }
176 : #endif
177 :
178 : #if APPEND_PROB
179 25227 : if (random() % APPEND_PROB == 0) {
180 4227 : if (tdb_append(db, key, data) != 0) {
181 0 : fatal("tdb_append failed");
182 : }
183 4227 : goto next;
184 : }
185 : #endif
186 :
187 : #if LOCKSTORE_PROB
188 21000 : if (random() % LOCKSTORE_PROB == 0) {
189 4163 : tdb_chainlock(db, key);
190 4163 : data = tdb_fetch(db, key);
191 4163 : if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
192 0 : fatal("tdb_store failed");
193 : }
194 4163 : if (data.dptr) free(data.dptr);
195 4163 : tdb_chainunlock(db, key);
196 4163 : goto next;
197 : }
198 : #endif
199 :
200 : #if TRAVERSE_PROB
201 16837 : if (random() % TRAVERSE_PROB == 0) {
202 845 : tdb_traverse(db, cull_traverse, NULL);
203 845 : goto next;
204 : }
205 : #endif
206 :
207 : #if TRAVERSE_READ_PROB
208 15992 : if (random() % TRAVERSE_READ_PROB == 0) {
209 781 : tdb_traverse_read(db, NULL, NULL);
210 781 : goto next;
211 : }
212 : #endif
213 :
214 15211 : data = tdb_fetch(db, key);
215 15211 : if (data.dptr) free(data.dptr);
216 :
217 9752 : next:
218 45000 : free(k);
219 45000 : free(d);
220 45000 : }
221 :
222 4045 : static int traverse_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
223 : void *state)
224 : {
225 4045 : tdb_delete(tdb, key);
226 4045 : return 0;
227 : }
228 :
229 0 : static void usage(void)
230 : {
231 0 : printf("Usage: tdbtorture [-t] [-k] [-m] [-n NUM_PROCS] [-l NUM_LOOPS] [-s SEED] [-H HASH_SIZE]\n");
232 0 : exit(0);
233 : }
234 :
235 0 : static void send_count_and_suicide(int sig)
236 : {
237 : ssize_t ret;
238 :
239 : /* This ensures our successor can continue where we left off. */
240 : do {
241 0 : ret = write(count_pipe, &loopnum, sizeof(loopnum));
242 0 : } while (ret == -1 && errno == EINTR);
243 : /* This gives a unique signature. */
244 0 : kill(getpid(), SIGUSR2);
245 0 : }
246 :
247 9 : static int run_child(const char *filename, int i, int seed, unsigned num_loops, unsigned start)
248 : {
249 9 : int tdb_flags = TDB_DEFAULT|TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH;
250 :
251 9 : if (mutex) {
252 0 : tdb_flags |= TDB_MUTEX_LOCKING;
253 : }
254 :
255 9 : db = tdb_open_ex(filename, hash_size, tdb_flags,
256 : O_RDWR | O_CREAT, 0600, &log_ctx, NULL);
257 9 : if (!db) {
258 0 : fatal("db open failed");
259 : }
260 :
261 9 : srand(seed + i);
262 9 : srandom(seed + i);
263 :
264 : /* Set global, then we're ready to handle being killed. */
265 9 : loopnum = start;
266 9 : signal(SIGUSR1, send_count_and_suicide);
267 :
268 45009 : for (;loopnum<num_loops && error_count == 0;loopnum++) {
269 45000 : addrec_db();
270 : }
271 :
272 9 : if (error_count == 0) {
273 9 : tdb_traverse_read(db, NULL, NULL);
274 9 : if (always_transaction) {
275 0 : while (in_transaction) {
276 0 : tdb_transaction_cancel(db);
277 0 : in_transaction--;
278 : }
279 0 : if (tdb_transaction_start(db) != 0)
280 0 : fatal("tdb_transaction_start failed");
281 : }
282 9 : tdb_traverse(db, traverse_fn, NULL);
283 9 : tdb_traverse(db, traverse_fn, NULL);
284 9 : if (always_transaction) {
285 0 : if (tdb_transaction_commit(db) != 0)
286 0 : fatal("tdb_transaction_commit failed");
287 : }
288 : }
289 :
290 9 : tdb_close(db);
291 :
292 9 : return (error_count < 100 ? error_count : 100);
293 : }
294 :
295 3 : static char *test_path(const char *filename)
296 : {
297 3 : const char *prefix = getenv("TEST_DATA_PREFIX");
298 :
299 3 : if (prefix) {
300 3 : char *path = NULL;
301 : int ret;
302 :
303 3 : ret = asprintf(&path, "%s/%s", prefix, filename);
304 3 : if (ret == -1) {
305 0 : return NULL;
306 : }
307 3 : return path;
308 : }
309 :
310 0 : return strdup(filename);
311 : }
312 :
313 3 : int main(int argc, char * const *argv)
314 : {
315 3 : int i, seed = -1;
316 3 : int num_loops = 5000;
317 3 : int num_procs = 3;
318 : int c, pfds[2];
319 : extern char *optarg;
320 : pid_t *pids;
321 3 : int kill_random = 0;
322 : int *done;
323 : char *test_tdb;
324 :
325 3 : log_ctx.log_fn = tdb_log;
326 :
327 3 : while ((c = getopt(argc, argv, "n:l:s:H:thkm")) != -1) {
328 0 : switch (c) {
329 0 : case 'n':
330 0 : num_procs = strtol(optarg, NULL, 0);
331 0 : break;
332 0 : case 'l':
333 0 : num_loops = strtol(optarg, NULL, 0);
334 0 : break;
335 0 : case 'H':
336 0 : hash_size = strtol(optarg, NULL, 0);
337 0 : break;
338 0 : case 's':
339 0 : seed = strtol(optarg, NULL, 0);
340 0 : break;
341 0 : case 't':
342 0 : always_transaction = 1;
343 0 : break;
344 0 : case 'k':
345 0 : kill_random = 1;
346 0 : break;
347 0 : case 'm':
348 0 : mutex = tdb_runtime_check_for_robust_mutexes();
349 0 : if (!mutex) {
350 0 : printf("tdb_runtime_check_for_robust_mutexes() returned false\n");
351 0 : exit(1);
352 : }
353 0 : break;
354 0 : default:
355 0 : usage();
356 : }
357 : }
358 :
359 3 : test_tdb = test_path("torture.tdb");
360 :
361 3 : unlink(test_tdb);
362 :
363 3 : if (seed == -1) {
364 3 : seed = (getpid() + time(NULL)) & 0x7FFFFFFF;
365 : }
366 :
367 3 : printf("Testing with %d processes, %d loops, %d hash_size, seed=%d%s\n",
368 : num_procs, num_loops, hash_size, seed,
369 3 : (always_transaction ? " (all within transactions)" : ""));
370 :
371 3 : if (num_procs == 1 && !kill_random) {
372 : /* Don't fork for this case, makes debugging easier. */
373 0 : error_count = run_child(test_tdb, 0, seed, num_loops, 0);
374 0 : goto done;
375 : }
376 :
377 3 : pids = (pid_t *)calloc(sizeof(pid_t), num_procs);
378 3 : if (pids == NULL) {
379 0 : perror("Unable to allocate memory for pids");
380 0 : exit(1);
381 : }
382 3 : done = (int *)calloc(sizeof(int), num_procs);
383 3 : if (done == NULL) {
384 0 : perror("Unable to allocate memory for done");
385 0 : exit(1);
386 : }
387 :
388 3 : if (pipe(pfds) != 0) {
389 0 : perror("Creating pipe");
390 0 : exit(1);
391 : }
392 3 : count_pipe = pfds[1];
393 :
394 12 : for (i=0;i<num_procs;i++) {
395 9 : if ((pids[i]=fork()) == 0) {
396 9 : close(pfds[0]);
397 9 : exit(run_child(test_tdb, i, seed, num_loops, 0));
398 : }
399 : }
400 :
401 12 : while (num_procs) {
402 : int status, j;
403 : pid_t pid;
404 :
405 9 : if (error_count != 0) {
406 : /* try and stop the test on any failure */
407 0 : for (j=0;j<num_procs;j++) {
408 0 : if (pids[j] != 0) {
409 0 : kill(pids[j], SIGTERM);
410 : }
411 : }
412 : }
413 :
414 9 : pid = waitpid(-1, &status, kill_random ? WNOHANG : 0);
415 9 : if (pid == 0) {
416 : struct timeval tv;
417 :
418 : /* Sleep for 1/10 second. */
419 0 : tv.tv_sec = 0;
420 0 : tv.tv_usec = 100000;
421 0 : select(0, NULL, NULL, NULL, &tv);
422 :
423 : /* Kill someone. */
424 0 : kill(pids[random() % num_procs], SIGUSR1);
425 0 : continue;
426 : }
427 :
428 9 : if (pid == -1) {
429 0 : perror("failed to wait for child\n");
430 0 : exit(1);
431 : }
432 :
433 16 : for (j=0;j<num_procs;j++) {
434 16 : if (pids[j] == pid) break;
435 : }
436 9 : if (j == num_procs) {
437 0 : printf("unknown child %d exited!?\n", (int)pid);
438 0 : exit(1);
439 : }
440 9 : if (WIFSIGNALED(status)) {
441 0 : if (WTERMSIG(status) == SIGUSR2
442 0 : || WTERMSIG(status) == SIGUSR1) {
443 : /* SIGUSR2 means they wrote to pipe. */
444 0 : if (WTERMSIG(status) == SIGUSR2) {
445 : ssize_t ret;
446 :
447 : do {
448 0 : ret = read(pfds[0], &done[j],
449 : sizeof(done[j]));
450 0 : } while (ret == -1 && errno == EINTR);
451 : }
452 0 : pids[j] = fork();
453 0 : if (pids[j] == 0)
454 0 : exit(run_child(test_tdb, j, seed,
455 0 : num_loops, done[j]));
456 0 : printf("Restarting child %i for %u-%u\n",
457 0 : j, done[j], num_loops);
458 0 : continue;
459 : }
460 0 : printf("child %d exited with signal %d\n",
461 : (int)pid, WTERMSIG(status));
462 0 : error_count++;
463 : } else {
464 9 : if (WEXITSTATUS(status) != 0) {
465 0 : printf("child %d exited with status %d\n",
466 0 : (int)pid, WEXITSTATUS(status));
467 0 : error_count++;
468 : }
469 : }
470 9 : ARRAY_DEL_ELEMENT(pids, j, num_procs);
471 9 : num_procs--;
472 : }
473 :
474 3 : free(pids);
475 :
476 3 : done:
477 3 : if (error_count == 0) {
478 3 : int tdb_flags = TDB_DEFAULT;
479 :
480 3 : if (mutex) {
481 0 : tdb_flags |= TDB_NOLOCK;
482 : }
483 :
484 3 : db = tdb_open_ex(test_tdb, hash_size, tdb_flags,
485 : O_RDWR, 0, &log_ctx, NULL);
486 3 : if (!db) {
487 0 : fatal("db open failed\n");
488 0 : exit(1);
489 : }
490 3 : if (tdb_check(db, NULL, NULL) == -1) {
491 0 : printf("db check failed\n");
492 0 : exit(1);
493 : }
494 3 : tdb_close(db);
495 3 : printf("OK\n");
496 : }
497 :
498 3 : free(test_tdb);
499 3 : return error_count;
500 : }
|