mirror of
https://github.com/MariaDB/server.git
synced 2025-01-22 06:44:16 +01:00
logcursor changes, fix LSNs, make progress on tests [t:1926]
git-svn-id: file:///svn/toku/tokudb@13970 c7de825b-a66e-492c-adef-691d508d4ae1
This commit is contained in:
parent
9197483c86
commit
3523924d66
9 changed files with 381 additions and 207 deletions
|
@ -141,6 +141,7 @@ static inline int toku_logsizeof_BYTESTRING (BYTESTRING bs) {
|
|||
return 4+bs.len;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static inline int toku_logsizeof_LOGGEDBRTHEADER (LOGGEDBRTHEADER bs) {
|
||||
int in_both = 4+4+4+8+8+4+8;
|
||||
in_both += 8; // for the number of block headers
|
||||
|
@ -152,6 +153,7 @@ static inline int toku_logsizeof_LOGGEDBRTHEADER (LOGGEDBRTHEADER bs) {
|
|||
static inline int toku_logsizeof_INTPAIRARRAY (INTPAIRARRAY pa) {
|
||||
return 4+(4+4)*pa.size;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline char *fixup_fname(BYTESTRING *f) {
|
||||
assert(f->len>0);
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
|
||||
#include "includes.h"
|
||||
|
||||
enum lc_direction { LC_FORWARD, LC_BACKWARD, LC_FIRST, LC_LAST };
|
||||
|
||||
struct toku_logcursor {
|
||||
char *logdir;
|
||||
char *logdir; // absolute directory name
|
||||
char **logfiles;
|
||||
int n_logfiles;
|
||||
int cur_logfiles_index;
|
||||
|
@ -14,8 +16,27 @@ struct toku_logcursor {
|
|||
BOOL is_open;
|
||||
struct log_entry entry;
|
||||
BOOL entry_valid;
|
||||
LSN cur_lsn;
|
||||
enum lc_direction last_direction;
|
||||
};
|
||||
|
||||
#define LC_LSN_ERROR (-1)
|
||||
|
||||
static void lc_print_logcursor (TOKULOGCURSOR lc) __attribute__((unused));
|
||||
static void lc_print_logcursor (TOKULOGCURSOR lc) {
|
||||
printf("lc = %p\n", lc);
|
||||
printf(" logdir = %s\n", lc->logdir);
|
||||
printf(" logfiles = %p\n", lc->logfiles);
|
||||
for (int lf=0;lf<lc->n_logfiles;lf++) {
|
||||
printf(" logfile[%d] = %p (%s)\n", lf, lc->logfiles[lf], lc->logfiles[lf]);
|
||||
}
|
||||
printf(" n_logfiles = %d\n", lc->n_logfiles);
|
||||
printf(" cur_logfiles_index = %d\n", lc->cur_logfiles_index);
|
||||
printf(" cur_fp = %p\n", lc->cur_fp);
|
||||
printf(" cur_lsn = %lu\n", lc->cur_lsn.lsn);
|
||||
printf(" last_direction = %d\n", lc->last_direction);
|
||||
}
|
||||
|
||||
static int lc_close_cur_logfile(TOKULOGCURSOR lc) {
|
||||
int r=0;
|
||||
if ( lc->is_open ) {
|
||||
|
@ -28,6 +49,7 @@ static int lc_close_cur_logfile(TOKULOGCURSOR lc) {
|
|||
static int lc_open_logfile(TOKULOGCURSOR lc, int index) {
|
||||
int r=0;
|
||||
assert( !lc->is_open );
|
||||
if( index == -1 || index >= lc->n_logfiles) return DB_NOTFOUND;
|
||||
lc->cur_fp = fopen(lc->logfiles[index], "r");
|
||||
if ( lc->cur_fp == NULL )
|
||||
return DB_NOTFOUND;
|
||||
|
@ -36,13 +58,25 @@ static int lc_open_logfile(TOKULOGCURSOR lc, int index) {
|
|||
r = toku_read_logmagic(lc->cur_fp, &version);
|
||||
if (r!=0)
|
||||
return DB_BADFORMAT;
|
||||
if (version != 1)
|
||||
if (version != TOKU_LOG_VERSION)
|
||||
return DB_BADFORMAT;
|
||||
// mark as open
|
||||
lc->is_open = TRUE;
|
||||
return r;
|
||||
}
|
||||
|
||||
static int lc_check_lsn(TOKULOGCURSOR lc, int dir) {
|
||||
int r=0;
|
||||
LSN lsn = toku_log_entry_get_lsn(&(lc->entry));
|
||||
if (((dir == LC_FORWARD) && ( lsn.lsn != lc->cur_lsn.lsn + 1 )) ||
|
||||
((dir == LC_BACKWARD) && ( lsn.lsn != lc->cur_lsn.lsn - 1 ))) {
|
||||
fprintf(stderr, "Bad LSN : direction = %d, lsn.lsn = %"PRIu64", cur_lsn.lsn=%"PRIu64"\n", dir, lsn.lsn, lc->cur_lsn.lsn);
|
||||
return LC_LSN_ERROR;
|
||||
}
|
||||
lc->cur_lsn.lsn = lsn.lsn;
|
||||
return r;
|
||||
}
|
||||
|
||||
// toku_logcursor_create()
|
||||
// - returns a pointer to a logcursor
|
||||
int toku_logcursor_create(TOKULOGCURSOR *lc, const char *log_dir) {
|
||||
|
@ -51,16 +85,38 @@ int toku_logcursor_create(TOKULOGCURSOR *lc, const char *log_dir) {
|
|||
|
||||
// malloc a cursor
|
||||
TOKULOGCURSOR cursor = (TOKULOGCURSOR) toku_malloc(sizeof(struct toku_logcursor));
|
||||
if ( NULL==cursor )
|
||||
return ENOMEM;
|
||||
if ( cursor == NULL ) {
|
||||
failresult = ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
// find logfiles in logdir
|
||||
cursor->is_open = FALSE;
|
||||
cursor->cur_logfiles_index = 0;
|
||||
cursor->entry_valid = FALSE;
|
||||
cursor->logdir = (char *) toku_malloc(strlen(log_dir)+1);
|
||||
if ( NULL==cursor->logdir )
|
||||
return ENOMEM;
|
||||
strcpy(cursor->logdir, log_dir);
|
||||
// cursor->logdir must be an absolute path
|
||||
if ( log_dir[0]=='/' ) {
|
||||
cursor->logdir = (char *) toku_malloc(strlen(log_dir)+1);
|
||||
if ( cursor->logdir == NULL ) {
|
||||
failresult = ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
sprintf(cursor->logdir, "%s", log_dir);
|
||||
}
|
||||
else {
|
||||
char *cwd = getcwd(NULL, 0);
|
||||
if ( cwd == NULL ) {
|
||||
failresult = -1;
|
||||
goto fail;
|
||||
}
|
||||
cursor->logdir = (char *) toku_malloc(strlen(cwd)+strlen(log_dir)+2);
|
||||
if ( cursor->logdir == NULL ) {
|
||||
toku_free(cwd);
|
||||
failresult = ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
sprintf(cursor->logdir, "%s/%s", cwd, log_dir);
|
||||
toku_free(cwd);
|
||||
}
|
||||
cursor->logfiles = NULL;
|
||||
cursor->n_logfiles = 0;
|
||||
r = toku_logger_find_logfiles(cursor->logdir, &(cursor->logfiles), &(cursor->n_logfiles));
|
||||
|
@ -68,6 +124,8 @@ int toku_logcursor_create(TOKULOGCURSOR *lc, const char *log_dir) {
|
|||
failresult=r;
|
||||
goto fail;
|
||||
}
|
||||
cursor->cur_lsn.lsn=0;
|
||||
cursor->last_direction=LC_FIRST;
|
||||
*lc = cursor;
|
||||
return r;
|
||||
fail:
|
||||
|
@ -76,6 +134,50 @@ int toku_logcursor_create(TOKULOGCURSOR *lc, const char *log_dir) {
|
|||
return failresult;
|
||||
}
|
||||
|
||||
int toku_logcursor_create_for_file(TOKULOGCURSOR *lc, const char *log_dir, const char *log_file) {
|
||||
int r=0;
|
||||
int failresult=0;
|
||||
|
||||
TOKULOGCURSOR cursor;
|
||||
r = toku_logcursor_create(&cursor, log_dir);
|
||||
if (r!=0)
|
||||
return r;
|
||||
|
||||
int idx;
|
||||
int found_it=0;
|
||||
int fullnamelen = strlen(cursor->logdir) + strlen(log_file) + 3;
|
||||
char *log_file_fullname = toku_malloc(fullnamelen);
|
||||
if ( log_file_fullname == NULL ) {
|
||||
failresult = ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
sprintf(log_file_fullname, "%s/%s", cursor->logdir, log_file);
|
||||
for(idx=0;idx<cursor->n_logfiles;idx++) {
|
||||
if ( strcmp(log_file_fullname, cursor->logfiles[idx]) == 0 ) {
|
||||
found_it = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found_it==0) {
|
||||
failresult = DB_NOTFOUND;
|
||||
goto fail;
|
||||
}
|
||||
// replace old logfile structure with single file version
|
||||
int lf;
|
||||
for(lf=0;lf<cursor->n_logfiles;lf++) {
|
||||
toku_free(cursor->logfiles[lf]);
|
||||
}
|
||||
cursor->n_logfiles=1;
|
||||
cursor->logfiles[0] = log_file_fullname;
|
||||
*lc = cursor;
|
||||
return r;
|
||||
fail:
|
||||
toku_free(log_file_fullname);
|
||||
toku_logcursor_destroy(&cursor);
|
||||
*lc = NULL;
|
||||
return failresult;
|
||||
}
|
||||
|
||||
int toku_logcursor_destroy(TOKULOGCURSOR *lc) {
|
||||
int r=0;
|
||||
if ( (*lc)->entry_valid ) {
|
||||
|
@ -99,7 +201,13 @@ int toku_logcursor_next(TOKULOGCURSOR lc, struct log_entry **le) {
|
|||
if ( lc->entry_valid ) {
|
||||
toku_log_free_log_entry_resources(&(lc->entry));
|
||||
lc->entry_valid = FALSE;
|
||||
} else if ( !lc->entry_valid ) {
|
||||
if (lc->last_direction == LC_BACKWARD) {
|
||||
struct log_entry junk;
|
||||
r = toku_log_fread(lc->cur_fp, &junk);
|
||||
assert(r == 0);
|
||||
toku_log_free_log_entry_resources(&junk);
|
||||
}
|
||||
} else {
|
||||
r = toku_logcursor_first(lc, le);
|
||||
return r;
|
||||
}
|
||||
|
@ -126,6 +234,10 @@ int toku_logcursor_next(TOKULOGCURSOR lc, struct log_entry **le) {
|
|||
return r;
|
||||
}
|
||||
}
|
||||
r = lc_check_lsn(lc, LC_FORWARD);
|
||||
if (r!=0)
|
||||
return r;
|
||||
lc->last_direction = LC_FORWARD;
|
||||
lc->entry_valid = TRUE;
|
||||
*le = &(lc->entry);
|
||||
return r;
|
||||
|
@ -136,7 +248,13 @@ int toku_logcursor_prev(TOKULOGCURSOR lc, struct log_entry **le) {
|
|||
if ( lc->entry_valid ) {
|
||||
toku_log_free_log_entry_resources(&(lc->entry));
|
||||
lc->entry_valid = FALSE;
|
||||
} else if ( !lc->entry_valid ) {
|
||||
if (lc->last_direction == LC_FORWARD) {
|
||||
struct log_entry junk;
|
||||
r = toku_log_fread_backward(lc->cur_fp, &junk);
|
||||
assert(r == 0);
|
||||
toku_log_free_log_entry_resources(&junk);
|
||||
}
|
||||
} else {
|
||||
r = toku_logcursor_last(lc, le);
|
||||
return r;
|
||||
}
|
||||
|
@ -163,6 +281,10 @@ int toku_logcursor_prev(TOKULOGCURSOR lc, struct log_entry **le) {
|
|||
return r;
|
||||
}
|
||||
}
|
||||
r = lc_check_lsn(lc, LC_BACKWARD);
|
||||
if (r!=0)
|
||||
return r;
|
||||
lc->last_direction = LC_BACKWARD;
|
||||
lc->entry_valid = TRUE;
|
||||
*le = &(lc->entry);
|
||||
return r;
|
||||
|
@ -188,6 +310,10 @@ int toku_logcursor_first(TOKULOGCURSOR lc, struct log_entry **le) {
|
|||
r = toku_log_fread(lc->cur_fp, &(lc->entry));
|
||||
if (r!=0)
|
||||
return r;
|
||||
r = lc_check_lsn(lc, LC_FIRST);
|
||||
if (r!=0)
|
||||
return r;
|
||||
lc->last_direction = LC_FIRST;
|
||||
lc->entry_valid = TRUE;
|
||||
*le = &(lc->entry);
|
||||
return r;
|
||||
|
@ -217,6 +343,10 @@ int toku_logcursor_last(TOKULOGCURSOR lc, struct log_entry **le) {
|
|||
r = toku_log_fread_backward(lc->cur_fp, &(lc->entry));
|
||||
if (r!=0)
|
||||
return r;
|
||||
r = lc_check_lsn(lc, LC_LAST);
|
||||
if (r!=0)
|
||||
return r;
|
||||
lc->last_direction = LC_LAST;
|
||||
lc->entry_valid = TRUE;
|
||||
*le = &(lc->entry);
|
||||
return r;
|
||||
|
|
|
@ -13,11 +13,14 @@ typedef struct toku_logcursor *TOKULOGCURSOR;
|
|||
// All routines return 0 on success
|
||||
|
||||
// toku_logcursor_create()
|
||||
// - returns a pointer to a logcursor
|
||||
// - creates a logcursor (lc)
|
||||
// - following toku_logcursor_create()
|
||||
// if toku_logcursor_next() is called, it returns the first entry in the log
|
||||
// if toku_logcursor_prev() is called, it returns the last entry in the log
|
||||
int toku_logcursor_create(TOKULOGCURSOR *lc, const char *log_dir);
|
||||
// toku_logcursor_create_for_file()
|
||||
// - creates a logcusor (lc) that only knows about the file log_file
|
||||
int toku_logcursor_create_for_file(TOKULOGCURSOR *lc, const char *log_dir, const char *log_file);
|
||||
// toku_logcursor_destroy()
|
||||
// - frees all resources associated with the logcursor, including the log_entry
|
||||
// associated with the latest cursor action
|
||||
|
|
|
@ -48,7 +48,25 @@ int toku_logger_create (TOKULOGGER *resultp) {
|
|||
int toku_logger_open (const char *directory, TOKULOGGER logger) {
|
||||
if (logger->is_open) return EINVAL;
|
||||
if (logger->is_panicked) return EINVAL;
|
||||
|
||||
int r;
|
||||
TOKULOGCURSOR logcursor;
|
||||
struct log_entry *le;
|
||||
logger->lsn.lsn=0;
|
||||
// In searching existing logfiles for last LSN, have chosen to
|
||||
// ignore errors that may occur. In the event of a return error,
|
||||
// revert to using LSN=0
|
||||
r = toku_logcursor_create(&logcursor, directory);
|
||||
if (r==0) {
|
||||
r = toku_logcursor_last(logcursor, &le);
|
||||
if (r==0)
|
||||
logger->lsn = toku_log_entry_get_lsn(le);
|
||||
toku_logcursor_destroy(&logcursor);
|
||||
}
|
||||
//printf("starting after LSN=%lu\n", logger->lsn.lsn);
|
||||
logger->written_lsn = logger->lsn;
|
||||
logger->fsynced_lsn = logger->lsn;
|
||||
|
||||
long long nexti;
|
||||
r = toku_logger_find_next_unused_log_file(directory, &nexti);
|
||||
if (r!=0) return r;
|
||||
|
@ -57,10 +75,6 @@ int toku_logger_open (const char *directory, TOKULOGGER logger) {
|
|||
logger->next_log_file_number = nexti;
|
||||
open_logfile(logger);
|
||||
|
||||
logger->lsn.lsn = 0; // WRONG!!! This should actually be calculated by looking at the log file.
|
||||
logger->written_lsn.lsn = 0;
|
||||
logger->fsynced_lsn.lsn = 0;
|
||||
|
||||
logger->is_open = 1;
|
||||
if (!logger->write_log_files)
|
||||
toku_set_func_fsync(toku_logger_fsync_null);
|
||||
|
|
202
newbrt/recover.c
202
newbrt/recover.c
|
@ -58,6 +58,7 @@ struct backward_scan_state {
|
|||
int n_live_txns;
|
||||
LSN min_live_txn;
|
||||
};
|
||||
|
||||
static struct backward_scan_state initial_bss = {BS_INIT,{0},0,{0}};
|
||||
|
||||
static void
|
||||
|
@ -176,12 +177,10 @@ internal_toku_recover_fopen_or_fcreate (int flags, int mode, char *fixedfname, F
|
|||
static void
|
||||
toku_recover_fopen (LSN UU(lsn), TXNID UU(txnid), BYTESTRING fname, FILENUM filenum) {
|
||||
char *fixedfname = fixup_fname(&fname);
|
||||
toku_free_BYTESTRING(fname);
|
||||
internal_toku_recover_fopen_or_fcreate(0, 0, fixedfname, filenum);
|
||||
}
|
||||
|
||||
static int toku_recover_backward_fopen (struct logtype_fopen *l, struct backward_scan_state *UU(bs)) {
|
||||
toku_free_BYTESTRING(l->fname);
|
||||
static int toku_recover_backward_fopen (struct logtype_fopen *UU(l), struct backward_scan_state *UU(bs)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -190,44 +189,14 @@ static int toku_recover_backward_fopen (struct logtype_fopen *l, struct backward
|
|||
static void
|
||||
toku_recover_fcreate (LSN UU(lsn), TXNID UU(txnid), FILENUM filenum, BYTESTRING fname,u_int32_t mode) {
|
||||
char *fixedfname = fixup_fname(&fname);
|
||||
toku_free_BYTESTRING(fname);
|
||||
create_dir_from_file(fixedfname);
|
||||
internal_toku_recover_fopen_or_fcreate(O_CREAT|O_TRUNC, mode, fixedfname, filenum);
|
||||
}
|
||||
|
||||
static int toku_recover_backward_fcreate (struct logtype_fcreate *l, struct backward_scan_state *UU(bs)) {
|
||||
toku_free_BYTESTRING(l->fname);
|
||||
static int toku_recover_backward_fcreate (struct logtype_fcreate *UU(l), struct backward_scan_state *UU(bs)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void
|
||||
toku_recover_enqrootentry (LSN lsn __attribute__((__unused__)), FILENUM filenum, TXNID xid, u_int32_t typ, BYTESTRING key, BYTESTRING val) {
|
||||
struct cf_pair *pair = NULL;
|
||||
int r = find_cachefile(filenum, &pair);
|
||||
if (r!=0) {
|
||||
// if we didn't find a cachefile, then we don't have to do anything.
|
||||
return;
|
||||
}
|
||||
struct brt_cmd cmd;
|
||||
DBT keydbt, valdbt;
|
||||
cmd.type=(enum brt_cmd_type) typ;
|
||||
cmd.xid =xid;
|
||||
cmd.u.id.key = toku_fill_dbt(&keydbt, key.data, key.len);
|
||||
cmd.u.id.val = toku_fill_dbt(&valdbt, val.data, val.len);
|
||||
r = toku_brt_root_put_cmd(pair->brt, &cmd, null_tokulogger);
|
||||
assert(r==0);
|
||||
toku_free(key.data);
|
||||
toku_free(val.data);
|
||||
}
|
||||
|
||||
static int toku_recover_backward_enqrootentry (struct logtype_enqrootentry *l, struct backward_scan_state *UU(bs)) {
|
||||
toku_free_BYTESTRING(l->key);
|
||||
toku_free_BYTESTRING(l->data);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
toku_recover_enq_insert (LSN lsn __attribute__((__unused__)), FILENUM filenum, TXNID xid, BYTESTRING key, BYTESTRING val) {
|
||||
struct cf_pair *pair = NULL;
|
||||
|
@ -250,13 +219,9 @@ toku_recover_enq_insert (LSN lsn __attribute__((__unused__)), FILENUM filenum, T
|
|||
assert(r==0);
|
||||
xids_destroy(&cmd.xids);
|
||||
xids_destroy(&root);
|
||||
toku_free(key.data);
|
||||
toku_free(val.data);
|
||||
}
|
||||
|
||||
static int toku_recover_backward_enq_insert (struct logtype_enq_insert *l, struct backward_scan_state *UU(bs)) {
|
||||
toku_free_BYTESTRING(l->key);
|
||||
toku_free_BYTESTRING(l->value);
|
||||
static int toku_recover_backward_enq_insert (struct logtype_enq_insert *UU(l), struct backward_scan_state *UU(bs)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -282,13 +247,9 @@ toku_recover_enq_delete_both (LSN lsn __attribute__((__unused__)), FILENUM filen
|
|||
assert(r==0);
|
||||
xids_destroy(&cmd.xids);
|
||||
xids_destroy(&root);
|
||||
toku_free(key.data);
|
||||
toku_free(val.data);
|
||||
}
|
||||
|
||||
static int toku_recover_backward_enq_delete_both (struct logtype_enq_delete_both *l, struct backward_scan_state *UU(bs)) {
|
||||
toku_free_BYTESTRING(l->key);
|
||||
toku_free_BYTESTRING(l->value);
|
||||
static int toku_recover_backward_enq_delete_both (struct logtype_enq_delete_both *UU(l), struct backward_scan_state *UU(bs)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -314,13 +275,9 @@ toku_recover_enq_delete_any (LSN lsn __attribute__((__unused__)), FILENUM filenu
|
|||
assert(r==0);
|
||||
xids_destroy(&cmd.xids);
|
||||
xids_destroy(&root);
|
||||
toku_free(key.data);
|
||||
toku_free(val.data);
|
||||
}
|
||||
|
||||
static int toku_recover_backward_enq_delete_any (struct logtype_enq_delete_any *l, struct backward_scan_state *UU(bs)) {
|
||||
toku_free_BYTESTRING(l->key);
|
||||
toku_free_BYTESTRING(l->value);
|
||||
static int toku_recover_backward_enq_delete_any (struct logtype_enq_delete_any *UU(l), struct backward_scan_state *UU(bs)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -332,11 +289,9 @@ toku_recover_fclose (LSN UU(lsn), BYTESTRING UU(fname), FILENUM filenum) {
|
|||
r = toku_close_brt(pair->brt, 0, 0);
|
||||
assert(r==0);
|
||||
pair->brt=0;
|
||||
toku_free_BYTESTRING(fname);
|
||||
}
|
||||
|
||||
static int toku_recover_backward_fclose (struct logtype_fclose *l, struct backward_scan_state *UU(bs)) {
|
||||
toku_free_BYTESTRING(l->fname);
|
||||
static int toku_recover_backward_fclose (struct logtype_fclose *UU(l), struct backward_scan_state *UU(bs)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -363,7 +318,6 @@ static int toku_recover_backward_begin_checkpoint (struct logtype_begin_checkpoi
|
|||
abort();
|
||||
}
|
||||
|
||||
|
||||
static int toku_recover_end_checkpoint (LSN UU(lsn), TXNID UU(txnid)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -391,8 +345,7 @@ static int toku_recover_fassociate (LSN UU(lsn), FILENUM UU(filenum), BYTESTRING
|
|||
|
||||
static int toku_recover_backward_fassociate (struct logtype_fassociate *l, struct backward_scan_state *UU(bs)) {
|
||||
char *fixedfname = fixup_fname(&l->fname);
|
||||
toku_free_BYTESTRING(l->fname);
|
||||
internal_toku_recover_fopen_or_fcreate(0, 0, fixedfname, l->filenum);
|
||||
internal_toku_recover_fopen_or_fcreate(0, 0, fixedfname, l->filenum);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -445,13 +398,11 @@ static int toku_recover_backward_xbegin (struct logtype_xbegin *l, struct backwa
|
|||
abort();
|
||||
}
|
||||
|
||||
static int toku_recover_timestamp (LSN UU(lsn), u_int64_t UU(timestamp), BYTESTRING comment) {
|
||||
toku_free_BYTESTRING(comment);
|
||||
static int toku_recover_timestamp (LSN UU(lsn), u_int64_t UU(timestamp), BYTESTRING UU(comment)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int toku_recover_backward_timestamp (struct logtype_timestamp *l, struct backward_scan_state *UU(bs)) {
|
||||
toku_free_BYTESTRING(l->comment);
|
||||
static int toku_recover_backward_timestamp (struct logtype_timestamp *UU(l), struct backward_scan_state *UU(bs)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -495,9 +446,6 @@ static int toku_delete_rolltmp_files (const char *log_dir) {
|
|||
int tokudb_recover(const char *data_dir, const char *log_dir) {
|
||||
int failresult = 0;
|
||||
int r;
|
||||
int entrycount=0;
|
||||
char **logfiles;
|
||||
|
||||
int lockfd;
|
||||
|
||||
{
|
||||
|
@ -514,117 +462,61 @@ int tokudb_recover(const char *data_dir, const char *log_dir) {
|
|||
}
|
||||
}
|
||||
|
||||
r = toku_delete_rolltmp_files(log_dir);
|
||||
if (r!=0) { failresult=r; goto fail; }
|
||||
|
||||
int n_logfiles;
|
||||
r = toku_logger_find_logfiles(log_dir, &logfiles, &n_logfiles);
|
||||
if (r!=0) { failresult=r; goto fail; }
|
||||
int i;
|
||||
toku_recover_init();
|
||||
char org_wd[1000];
|
||||
{
|
||||
char *wd=getcwd(org_wd, sizeof(org_wd));
|
||||
assert(wd!=0);
|
||||
//printf("%s:%d org_wd=\"%s\"\n", __FILE__, __LINE__, org_wd);
|
||||
}
|
||||
char data_wd[1000];
|
||||
{
|
||||
r=chdir(data_dir); assert(r==0);
|
||||
char *wd=getcwd(data_wd, sizeof(data_wd));
|
||||
assert(wd!=0);
|
||||
//printf("%s:%d data_wd=\"%s\"\n", __FILE__, __LINE__, data_wd);
|
||||
|
||||
r = toku_delete_rolltmp_files(log_dir);
|
||||
if (r!=0) { failresult=r; goto fail; }
|
||||
|
||||
TOKULOGCURSOR logcursor;
|
||||
r = toku_logcursor_create(&logcursor, log_dir);
|
||||
assert(r == 0);
|
||||
|
||||
toku_recover_init();
|
||||
|
||||
r = chdir(data_dir);
|
||||
assert(r == 0);
|
||||
|
||||
struct log_entry *le;
|
||||
|
||||
struct backward_scan_state bs = initial_bss;
|
||||
while (1) {
|
||||
r = toku_logcursor_prev(logcursor, &le);
|
||||
if (0) printf("%s:%d r=%d cmd=%c\n", __FUNCTION__, __LINE__, r, r==0 ? le->cmd:'?');
|
||||
if (r != 0)
|
||||
break;
|
||||
logtype_dispatch_assign(le, toku_recover_backward_, r, &bs);
|
||||
if (r != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
LSN lastlsn = ZERO_LSN;
|
||||
FILE *f = NULL;
|
||||
for (i=0; i<n_logfiles; i++) {
|
||||
if (f) fclose(f);
|
||||
r=chdir(org_wd);
|
||||
assert(r==0);
|
||||
char *logfile = logfiles[n_logfiles-i-1];
|
||||
f = fopen(logfile, "r");
|
||||
assert(f);
|
||||
printf("Opened %s\n", logfiles[n_logfiles-i-1]);
|
||||
r = fseek(f, 0, SEEK_END); assert(r==0);
|
||||
struct log_entry le;
|
||||
struct backward_scan_state bs = initial_bss;
|
||||
r=chdir(data_wd);
|
||||
assert(r==0);
|
||||
while (1) {
|
||||
r = toku_log_fread_backward(f, &le);
|
||||
if (r==-1) break; // Ran out of file
|
||||
LSN thislsn = toku_log_entry_get_lsn(&le);
|
||||
if (lastlsn.lsn != 0) {
|
||||
if (thislsn.lsn != lastlsn.lsn - 1)
|
||||
printf("bw lastlsn=%"PRIu64" lsn=%"PRIu64"\n", lastlsn.lsn, thislsn.lsn);
|
||||
//assert(thislsn.lsn == lastlsn.lsn - 1);
|
||||
}
|
||||
lastlsn = thislsn;
|
||||
logtype_dispatch_assign(&le, toku_recover_backward_, r, &bs);
|
||||
if (r!=0) goto go_forward;
|
||||
}
|
||||
while (1) {
|
||||
r = toku_logcursor_next(logcursor, &le);
|
||||
if (0) printf("%s:%d r=%d cmd=%c\n", __FUNCTION__, __LINE__, r, r==0 ? le->cmd:'?');
|
||||
if (r != 0)
|
||||
break;
|
||||
logtype_dispatch_args(le, toku_recover_);
|
||||
}
|
||||
i--;
|
||||
// We got to the end of the last log
|
||||
if (f) { r=fclose(f); assert(r==0); }
|
||||
|
||||
// Now we go forward from this point
|
||||
while (n_logfiles-i-1<n_logfiles) {
|
||||
//fprintf(stderr, "Opening %s\n", logfiles[i]);
|
||||
int j = n_logfiles-i-1;
|
||||
r=chdir(org_wd);
|
||||
assert(r==0);
|
||||
f = fopen(logfiles[j], "r");
|
||||
assert(f);
|
||||
struct log_entry le;
|
||||
u_int32_t version;
|
||||
//printf("Reading file %d: %s\n", j, logfiles[j]);
|
||||
r=toku_read_logmagic(f, &version);
|
||||
assert(r==0 && version==TOKU_LOG_VERSION);
|
||||
go_forward: // we have an open file, so go forward.
|
||||
//printf("Going forward\n");
|
||||
r=chdir(data_wd);
|
||||
assert(r==0);
|
||||
while ((r = toku_log_fread(f, &le))==0) {
|
||||
//printf("doing %c\n", le.cmd);
|
||||
LSN thislsn = toku_log_entry_get_lsn(&le);
|
||||
if (lastlsn.lsn != thislsn.lsn) {
|
||||
printf("fw expectlsn=%"PRIu64" lsn=%"PRIu64"\n", lastlsn.lsn, thislsn.lsn);
|
||||
}
|
||||
// assert(lastlsn.lsn == thislsn.lsn);
|
||||
lastlsn.lsn += 1;
|
||||
|
||||
logtype_dispatch_args(&le, toku_recover_);
|
||||
entrycount++;
|
||||
}
|
||||
if (r!=EOF) {
|
||||
if (r==DB_BADFORMAT) {
|
||||
fprintf(stderr, "Bad log format at record %d\n", entrycount);
|
||||
return r;
|
||||
} else {
|
||||
fprintf(stderr, "Huh? %s\n", strerror(r));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
i--;
|
||||
}
|
||||
toku_recover_cleanup();
|
||||
for (i=0; logfiles[i]; i++) {
|
||||
toku_free(logfiles[i]);
|
||||
}
|
||||
toku_free(logfiles);
|
||||
|
||||
r = chdir(org_wd);
|
||||
assert(r == 0);
|
||||
|
||||
r = toku_logcursor_destroy(&logcursor);
|
||||
assert(r == 0);
|
||||
|
||||
r=toku_os_unlock_file(lockfd);
|
||||
if (r!=0) return errno;
|
||||
|
||||
r=chdir(org_wd);
|
||||
if (r!=0) return errno;
|
||||
|
||||
//printf("%s:%d recovery successful! ls -l says\n", __FILE__, __LINE__);
|
||||
//system("ls -l");
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
toku_os_unlock_file(lockfd);
|
||||
chdir(org_wd);
|
||||
|
|
|
@ -83,7 +83,8 @@ REGRESSION_TESTS_RAW = \
|
|||
log-test5 \
|
||||
log-test6 \
|
||||
log-test7 \
|
||||
log-test-timestamp \
|
||||
logcursor-timestamp \
|
||||
logcursor-empty-logdir \
|
||||
memtest \
|
||||
minicron-test \
|
||||
omt-cursor-test \
|
||||
|
@ -97,6 +98,7 @@ REGRESSION_TESTS_RAW = \
|
|||
test-inc-split \
|
||||
test-leafentry10 \
|
||||
test-leafentry-nested \
|
||||
test_logcursor \
|
||||
test_oexcl \
|
||||
test_toku_malloc_plain_free \
|
||||
threadpool-test \
|
||||
|
|
39
newbrt/tests/logcursor-empty-logdir.c
Normal file
39
newbrt/tests/logcursor-empty-logdir.c
Normal file
|
@ -0,0 +1,39 @@
|
|||
/* -*- mode: C; c-basic-offset: 4 -*- */
|
||||
#ident "Copyright (c) 2007, 2008 Tokutek Inc. All rights reserved."
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "includes.h"
|
||||
|
||||
#define dname __FILE__ ".dir"
|
||||
#define rmrf "rm -rf " dname "/"
|
||||
|
||||
// a logcursor in an empty directory should not find any log entries
|
||||
|
||||
int
|
||||
test_main (int argc, const char *argv[]) {
|
||||
default_parse_args(argc, argv);
|
||||
|
||||
int r;
|
||||
system(rmrf);
|
||||
r = toku_os_mkdir(dname, S_IRWXU); assert(r==0);
|
||||
|
||||
// verify the log is empty
|
||||
|
||||
TOKULOGCURSOR lc = NULL;
|
||||
struct log_entry *le;
|
||||
|
||||
r = toku_logcursor_create(&lc, dname);
|
||||
assert(r == 0 && lc != NULL);
|
||||
|
||||
r = toku_logcursor_next(lc, &le);
|
||||
assert(r != 0);
|
||||
|
||||
r = toku_logcursor_prev(lc, &le);
|
||||
assert(r != 0);
|
||||
|
||||
r = toku_logcursor_destroy(&lc);
|
||||
assert(r == 0 && lc == NULL);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -19,8 +19,9 @@ static u_int64_t now(void) {
|
|||
// a cursor through the log entries
|
||||
|
||||
int
|
||||
test_main (int argc __attribute__((__unused__)),
|
||||
const char *argv[] __attribute__((__unused__))) {
|
||||
test_main (int argc, const char *argv[]) {
|
||||
default_parse_args(argc, argv);
|
||||
|
||||
int r;
|
||||
system(rmrf);
|
||||
r = toku_os_mkdir(dname, S_IRWXU); assert(r==0);
|
||||
|
@ -48,12 +49,13 @@ test_main (int argc __attribute__((__unused__)),
|
|||
r = toku_logger_close(&logger);
|
||||
assert(r == 0);
|
||||
|
||||
// TODO verify the log
|
||||
// verify the log forwards
|
||||
TOKULOGCURSOR lc = NULL;
|
||||
struct log_entry *le;
|
||||
|
||||
r = toku_logcursor_create(&lc, dname);
|
||||
assert(r == 0 && lc != NULL);
|
||||
|
||||
struct log_entry *le;
|
||||
r = toku_logcursor_next(lc, &le);
|
||||
assert(r == 0 && le->cmd == LT_timestamp);
|
||||
assert(le->u.timestamp.comment.len == 5 && memcmp(le->u.timestamp.comment.data, "hello", 5) == 0);
|
||||
|
@ -62,7 +64,9 @@ test_main (int argc __attribute__((__unused__)),
|
|||
r = toku_logcursor_next(lc, &le);
|
||||
assert(r == 0 && le->cmd == LT_timestamp);
|
||||
assert(le->u.timestamp.comment.len == 5 && memcmp(le->u.timestamp.comment.data, "world", 5) == 0);
|
||||
printf("%"PRId64"\n", le->u.timestamp.timestamp - t);
|
||||
if (verbose)
|
||||
printf("%"PRId64"\n", le->u.timestamp.timestamp - t);
|
||||
assert(le->u.timestamp.timestamp - t >= 10*1000000);
|
||||
|
||||
r = toku_logcursor_next(lc, &le);
|
||||
assert(r != 0);
|
||||
|
@ -70,5 +74,27 @@ test_main (int argc __attribute__((__unused__)),
|
|||
r = toku_logcursor_destroy(&lc);
|
||||
assert(r == 0 && lc == NULL);
|
||||
|
||||
// verify the log backwards
|
||||
r = toku_logcursor_create(&lc, dname);
|
||||
assert(r == 0 && lc != NULL);
|
||||
|
||||
r = toku_logcursor_prev(lc, &le);
|
||||
assert(r == 0 && le->cmd == LT_timestamp);
|
||||
assert(le->u.timestamp.comment.len == 5 && memcmp(le->u.timestamp.comment.data, "world", 5) == 0);
|
||||
t = le->u.timestamp.timestamp;
|
||||
|
||||
r = toku_logcursor_prev(lc, &le);
|
||||
assert(r == 0 && le->cmd == LT_timestamp);
|
||||
assert(le->u.timestamp.comment.len == 5 && memcmp(le->u.timestamp.comment.data, "hello", 5) == 0);
|
||||
if (verbose)
|
||||
printf("%"PRId64"\n", t - le->u.timestamp.timestamp);
|
||||
assert(t - le->u.timestamp.timestamp >= 10*1000000);
|
||||
|
||||
r = toku_logcursor_prev(lc, &le);
|
||||
assert(r != 0);
|
||||
|
||||
r = toku_logcursor_destroy(&lc);
|
||||
assert(r == 0 && lc == NULL);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -6,50 +6,116 @@
|
|||
#include "includes.h"
|
||||
|
||||
int test_0 ();
|
||||
int test_1 ();
|
||||
static void usage() {
|
||||
printf("test_logcursors [OPTIONS]\n");
|
||||
printf("[-v]\n");
|
||||
printf("[-q]\n");
|
||||
}
|
||||
|
||||
int test_main(int argc, const char *argv[]) {
|
||||
int i;
|
||||
for (i=1; i<argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
if (arg[0] != '-')
|
||||
break;
|
||||
if (strcmp(arg, "-v")==0) {
|
||||
verbose++;
|
||||
} else if (strcmp(arg, "-q")==0) {
|
||||
verbose = 0;
|
||||
} else {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int test_main(int argc __attribute__((unused)), const char *argv[] __attribute__((unused))) {
|
||||
int r = 0;
|
||||
|
||||
r = test_0();
|
||||
|
||||
return r
|
||||
if ( (r=test_0()) !=0 ) return r;
|
||||
if ( (r=test_1()) !=0 ) return r;
|
||||
return r;
|
||||
}
|
||||
|
||||
int test_0 () {
|
||||
int r=0;
|
||||
char dbdir[100] = "./dir.test_logcursor.tdb";
|
||||
char dbdir[100] = "./dir.test_logcursor";
|
||||
struct toku_logcursor *cursor;
|
||||
struct log_entry *entry;
|
||||
|
||||
r = toku_logcursor_create(&cursor, dbdir);
|
||||
if ( r!=0 ) return r;
|
||||
r = toku_logcursor_next(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
r = toku_logcursor_next(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
r = toku_logcursor_next(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
printf("r=%d\n", r);
|
||||
r = toku_logcursor_destroy(&cursor);
|
||||
r = toku_logcursor_create(&cursor, dbdir); if (verbose) printf("create returns %d\n", r); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_destroy(&cursor); if (verbose) printf("destroy returns %d\n", r); assert(r==0);
|
||||
|
||||
r = toku_logcursor_create(&cursor, dbdir);
|
||||
if ( r!=0 ) return r;
|
||||
r = toku_logcursor_prev(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
r = toku_logcursor_prev(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
r = toku_logcursor_prev(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
printf("r=%d\n", r);
|
||||
r = toku_logcursor_destroy(&cursor);
|
||||
r = toku_logcursor_create(&cursor, dbdir); if (verbose) printf("create returns %d\n", r); assert(r==0);
|
||||
r = toku_logcursor_first(cursor, &entry); if (verbose) printf("First Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_destroy(&cursor); if (verbose) printf("destroy returns %d\n", r); assert(r==0);
|
||||
|
||||
r = toku_logcursor_create(&cursor, dbdir);
|
||||
if ( r!=0 ) return r;
|
||||
r = toku_logcursor_next(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
r = toku_logcursor_next(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
r = toku_logcursor_next(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
r = toku_logcursor_prev(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
r = toku_logcursor_prev(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
r = toku_logcursor_prev(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
r = toku_logcursor_prev(cursor, &entry); printf("Entry = %c\n", entry->cmd);
|
||||
if ( r == DB_NOTFOUND ) printf("PASS\n"); else printf("FAIL\n");
|
||||
r = toku_logcursor_create(&cursor, dbdir); if (verbose) printf("create returns %d\n", r); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_destroy(&cursor); if (verbose) printf("destroy returns %d\n", r); assert(r==0);
|
||||
|
||||
r = toku_logcursor_destroy(&cursor);
|
||||
r = toku_logcursor_create(&cursor, dbdir); if (verbose) printf("create returns %d\n", r); assert(r==0);
|
||||
r = toku_logcursor_last(cursor, &entry); if (verbose) printf("Last Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_destroy(&cursor); if (verbose) printf("destroy returns %d\n", r); assert(r==0);
|
||||
|
||||
r = toku_logcursor_create(&cursor, dbdir); if (verbose) printf("create returns %d\n", r); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); assert(r==DB_NOTFOUND);
|
||||
r = toku_logcursor_destroy(&cursor); if (verbose) printf("destroy returns %d\n", r); assert(r==0);
|
||||
|
||||
r = toku_logcursor_create(&cursor, dbdir); if (verbose) printf("create returns %d\n", r); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); assert(r==DB_NOTFOUND);
|
||||
r = toku_logcursor_destroy(&cursor); if (verbose) printf("destroy returns %d\n", r); assert(r==0);
|
||||
|
||||
r = toku_logcursor_create(&cursor, dbdir); if (verbose) printf("create returns %d\n", r); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_next(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd); assert(r==0);
|
||||
r = toku_logcursor_prev(cursor, &entry); if (verbose) printf("Entry = %c\n", entry->cmd);
|
||||
if ( verbose) {
|
||||
if ( r == DB_NOTFOUND ) printf("PASS\n");
|
||||
else printf("FAIL\n");
|
||||
}
|
||||
assert(r==DB_NOTFOUND);
|
||||
r = toku_logcursor_destroy(&cursor); if (verbose) printf("destroy returns %d\n", r); assert(r==0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// test per-file version
|
||||
int test_1 () {
|
||||
int r=0;
|
||||
char dbdir[100] = "./dir.test_logcursor";
|
||||
char logfile[100] = "log000000000000.tokulog";
|
||||
struct toku_logcursor *cursor;
|
||||
struct log_entry *entry;
|
||||
|
||||
r = toku_logcursor_create_for_file(&cursor, dbdir, logfile);
|
||||
if (verbose) printf("create returns %d\n", r);
|
||||
assert(r==0);
|
||||
|
||||
r = toku_logcursor_last(cursor, &entry);
|
||||
if (verbose) printf("entry = %c\n", entry->cmd);
|
||||
assert(r==0);
|
||||
assert(entry->cmd =='C');
|
||||
|
||||
r = toku_logcursor_destroy(&cursor);
|
||||
if (verbose) printf("destroy returns %d\n", r);
|
||||
assert(r==0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue