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