diff --git a/src/tests/Makefile b/src/tests/Makefile index 19bf08e152b..30d96da7ecb 100644 --- a/src/tests/Makefile +++ b/src/tests/Makefile @@ -147,6 +147,7 @@ BDB_DONTRUN_TESTS = \ recover-put-multiple-fdelete-all \ recover-put-multiple-fdelete-some \ recover-split-checkpoint \ + recover-loader-test \ progress \ redirect \ isolation-read-committed \ @@ -456,7 +457,7 @@ libs: -loader-cleanup-test.tdb$(BINSUF) diskfull.tdb$(BINSUF): CPPFLAGS+=-DDONT_DEPRECATE_WRITES +loader-cleanup-test.tdb$(BINSUF) recover-loader-test.tdb$(BINSUF) diskfull.tdb$(BINSUF): CPPFLAGS+=-DDONT_DEPRECATE_WRITES test_db_curs4.tdb$(BINSUF): trace.h test_db_curs4.bdb$(BINSUF): trace.h # a bunch of little tests designed to run in parallel diff --git a/src/tests/recover-loader-test.c b/src/tests/recover-loader-test.c new file mode 100644 index 00000000000..f8fe5ca8e97 --- /dev/null +++ b/src/tests/recover-loader-test.c @@ -0,0 +1,487 @@ +/* -*- mode: C; c-basic-offset: 4 -*- */ +#ident "Copyright (c) 2010 Tokutek Inc. All rights reserved." +#ident "$Id: loader-stress-test.c 19683 2010-04-30 18:19:17Z yfogel $" + + + +/* Purpose is to verify that when a loader crashes: + * - there are no temp files remaining + * - the loader-generated iname file is not present + * + * In the event of a crash, the verification of no temp files and + * no loader-generated iname file is done after recovery. + * + * Mechanism: + * This test is derived from loader-cleanup-test, which was derived from loader-stress-test. + * + * The outline of the test is as follows: + * - use loader to create table + * - verify presence of temp files + * - crash + * - recover + * - verify absence of temp files + * - verify absence of unwanted iname files (old inames if committed, new inames if aborted) + * + * + */ + + +#include "test.h" +#include "toku_pthread.h" +#include +#include + +#include +#include + +#include "ydb-internal.h" + +static const int envflags = DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_CREATE | DB_PRIVATE; + +BOOL do_test=FALSE, do_recover=FALSE; + +DB_ENV *env; +enum {MAX_NAME=128}; +enum {MAX_DBS=256}; +int NUM_DBS=5; +int NUM_ROWS=100000; +int CHECK_RESULTS=0; +int USE_PUTS=0; +int INDUCE_ENOSPC=0; +enum {MAGIC=311}; + + +DBT old_inames[MAX_DBS]; +DBT new_inames[MAX_DBS]; + + +int count_temp(char * dirname); +void get_inames(DBT* inames, DB** dbs); +int verify_file(char * dirname, char * filename); +void assert_inames_missing(DBT* inames); +int print_dir(char * dirname); + +// return number of temp files +int +count_temp(char * dirname) { + int n = 0; + + DIR * dir = opendir(dirname); + + struct dirent *ent; + while ((ent=readdir(dir))) { + if (ent->d_type==DT_REG && strncmp(ent->d_name, "temp", 4)==0) { + n++; + } + } + closedir(dir); + return n; +} + +// print contents of directory +int +print_dir(char * dirname) { + int n; + + DIR * dir = opendir(dirname); + + struct dirent *ent; + while ((ent=readdir(dir))) { + if (ent->d_type==DT_REG) { + n++; + printf("File: %s\n", ent->d_name); + } + } + closedir(dir); + return n; +} + + + +// return non-zero if file exists +int +verify_file(char * dirname, char * filename) { + int n = 0; + DIR * dir = opendir(dirname); + + struct dirent *ent; + while ((ent=readdir(dir))) { + if (ent->d_type==DT_REG && strcmp(ent->d_name, filename)==0) { + n++; + } + } + closedir(dir); + return n; +} + +void +get_inames(DBT* inames, DB** dbs) { + int i; + for (i = 0; i < NUM_DBS; i++) { + DBT dname; + char * dname_str = dbs[i]->i->dname; + dbt_init(&dname, dname_str, sizeof(dname_str)); + dbt_init(&(inames[i]), NULL, 0); + inames[i].flags |= DB_DBT_MALLOC; + int r = env->get_iname(env, &dname, &inames[i]); + CKERR(r); + char * iname_str = (char*) (inames[i].data); + if (verbose) printf("dname = %s, iname = %s\n", dname_str, iname_str); + } +} + + +void +assert_inames_missing(DBT* inames) { + int i; + char * dir = env->i->real_data_dir; + for (i=0; ii->dname; + dbt_init(&dname, dname_str, sizeof(dname_str)); + dbt_init(&iname, NULL, 0); + iname.flags |= DB_DBT_MALLOC; + int r = env->get_iname(env, &dname, &iname); + CKERR(r); + char * iname_str = (char*)iname.data; + if (verbose) printf("dname = %s, iname = %s\n", dname_str, iname_str); + int n = verify_file(env->i->real_data_dir, iname_str); + assert(n == 1); + toku_free(iname.data); + } +} +#endif + + +// +// Functions to create unique key/value pairs, row generators, checkers, ... for each of NUM_DBS +// + +// a is the bit-wise permute table. For DB[i], permute bits as described in a[i] using 'twiddle32' +// inv is the inverse bit-wise permute of a[]. To get the original value from a twiddled value, twiddle32 (again) with inv[] +int a[MAX_DBS][32]; +int inv[MAX_DBS][32]; + +#if defined(__cilkplusplus) || defined (__cplusplus) +extern "C" { +#endif + +// rotate right and left functions +static inline unsigned int rotr32(const unsigned int x, const unsigned int num) { + const unsigned int n = num % 32; + return (x >> n) | ( x << (32 - n)); +} +static inline unsigned int rotl32(const unsigned int x, const unsigned int num) { + const unsigned int n = num % 32; + return (x << n) | ( x >> (32 - n)); +} + +static void generate_permute_tables(void) { + int i, j, tmp; + for(int db=0;db> i ) & 1) << a[db][i]; + } + return b; +} + +// generate val from key, index +static unsigned int generate_val(int key, int i) { + return rotl32((key + MAGIC), i); +} + +// There is no handlerton in this test, so this function is a local replacement +// for the handlerton's generate_row_for_put(). +static int put_multiple_generate(DB *dest_db, DB *src_db, DBT *dest_key, DBT *dest_val, const DBT *src_key, const DBT *src_val, void *extra) { + + src_db = src_db; + extra = extra; + + uint32_t which = *(uint32_t*)dest_db->app_private; + + if ( which == 0 ) { + if (dest_key->flags==DB_DBT_REALLOC) { + if (dest_key->data) toku_free(dest_key->data); + dest_key->flags = 0; + dest_key->ulen = 0; + } + if (dest_val->flags==DB_DBT_REALLOC) { + if (dest_val->data) toku_free(dest_val->data); + dest_val->flags = 0; + dest_val->ulen = 0; + } + dbt_init(dest_key, src_key->data, src_key->size); + dbt_init(dest_val, src_val->data, src_val->size); + } + else { + assert(dest_key->flags==DB_DBT_REALLOC); + if (dest_key->ulen < sizeof(unsigned int)) { + dest_key->data = toku_xrealloc(dest_key->data, sizeof(unsigned int)); + dest_key->ulen = sizeof(unsigned int); + } + assert(dest_val->flags==DB_DBT_REALLOC); + if (dest_val->ulen < sizeof(unsigned int)) { + dest_val->data = toku_xrealloc(dest_val->data, sizeof(unsigned int)); + dest_val->ulen = sizeof(unsigned int); + } + unsigned int *new_key = (unsigned int *)dest_key->data; + unsigned int *new_val = (unsigned int *)dest_val->data; + + *new_key = twiddle32(*(unsigned int*)src_key->data, which); + *new_val = generate_val(*(unsigned int*)src_key->data, which); + + dest_key->size = sizeof(unsigned int); + dest_val->size = sizeof(unsigned int); + //data is already set above + } + +// printf("dest_key.data = %d\n", *(int*)dest_key->data); +// printf("dest_val.data = %d\n", *(int*)dest_val->data); + + return 0; +} + +#if defined(__cilkplusplus) || defined(__cplusplus) +} // extern "C" +#endif + +static void *expect_poll_void = &expect_poll_void; + +static int poll_function (void *UU(extra), float UU(progress)) { + toku_hard_crash_on_purpose(); + return -1; +} + +static void test_loader(DB **dbs) +{ + int r; + DB_TXN *txn; + DB_LOADER *loader; + uint32_t db_flags[MAX_DBS]; + uint32_t dbt_flags[MAX_DBS]; + for(int i=0;itxn_begin(env, NULL, &txn, 0); + CKERR(r); + r = env->create_loader(env, txn, &loader, dbs[0], NUM_DBS, dbs, db_flags, dbt_flags, loader_flags); + CKERR(r); + r = loader->set_error_callback(loader, NULL, NULL); + CKERR(r); + r = loader->set_poll_function(loader, poll_function, expect_poll_void); + CKERR(r); + + printf("USE_PUTS = %d\n", USE_PUTS); + if (verbose) printf("new inames:\n"); + get_inames(new_inames, dbs); + + // using loader->put, put values into DB + DBT key, val; + unsigned int k, v; + for(int i=1;i<=NUM_ROWS;i++) { + k = i; + v = generate_val(i, 0); + dbt_init(&key, &k, sizeof(unsigned int)); + dbt_init(&val, &v, sizeof(unsigned int)); + r = loader->put(loader, &key, &val); + CKERR(r); + if ( CHECK_RESULTS || verbose) { if((i%10000) == 0){printf("."); fflush(stdout);} } + } + if( CHECK_RESULTS || verbose ) {printf("\n"); fflush(stdout);} + + printf("Data dir is %s\n", env->i->real_data_dir); + int n = count_temp(env->i->real_data_dir); + printf("Num temp files = %d\n", n); + assert(n); // test is useless unless at least one temp file is created + + printf("Contents of data dir:\n"); + print_dir(env->i->real_data_dir); + printf("closing, will crash\n"); fflush(stdout); + r = loader->close(loader); + printf("Should never return from loader->close()\n"); fflush(stdout); + assert(0); + +} + + +static void run_test(void) +{ + int r; + + r = system("rm -rf " ENVDIR); CKERR(r); + r = toku_os_mkdir(ENVDIR, S_IRWXU+S_IRWXG+S_IRWXO); CKERR(r); + + r = db_env_create(&env, 0); CKERR(r); + r = env->set_default_bt_compare(env, uint_dbt_cmp); CKERR(r); + r = env->set_default_dup_compare(env, uint_dbt_cmp); CKERR(r); + r = env->set_generate_row_callback_for_put(env, put_multiple_generate); + CKERR(r); +// int envflags = DB_INIT_LOCK | DB_INIT_MPOOL | DB_INIT_TXN | DB_CREATE | DB_PRIVATE; + r = env->open(env, ENVDIR, envflags, S_IRWXU+S_IRWXG+S_IRWXO); CKERR(r); + env->set_errfile(env, stderr); + //Disable auto-checkpointing + r = env->checkpointing_set_period(env, 0); CKERR(r); + + DBT desc; + dbt_init(&desc, "foo", sizeof("foo")); + char name[MAX_NAME*2]; + + DB **dbs = (DB**)toku_malloc(sizeof(DB*) * NUM_DBS); + assert(dbs != NULL); + int idx[MAX_DBS]; + for(int i=0;iset_descriptor(dbs[i], 1, &desc, abort_on_upgrade); CKERR(r); + dbs[i]->app_private = &idx[i]; + snprintf(name, sizeof(name), "db_%04x", i); + r = dbs[i]->open(dbs[i], NULL, name, NULL, DB_BTREE, DB_CREATE, 0666); CKERR(r); + } + + generate_permute_tables(); + + test_loader(dbs); + printf("Should never return from test_loader\n"); fflush(stdout); + assert(0); +} + +// ------------ infrastructure ---------- +static void do_args(int argc, char * const argv[]); + + + +static void run_recover (void) { + + // Recovery starts from oldest_living_txn, which is older than any inserts done in run_test, + // so recovery always runs over the entire log. + + // run recovery + int r = db_env_create(&env, 0); CKERR(r); + r = env->open(env, ENVDIR, envflags + DB_RECOVER, S_IRWXU+S_IRWXG+S_IRWXO); CKERR(r); + + // now verify contents of data_dir, should be no temp files, no loader-created iname files + print_dir(env->i->real_data_dir); + + int n = count_temp(env->i->real_data_dir); + printf("Num temp files = %d\n", n); + assert(n==0); // There should be no temp files remaining after recovery + + r = env->close(env, 0); CKERR(r); + exit(0); + +} + +int test_main(int argc, char * const *argv) { + do_args(argc, argv); + + if (do_test) { + printf("\n\n perform test, crash\n"); + fflush(stdout); + run_test(); + } + else if (do_recover) { + printf("\n\n perform recovery\n"); + run_recover(); + } + else { + printf("\n\n BOGUS!\n"); + assert(0); + } + + return 0; +} + +static void do_args(int argc, char * const argv[]) { + int resultcode; + char *cmd = argv[0]; + argc--; argv++; + while (argc>0) { + if (strcmp(argv[0], "-v")==0) { + verbose++; + } else if (strcmp(argv[0],"-q")==0) { + verbose--; + if (verbose<0) verbose=0; + } else if (strcmp(argv[0], "-h")==0) { + resultcode=0; + do_usage: + fprintf(stderr, "Usage: -h -c -d -r \n%s\n", cmd); + exit(resultcode); + } else if (strcmp(argv[0], "-d")==0) { + argc--; argv++; + NUM_DBS = atoi(argv[0]); + if ( NUM_DBS > MAX_DBS ) { + fprintf(stderr, "max value for -d field is %d\n", MAX_DBS); + resultcode=1; + goto do_usage; + } + } else if (strcmp(argv[0], "-r")==0) { + argc--; argv++; + NUM_ROWS = atoi(argv[0]); + } else if (strcmp(argv[0], "-c")==0) { + CHECK_RESULTS = 1; + } else if (strcmp(argv[0], "-p")==0) { + USE_PUTS = LOADER_USE_PUTS; + printf("Using puts\n"); + } else if (strcmp(argv[0], "-e")==0) { + INDUCE_ENOSPC = 1; + printf("Using enospc\n"); + + } else if (strcmp(argv[0], "--test")==0) { + do_test=TRUE; + } else if (strcmp(argv[0], "--recover") == 0) { + do_recover=TRUE; + + } else { + fprintf(stderr, "Unknown arg: %s\n", argv[0]); + resultcode=1; + goto do_usage; + } + argc--; + argv++; + } +}