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