1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 // vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4:
3 #ident "$Id$"
4 /*======
5 This file is part of PerconaFT.
6 
7 
8 Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved.
9 
10     PerconaFT is free software: you can redistribute it and/or modify
11     it under the terms of the GNU General Public License, version 2,
12     as published by the Free Software Foundation.
13 
14     PerconaFT is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License
20     along with PerconaFT.  If not, see <http://www.gnu.org/licenses/>.
21 
22 ----------------------------------------
23 
24     PerconaFT is free software: you can redistribute it and/or modify
25     it under the terms of the GNU Affero General Public License, version 3,
26     as published by the Free Software Foundation.
27 
28     PerconaFT is distributed in the hope that it will be useful,
29     but WITHOUT ANY WARRANTY; without even the implied warranty of
30     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31     GNU Affero General Public License for more details.
32 
33     You should have received a copy of the GNU Affero General Public License
34     along with PerconaFT.  If not, see <http://www.gnu.org/licenses/>.
35 ======= */
36 
37 #ident "Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved."
38 
39 #include "test.h"
40 #include "toku_pthread.h"
41 #include <db.h>
42 #include <sys/stat.h>
43 #include <stdlib.h>
44 
45 static const int NUM_DICTIONARIES = 100;
46 //static const int NUM_DICTIONARIES = 3;
47 static const char *table = "tbl";
48 static const int ROWS_PER_TABLE = 10;
49 
50 DB_ENV *env;
51 DB** db_array;
52 DB* states;
53 static const int percent_do_op = 20;
54 static const int percent_do_abort = 25;
55 static const int start_crashing_iter = 10;
56 // iterations_per_crash_in_recovery should be an odd number;
57 static const int iterations_per_crash_in_recovery = 7;
58 const char *state_db_name="states.db";
59 
60 #define CREATED 0
61 #define OPEN    1
62 #define CLOSED  2
63 #define DELETED 3
64 
65 #define COMMIT_TXN 0
66 #define ABORT_TXN 1
67 
commit_or_abort(void)68 static int commit_or_abort(void) {
69     int i = random() % 100;
70     int rval = ( i < percent_do_abort ) ? ABORT_TXN : COMMIT_TXN;
71     if ( verbose ) {
72         if ( rval == ABORT_TXN ) printf("%s :     abort txn\n", __FILE__);
73     }
74     return rval;
75 }
76 
put_state(int db_num,int state)77 static void put_state(int db_num, int state) {
78     int r;
79     DB_TXN* txn;
80     DBT key, val;
81     int key_data = db_num;
82     int val_data = state;
83     r = env->txn_begin(env, NULL, &txn, 0);                                                               CKERR(r);
84     r = states->put(states, txn,
85                     dbt_init(&key, &key_data, sizeof(key_data)),
86                     dbt_init(&val, &val_data, sizeof(val_data)),
87                     0);                                                                                   CKERR(r);
88     r = txn->commit(txn, 0);                                                                              CKERR(r);
89 }
90 
get_state(int db_num)91 static int get_state(int db_num) {
92     int r;
93     DBT key, val;
94 
95     memset(&val, 0, sizeof(val));
96     r = states->get(states, 0,
97                     dbt_init(&key, &db_num, sizeof(db_num)),
98                     &val,
99                     0);
100     CKERR(r);
101     int state = *(int*)val.data;
102     return state;
103 }
104 
105 static int crash_timer;
106 static void crash_it(void);
107 static void crash_it_callback_f(void*);
set_crash_timer(void)108 static void set_crash_timer(void) {
109     crash_timer = random() % (3 * NUM_DICTIONARIES);
110 }
111 
update_crash_timer(void)112 static void update_crash_timer(void) {
113     if ( --crash_timer == 0 ) {
114         // close the states table before we crash
115         int r = states->close(states, 0);
116         CKERR(r);
117         if ( verbose ) {
118             printf("%s : crash\n", __FILE__);
119             fflush(stdout);
120         }
121         crash_it();
122     }
123 }
124 
125 static void    env_startup(int recovery_flags);
126 static int64_t generate_val(int64_t key);
127 static void    insert_n(DB *db, DB_TXN *txn, int firstkey, int n);
128 static int     verify_identical_dbts(const DBT *dbt1, const DBT *dbt2);
129 static void    verify_sequential_rows(DB* compare_db, int64_t firstkey, int64_t numkeys);
130 
do_create(char * name,int * next_state)131 static DB* do_create(char* name, int* next_state) {
132     DB* db = NULL;
133     if ( verbose ) printf("%s :   do_create(%s)\n", __FILE__, name);
134     int r;
135     DB_TXN* txn;
136     r = env->txn_begin(env, NULL, &txn, 0);
137     CKERR(r);
138     r = db_create(&db, env, 0);
139     CKERR(r);
140     r = db->open(db, txn, name, NULL, DB_BTREE, DB_CREATE, 0666);
141     CKERR(r);
142     insert_n(db, txn, 0, ROWS_PER_TABLE);
143     if ( commit_or_abort() == COMMIT_TXN ) {
144         r = txn->commit(txn, 0);
145         CKERR(r);
146         *next_state = CREATED;
147     }
148     else {
149         r = db->close(db, 0);
150         db = NULL;
151         CKERR(r);
152         r = txn->abort(txn);
153         CKERR(r);
154         db = NULL;
155     }
156     return db;
157 }
158 
do_open(char * name,int * next_state)159 static DB* do_open(char* name, int* next_state) {
160     DB* db = NULL;
161     DB_TXN* txn;
162     if ( verbose ) printf("%s :   do_open(%s)\n", __FILE__, name);
163     int r;
164     r = env->txn_begin(env, NULL, &txn, 0);
165     CKERR(r);
166     r = db_create(&db, env, 0);
167     CKERR(r);
168     r = db->open(db, txn, name, NULL, DB_UNKNOWN, 0, 0666);
169     CKERR(r);
170     if ( commit_or_abort() == COMMIT_TXN ) {
171         r = txn->commit(txn, 0);
172         CKERR(r);
173         *next_state = OPEN;
174     }
175     else {
176         r = db->close(db, 0);
177         db = NULL;
178         CKERR(r);
179         r = txn->abort(txn);
180         CKERR(r);
181         db = NULL;
182     }
183     return db;
184 }
185 
do_close(DB * db,char * name,int * next_state)186 static void do_close(DB* db, char* name, int* next_state) {
187     if ( verbose ) printf("%s :   do_close(%s)\n", __FILE__, name);
188     if (!db) printf("db == NULL\n");
189 
190     int r = db->close(db, 0);
191     CKERR(r);
192     db = NULL;
193     *next_state = CLOSED;
194 }
195 
do_delete(char * name,int * next_state)196 static void do_delete(char* name, int* next_state) {
197     DB_TXN* txn;
198     if ( verbose ) printf("%s :   do_delete(%s)\n", __FILE__, name);
199     int r;
200     r = env->txn_begin(env, NULL, &txn, 0);
201     CKERR(r);
202     r = env->dbremove(env, txn, name, NULL, 0);
203     CKERR(r);
204 
205     if ( commit_or_abort() == COMMIT_TXN ) {
206         r = txn->commit(txn, 0);
207         CKERR(r);
208         *next_state = DELETED;
209     }
210     else {
211         r = txn->abort(txn);
212         CKERR(r);
213     }
214 }
215 
do_random_fileop(int i,int state)216 static int do_random_fileop(int i, int state) {
217     DB* db = db_array[i];
218     int rval = random() % 100;
219 //    if ( verbose ) printf("%s : %s : DB '%d', state '%d, rval '%d'\n", __FILE__, __FUNCTION__, i, state, rval);
220 
221     int next_state = state;
222 
223     char fname[100];
224     sprintf(fname, "%s%d.db", table, i);
225 
226     if ( rval < percent_do_op ) {
227         switch ( state ) {
228         case CREATED:
229             do_close(db, fname, &next_state);
230             db_array[i] = db = 0;
231             if ( rval < (percent_do_op / 2) ) {
232                 do_delete(fname, &next_state);
233             }
234             break;
235         case OPEN:
236             do_close(db, fname, &next_state);
237             db_array[i] = db = 0;
238             if ( rval < (percent_do_op / 2) ) {
239                 do_delete(fname, &next_state);
240             }
241             break;
242         case CLOSED:
243             if ( rval < (percent_do_op / 2) ) {
244                 db = do_open(fname, &next_state);
245                 db_array[i] = db;
246             }
247             else {
248                 do_delete(fname, &next_state);
249             }
250             break;
251         case DELETED:
252             db = do_create(fname, &next_state);
253             db_array[i] = db;
254             break;
255         }
256     }
257     return next_state;
258 }
259 
do_random_fileops(void)260 static void do_random_fileops(void)
261 {
262     int i, state, next_state;
263     DB_TXN *txn;
264     for (i=0;i<NUM_DICTIONARIES;i++) {
265         { int chk_r = env->txn_begin(env, NULL, &txn, 0); CKERR(chk_r); }
266         state = get_state(i);
267         next_state = do_random_fileop(i, state);
268         put_state(i, next_state);
269         { int chk_r = txn->commit(txn, 0); CKERR(chk_r); }
270         update_crash_timer();
271     }
272 }
273 
274 
run_test(int iter)275 static void run_test(int iter){
276     uint32_t recovery_flags = DB_INIT_LOG | DB_INIT_TXN;
277     int r, i;
278 
279     XMALLOC_N(NUM_DICTIONARIES, db_array);
280     srand(iter);
281 
282     if (iter == 0) {
283         // create working directory
284         toku_os_recursive_delete(TOKU_TEST_FILENAME);
285         r = toku_os_mkdir(TOKU_TEST_FILENAME, S_IRWXU+S_IRWXG+S_IRWXO);                                               CKERR(r);
286     }
287     else
288         recovery_flags += DB_RECOVER;
289 
290     // crash somewhat frequently during recovery
291     //   first, wait until after first crash
292     if ( iter > start_crashing_iter + 1 ) {
293         // every N cycles, crash in recovery
294         if ( (iter % iterations_per_crash_in_recovery) == 0 ) {
295             // crash at different places in recovery
296             if ( iter & 1 )
297                 db_env_set_recover_callback(crash_it_callback_f, NULL);
298             else
299                 db_env_set_recover_callback2(crash_it_callback_f, NULL);
300         }
301     }
302 
303     env_startup(recovery_flags);
304     if ( verbose ) printf("%s : environment init\n", __FILE__);
305 
306     if (iter == 0) {
307         // create a dictionary to store test state
308         r = db_create(&states, env, 0);                                                                   CKERR(r);
309         r = states->open(states, NULL, state_db_name, NULL, DB_BTREE, DB_CREATE, 0666);                   CKERR(r);
310         DB_TXN *states_txn;
311         r = env->txn_begin(env, NULL, &states_txn, 0);                                                    CKERR(r);
312         for (i=0;i<NUM_DICTIONARIES;i++) {
313             put_state(i, DELETED);
314         }
315         r = states_txn->commit(states_txn, 0);                                                            CKERR(r);
316         r = states->close(states, 0);                                                                     CKERR(r);
317         if ( verbose ) printf("%s : states.db initialized\n", __FILE__);
318     }
319 
320     // open the 'states' table
321     r = db_create(&states, env, 0);                                                                       CKERR(r);
322     r = states->open(states, NULL, state_db_name, NULL, DB_UNKNOWN, 0, 0666);                             CKERR(r);
323 
324     if ( verbose ) printf("%s : ===  ITERATION %6d ===\n", __FILE__, iter);
325 
326     // verify previous results
327     if ( verbose ) printf("%s : verify previous results\n", __FILE__);
328     int state = DELETED;
329     DB* db;
330     char fname[100];
331     if ( iter > 0 ) {
332         for (i=0;i<NUM_DICTIONARIES;i++) {
333             sprintf(fname, "%s%d.db", table, i);
334             state = get_state(i);
335             switch (state) {
336             case CREATED:
337             case OPEN:
338                 // open the table
339                 r = db_create(&db, env, 0);                                                               CKERR(r);
340                 r = db->open(db, NULL, fname, NULL, DB_UNKNOWN, 0, 0666);                                 CKERR(r);
341                 db_array[i] = db;
342                 verify_sequential_rows(db, 0, ROWS_PER_TABLE);
343                 // leave table open
344                 if (verbose) printf("%s :   verified open/created db[%d]\n", __FILE__, i);
345                 break;
346             case CLOSED:
347                 // open the table
348                 r = db_create(&db, env, 0);                                                               CKERR(r);
349                 r = db->open(db, NULL, fname, NULL, DB_UNKNOWN, 0, 0666);                                 CKERR(r);
350                 verify_sequential_rows(db, 0, ROWS_PER_TABLE);
351                 // close table
352                 r = db->close(db, 0);                                                                     CKERR(r);
353                 db_array[i] = db = NULL;
354                 if (verbose) printf("%s :   verified closed db[%d]\n", __FILE__, i);
355                 break;
356             case DELETED:
357                 r = db_create(&db, env, 0);                                                               CKERR(r);
358                 r = db->open(db, NULL, fname, NULL, DB_UNKNOWN, 0, 0666);
359                 if ( r == 0 ) assert(1);
360                 db_array[i] = db = NULL;
361                 if (verbose) printf("%s :   verified db[%d] removed\n", __FILE__, i);
362                 break;
363             default:
364                 printf("ERROR : Unknown state '%d'\n", state);
365                 return;
366             }
367         }
368     }
369     if ( verbose ) printf("%s : previous results verified\n", __FILE__);
370 
371     // for each of the dictionaries, perform a fileop some percentage of time (set in do_random_fileop).
372 
373     // before checkpoint #1
374     if ( verbose ) printf("%s : before checkpoint #1\n", __FILE__);
375     crash_timer = NUM_DICTIONARIES + 1;  // won't go off
376     do_random_fileops();
377 
378     // during checkpoint #1
379     if ( verbose ) printf("%s : during checkpoint #1\n", __FILE__);
380     crash_timer = NUM_DICTIONARIES + 1;  // won't go off
381 
382     if ( iter & 1 )
383         db_env_set_checkpoint_callback((void (*)(void*))do_random_fileops, NULL);
384     else
385         db_env_set_checkpoint_callback2((void (*)(void*))do_random_fileops, NULL);
386     // checkpoint
387     r = env->txn_checkpoint(env, 0, 0, 0);                                                                CKERR(r);
388     db_env_set_checkpoint_callback(NULL, NULL);
389     db_env_set_checkpoint_callback2(NULL, NULL);
390 
391     // randomly fail sometime during the next 3 phases
392     //  1) before the next checkpoint
393     //  2) during the next checkpoint
394     //  3) after the next (final) checkpoint
395 
396     if ( iter >= start_crashing_iter ) {
397         set_crash_timer();
398     }
399     else {
400         crash_timer = ( 3 * NUM_DICTIONARIES ) + 1;  // won't go off
401     }
402 
403     // before checkpoint #2
404     if ( verbose ) printf("%s : before checkpoint #2\n", __FILE__);
405     do_random_fileops();
406 
407     // during checkpoint
408     if ( verbose ) printf("%s : during checkpoint #2\n", __FILE__);
409 
410     if ( iter & 1 )
411         db_env_set_checkpoint_callback((void (*)(void*))do_random_fileops, NULL);
412     else
413         db_env_set_checkpoint_callback2((void (*)(void*))do_random_fileops, NULL);
414     // checkpoint
415     r = env->txn_checkpoint(env, 0, 0, 0);                                                                CKERR(r);
416     db_env_set_checkpoint_callback(NULL, NULL);
417     db_env_set_checkpoint_callback2(NULL, NULL);
418 
419     // after checkpoint
420     if ( verbose ) printf("%s : after checkpoint #2\n", __FILE__);
421     do_random_fileops();
422 
423     r = env->txn_checkpoint(env, 0, 0, 0);                                                                CKERR(r);
424 
425     for (i=0;i<NUM_DICTIONARIES;i++) {
426         db = db_array[i];
427         state = get_state(i);
428         if ( state == CREATED || state == OPEN ) {
429             r = db->close(db, 0);                                                                         CKERR(r);
430             db = NULL;
431         }
432     }
433 
434     r = states->close(states, 0);                                                                         CKERR(r);
435     r = env->close(env, 0);                                                                               CKERR(r);
436     if ( verbose ) printf("%s : done\n", __FILE__);
437 
438     toku_free(db_array);
439 }
440 
441 // ------------ infrastructure ----------
442 static void do_args(int argc, char * const argv[]);
443 
444 static int iter_arg = 0;
445 
test_main(int argc,char * const * argv)446 int test_main(int argc, char *const*argv) {
447     do_args(argc, argv);
448     run_test(iter_arg);
449     return 0;
450 }
451 
do_args(int argc,char * const argv[])452 static void do_args(int argc, char * const argv[]) {
453     int resultcode;
454     char *cmd = argv[0];
455     argc--; argv++;
456     while (argc>0) {
457 	if (strcmp(argv[0], "-v") == 0) {
458 	    verbose++;
459 	} else if (strcmp(argv[0],"-q")==0) {
460 	    verbose--;
461 	    if (verbose<0) verbose=0;
462 	} else if (strcmp(argv[0], "-h")==0) {
463 	    resultcode=0;
464 	do_usage:
465 	    fprintf(stderr, "Usage:\n%s [-v|-q]* [-h] [-i] \n", cmd);
466 	    exit(resultcode);
467 	} else if (strcmp(argv[0], "-i")==0) {
468             argc--; argv++;
469             iter_arg = atoi(argv[0]);
470 	} else {
471 	    fprintf(stderr, "Unknown arg: %s\n", argv[0]);
472 	    resultcode=1;
473 	    goto do_usage;
474 	}
475 	argc--;
476 	argv++;
477     }
478 }
479 
env_startup(int recovery_flags)480 static void env_startup(int recovery_flags) {
481     int r;
482     int envflags = DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_CREATE | DB_PRIVATE | recovery_flags;
483     r = db_env_create(&env, 0);                                     CKERR(r);
484     db_env_enable_engine_status(0);  // disable engine status on crash because test is expected to fail
485     r=env->set_redzone(env, 0);                                     CKERR(r);
486     env->set_errfile(env, stderr);
487     r = env->open(env, TOKU_TEST_FILENAME, envflags, S_IRWXU+S_IRWXG+S_IRWXO);  CKERR(r);
488     //Disable auto-checkpointing.
489     r = env->checkpointing_set_period(env, 0);                      CKERR(r);
490 }
491 
generate_val(int64_t key)492 static int64_t generate_val(int64_t key) {
493     return key + 314;
494 }
495 
insert_n(DB * db,DB_TXN * txn,int firstkey,int n)496 static void insert_n(DB *db, DB_TXN *txn, int firstkey, int n) {
497     int64_t k, v;
498     int r, i;
499     DBT key, val;
500 
501     if (!db) return;
502 
503     for (i = 0; i<n; i++) {
504 	k = firstkey + i;
505 	v = generate_val(k);
506 	dbt_init(&key, &k, sizeof(k));
507 	dbt_init(&val, &v, sizeof(v));
508         r = db->put(db, txn, &key, &val, 0);
509         CKERR(r);
510     }
511 }
512 
verify_identical_dbts(const DBT * dbt1,const DBT * dbt2)513 static int verify_identical_dbts(const DBT *dbt1, const DBT *dbt2) {
514     int r = 0;
515     if (dbt1->size != dbt2->size) r = 1;
516     else if (memcmp(dbt1->data, dbt2->data, dbt1->size)!=0) r = 1;
517     return r;
518 }
519 
verify_sequential_rows(DB * compare_db,int64_t firstkey,int64_t numkeys)520 static void verify_sequential_rows(DB* compare_db, int64_t firstkey, int64_t numkeys) {
521     //This does not lock the dbs/grab table locks.
522     //This means that you CANNOT CALL THIS while another thread is modifying the db.
523     //You CAN call it while a txn is open however.
524     int rval = 0;
525     DB_TXN *compare_txn;
526     int r, r1;
527 
528     assert(numkeys >= 1);
529     r = env->txn_begin(env, NULL, &compare_txn, DB_READ_UNCOMMITTED);
530         CKERR(r);
531     DBC *c1;
532 
533     r = compare_db->cursor(compare_db, compare_txn, &c1, 0);
534         CKERR(r);
535 
536 
537     DBT key1, val1;
538     DBT key2, val2;
539 
540     int64_t k, v;
541 
542     dbt_init_realloc(&key1);
543     dbt_init_realloc(&val1);
544 
545     dbt_init(&key2, &k, sizeof(k));
546     dbt_init(&val2, &v, sizeof(v));
547 
548 //    k = firstkey;
549 //    v = generate_val(k);
550 //    r1 = c1->c_get(c1, &key2, &val2, DB_SET);
551 //    CKERR(r1);
552 
553     int64_t i;
554     for (i = 0; i<numkeys; i++) {
555 	k = i + firstkey;
556 	v = generate_val(k);
557         r1 = c1->c_get(c1, &key1, &val1, DB_NEXT);
558 //        printf("k = %" PRIu64 ", v = %" PRIu64 ", key = %" PRIu64 ", val = %" PRIu64 "\n",
559 //               k, v, *((int64_t *)(key1.data)), *((int64_t *)(val1.data)));
560         assert(r1==0);
561 	rval = verify_identical_dbts(&key1, &key2) |
562 	    verify_identical_dbts(&val1, &val2);
563 	assert(rval == 0);
564     }
565     // now verify that there are no rows after the last expected
566     r1 = c1->c_get(c1, &key1, &val1, DB_NEXT);
567     assert(r1 == DB_NOTFOUND);
568 
569     c1->c_close(c1);
570     if (key1.data) toku_free(key1.data);
571     if (val1.data) toku_free(val1.data);
572     compare_txn->commit(compare_txn, 0);
573 }
574 
UU()575 static void UU() crash_it(void) {
576     fflush(stdout);
577     fflush(stderr);
578     int zero = 0;
579     int divide_by_zero = 1/zero;
580     printf("force use of %d\n", divide_by_zero);
581     fflush(stdout);
582     fflush(stderr);
583 }
584 
crash_it_callback_f(void * dummy UU ())585 static void crash_it_callback_f(void *dummy UU()) {
586     crash_it();
587 }
588