diff options
Diffstat (limited to 'src/third_party/wiredtiger/test/format/ops.c')
-rw-r--r-- | src/third_party/wiredtiger/test/format/ops.c | 2955 |
1 files changed, 1430 insertions, 1525 deletions
diff --git a/src/third_party/wiredtiger/test/format/ops.c b/src/third_party/wiredtiger/test/format/ops.c index 7adfb795694..a03b42e427b 100644 --- a/src/third_party/wiredtiger/test/format/ops.c +++ b/src/third_party/wiredtiger/test/format/ops.c @@ -28,50 +28,50 @@ #include "format.h" -static int col_insert(TINFO *, WT_CURSOR *); -static int col_modify(TINFO *, WT_CURSOR *, bool); -static int col_remove(TINFO *, WT_CURSOR *, bool); -static int col_reserve(TINFO *, WT_CURSOR *, bool); -static int col_truncate(TINFO *, WT_CURSOR *); -static int col_update(TINFO *, WT_CURSOR *, bool); -static int nextprev(TINFO *, WT_CURSOR *, bool); +static int col_insert(TINFO *, WT_CURSOR *); +static int col_modify(TINFO *, WT_CURSOR *, bool); +static int col_remove(TINFO *, WT_CURSOR *, bool); +static int col_reserve(TINFO *, WT_CURSOR *, bool); +static int col_truncate(TINFO *, WT_CURSOR *); +static int col_update(TINFO *, WT_CURSOR *, bool); +static int nextprev(TINFO *, WT_CURSOR *, bool); static WT_THREAD_RET ops(void *); -static int read_row(TINFO *, WT_CURSOR *); -static int row_insert(TINFO *, WT_CURSOR *, bool); -static int row_modify(TINFO *, WT_CURSOR *, bool); -static int row_remove(TINFO *, WT_CURSOR *, bool); -static int row_reserve(TINFO *, WT_CURSOR *, bool); -static int row_truncate(TINFO *, WT_CURSOR *); -static int row_update(TINFO *, WT_CURSOR *, bool); -static void table_append_init(void); +static int read_row(TINFO *, WT_CURSOR *); +static int row_insert(TINFO *, WT_CURSOR *, bool); +static int row_modify(TINFO *, WT_CURSOR *, bool); +static int row_remove(TINFO *, WT_CURSOR *, bool); +static int row_reserve(TINFO *, WT_CURSOR *, bool); +static int row_truncate(TINFO *, WT_CURSOR *); +static int row_update(TINFO *, WT_CURSOR *, bool); +static void table_append_init(void); static char modify_repl[256]; /* * modify_repl_init -- - * Initialize the replacement information. + * Initialize the replacement information. */ static void modify_repl_init(void) { - size_t i; + size_t i; - for (i = 0; i < sizeof(modify_repl); ++i) - modify_repl[i] = "zyxwvutsrqponmlkjihgfedcba"[i % 26]; + for (i = 0; i < sizeof(modify_repl); ++i) + modify_repl[i] = "zyxwvutsrqponmlkjihgfedcba"[i % 26]; } static void set_alarm(void) { #ifdef HAVE_TIMER_CREATE - struct itimerspec timer_val; - timer_t timer_id; - - testutil_check(timer_create(CLOCK_REALTIME, NULL, &timer_id)); - memset(&timer_val, 0, sizeof(timer_val)); - timer_val.it_value.tv_sec = 60 * 2; - timer_val.it_value.tv_nsec = 0; - testutil_check(timer_settime(timer_id, 0, &timer_val, NULL)); + struct itimerspec timer_val; + timer_t timer_id; + + testutil_check(timer_create(CLOCK_REALTIME, NULL, &timer_id)); + memset(&timer_val, 0, sizeof(timer_val)); + timer_val.it_value.tv_sec = 60 * 2; + timer_val.it_value.tv_nsec = 0; + testutil_check(timer_settime(timer_id, 0, &timer_val, NULL)); #endif } @@ -79,341 +79,319 @@ TINFO **tinfo_list; /* * wts_ops -- - * Perform a number of operations in a set of threads. + * Perform a number of operations in a set of threads. */ void wts_ops(bool lastrun) { - TINFO *tinfo, total; - WT_CONNECTION *conn; - WT_SESSION *session; - wt_thread_t alter_tid, backup_tid, checkpoint_tid, compact_tid, lrt_tid; - wt_thread_t timestamp_tid; - int64_t fourths, quit_fourths, thread_ops; - uint32_t i; - bool running; - - conn = g.wts_conn; - - session = NULL; /* -Wconditional-uninitialized */ - memset(&alter_tid, 0, sizeof(alter_tid)); - memset(&backup_tid, 0, sizeof(backup_tid)); - memset(&checkpoint_tid, 0, sizeof(checkpoint_tid)); - memset(&compact_tid, 0, sizeof(compact_tid)); - memset(&lrt_tid, 0, sizeof(lrt_tid)); - memset(×tamp_tid, 0, sizeof(timestamp_tid)); - - modify_repl_init(); - - /* - * There are two mechanisms to specify the length of the run, a number - * of operations and a timer, when either expire the run terminates. - * - * Each thread does an equal share of the total operations (and make - * sure that it's not 0). - * - * Calculate how many fourth-of-a-second sleeps until the timer expires. - * If the timer expires and threads don't return in 15 minutes, assume - * there is something hung, and force the quit. - */ - if (g.c_ops == 0) - thread_ops = -1; - else { - if (g.c_ops < g.c_threads) - g.c_ops = g.c_threads; - thread_ops = g.c_ops / g.c_threads; - } - if (g.c_timer == 0) - fourths = quit_fourths = -1; - else { - fourths = ((int64_t)g.c_timer * 4 * 60) / FORMAT_OPERATION_REPS; - quit_fourths = fourths + 15 * 4 * 60; - } - - /* Initialize the table extension code. */ - table_append_init(); - - /* - * We support replay of threaded runs, but don't log random numbers - * after threaded operations start, there's no point. - */ - if (!SINGLETHREADED) - g.rand_log_stop = true; - - /* Logging requires a session. */ - if (g.logging) - testutil_check(conn->open_session(conn, NULL, NULL, &session)); - logop(session, "%s", "=============== thread ops start"); - - /* - * Create the per-thread structures and start the worker threads. - * Allocate the thread structures separately to minimize false sharing. - */ - tinfo_list = dcalloc((size_t)g.c_threads + 1, sizeof(TINFO *)); - for (i = 0; i < g.c_threads; ++i) { - tinfo_list[i] = tinfo = dcalloc(1, sizeof(TINFO)); - - tinfo->id = (int)i + 1; - - /* - * Characterize the per-thread random number generator. Normally - * we want independent behavior so threads start in different - * parts of the RNG space, but we've found bugs by having the - * threads pound on the same key/value pairs, that is, by making - * them traverse the same RNG space. 75% of the time we run in - * independent RNG space. - */ - if (g.c_independent_thread_rng) - __wt_random_init_seed( - (WT_SESSION_IMPL *)session, &tinfo->rnd); - else - __wt_random_init(&tinfo->rnd); - - tinfo->state = TINFO_RUNNING; - testutil_check( - __wt_thread_create(NULL, &tinfo->tid, ops, tinfo)); - } - - /* - * If a multi-threaded run, start optional backup, compaction and - * long-running reader threads. - */ - if (g.c_alter) - testutil_check( - __wt_thread_create(NULL, &alter_tid, alter, NULL)); - if (g.c_backups) - testutil_check( - __wt_thread_create(NULL, &backup_tid, backup, NULL)); - if (g.c_checkpoint_flag == CHECKPOINT_ON) - testutil_check(__wt_thread_create( - NULL, &checkpoint_tid, checkpoint, NULL)); - if (g.c_compact) - testutil_check( - __wt_thread_create(NULL, &compact_tid, compact, NULL)); - if (!SINGLETHREADED && g.c_long_running_txn) - testutil_check(__wt_thread_create(NULL, &lrt_tid, lrt, NULL)); - if (g.c_txn_timestamps) - testutil_check(__wt_thread_create( - NULL, ×tamp_tid, timestamp, tinfo_list)); - - /* Spin on the threads, calculating the totals. */ - for (;;) { - /* Clear out the totals each pass. */ - memset(&total, 0, sizeof(total)); - for (i = 0, running = false; i < g.c_threads; ++i) { - tinfo = tinfo_list[i]; - total.commit += tinfo->commit; - total.insert += tinfo->insert; - total.prepare += tinfo->prepare; - total.remove += tinfo->remove; - total.rollback += tinfo->rollback; - total.search += tinfo->search; - total.truncate += tinfo->truncate; - total.update += tinfo->update; - - switch (tinfo->state) { - case TINFO_RUNNING: - running = true; - break; - case TINFO_COMPLETE: - tinfo->state = TINFO_JOINED; - testutil_check( - __wt_thread_join(NULL, &tinfo->tid)); - break; - case TINFO_JOINED: - break; - } - - /* - * If the timer has expired or this thread has completed - * its operations, notify the thread it should quit. - */ - if (fourths == 0 || - (thread_ops != -1 && - tinfo->ops >= (uint64_t)thread_ops)) { - /* - * On the last execution, optionally drop core - * for recovery testing. - */ - if (lastrun && g.c_abort) { - static char *core = NULL; - *core = 0; - } - tinfo->quit = true; - } - } - track("ops", 0ULL, &total); - if (!running) - break; - __wt_sleep(0, 250000); /* 1/4th of a second */ - if (fourths != -1) - --fourths; - if (quit_fourths != -1 && --quit_fourths == 0) { - fprintf(stderr, "%s\n", - "format run more than 15 minutes past the maximum " - "time"); - fprintf(stderr, "%s\n", - "format run dumping cache and transaction state, " - "then aborting the process"); - - /* - * If the library is deadlocked, we might just join the - * mess, set a timer to limit our exposure. - */ - set_alarm(); - - (void)conn->debug_info(conn, "txn"); - (void)conn->debug_info(conn, "cache"); - - __wt_abort(NULL); - } - } - - /* Wait for the other threads. */ - g.workers_finished = true; - if (g.c_alter) - testutil_check(__wt_thread_join(NULL, &alter_tid)); - if (g.c_backups) - testutil_check(__wt_thread_join(NULL, &backup_tid)); - if (g.c_checkpoint_flag == CHECKPOINT_ON) - testutil_check(__wt_thread_join(NULL, &checkpoint_tid)); - if (g.c_compact) - testutil_check(__wt_thread_join(NULL, &compact_tid)); - if (!SINGLETHREADED && g.c_long_running_txn) - testutil_check(__wt_thread_join(NULL, &lrt_tid)); - if (g.c_txn_timestamps) - testutil_check(__wt_thread_join(NULL, ×tamp_tid)); - g.workers_finished = false; - - logop(session, "%s", "=============== thread ops stop"); - if (g.logging) - testutil_check(session->close(session, NULL)); - - for (i = 0; i < g.c_threads; ++i) - free(tinfo_list[i]); - free(tinfo_list); + TINFO *tinfo, total; + WT_CONNECTION *conn; + WT_SESSION *session; + wt_thread_t alter_tid, backup_tid, checkpoint_tid, compact_tid, lrt_tid; + wt_thread_t timestamp_tid; + int64_t fourths, quit_fourths, thread_ops; + uint32_t i; + bool running; + + conn = g.wts_conn; + + session = NULL; /* -Wconditional-uninitialized */ + memset(&alter_tid, 0, sizeof(alter_tid)); + memset(&backup_tid, 0, sizeof(backup_tid)); + memset(&checkpoint_tid, 0, sizeof(checkpoint_tid)); + memset(&compact_tid, 0, sizeof(compact_tid)); + memset(&lrt_tid, 0, sizeof(lrt_tid)); + memset(×tamp_tid, 0, sizeof(timestamp_tid)); + + modify_repl_init(); + + /* + * There are two mechanisms to specify the length of the run, a number + * of operations and a timer, when either expire the run terminates. + * + * Each thread does an equal share of the total operations (and make + * sure that it's not 0). + * + * Calculate how many fourth-of-a-second sleeps until the timer expires. + * If the timer expires and threads don't return in 15 minutes, assume + * there is something hung, and force the quit. + */ + if (g.c_ops == 0) + thread_ops = -1; + else { + if (g.c_ops < g.c_threads) + g.c_ops = g.c_threads; + thread_ops = g.c_ops / g.c_threads; + } + if (g.c_timer == 0) + fourths = quit_fourths = -1; + else { + fourths = ((int64_t)g.c_timer * 4 * 60) / FORMAT_OPERATION_REPS; + quit_fourths = fourths + 15 * 4 * 60; + } + + /* Initialize the table extension code. */ + table_append_init(); + + /* + * We support replay of threaded runs, but don't log random numbers after threaded operations + * start, there's no point. + */ + if (!SINGLETHREADED) + g.rand_log_stop = true; + + /* Logging requires a session. */ + if (g.logging) + testutil_check(conn->open_session(conn, NULL, NULL, &session)); + logop(session, "%s", "=============== thread ops start"); + + /* + * Create the per-thread structures and start the worker threads. Allocate the thread structures + * separately to minimize false sharing. + */ + tinfo_list = dcalloc((size_t)g.c_threads + 1, sizeof(TINFO *)); + for (i = 0; i < g.c_threads; ++i) { + tinfo_list[i] = tinfo = dcalloc(1, sizeof(TINFO)); + + tinfo->id = (int)i + 1; + + /* + * Characterize the per-thread random number generator. Normally we want independent + * behavior so threads start in different parts of the RNG space, but we've found bugs by + * having the threads pound on the same key/value pairs, that is, by making them traverse + * the same RNG space. 75% of the time we run in independent RNG space. + */ + if (g.c_independent_thread_rng) + __wt_random_init_seed((WT_SESSION_IMPL *)session, &tinfo->rnd); + else + __wt_random_init(&tinfo->rnd); + + tinfo->state = TINFO_RUNNING; + testutil_check(__wt_thread_create(NULL, &tinfo->tid, ops, tinfo)); + } + + /* + * If a multi-threaded run, start optional backup, compaction and long-running reader threads. + */ + if (g.c_alter) + testutil_check(__wt_thread_create(NULL, &alter_tid, alter, NULL)); + if (g.c_backups) + testutil_check(__wt_thread_create(NULL, &backup_tid, backup, NULL)); + if (g.c_checkpoint_flag == CHECKPOINT_ON) + testutil_check(__wt_thread_create(NULL, &checkpoint_tid, checkpoint, NULL)); + if (g.c_compact) + testutil_check(__wt_thread_create(NULL, &compact_tid, compact, NULL)); + if (!SINGLETHREADED && g.c_long_running_txn) + testutil_check(__wt_thread_create(NULL, &lrt_tid, lrt, NULL)); + if (g.c_txn_timestamps) + testutil_check(__wt_thread_create(NULL, ×tamp_tid, timestamp, tinfo_list)); + + /* Spin on the threads, calculating the totals. */ + for (;;) { + /* Clear out the totals each pass. */ + memset(&total, 0, sizeof(total)); + for (i = 0, running = false; i < g.c_threads; ++i) { + tinfo = tinfo_list[i]; + total.commit += tinfo->commit; + total.insert += tinfo->insert; + total.prepare += tinfo->prepare; + total.remove += tinfo->remove; + total.rollback += tinfo->rollback; + total.search += tinfo->search; + total.truncate += tinfo->truncate; + total.update += tinfo->update; + + switch (tinfo->state) { + case TINFO_RUNNING: + running = true; + break; + case TINFO_COMPLETE: + tinfo->state = TINFO_JOINED; + testutil_check(__wt_thread_join(NULL, &tinfo->tid)); + break; + case TINFO_JOINED: + break; + } + + /* + * If the timer has expired or this thread has completed its operations, notify the + * thread it should quit. + */ + if (fourths == 0 || (thread_ops != -1 && tinfo->ops >= (uint64_t)thread_ops)) { + /* + * On the last execution, optionally drop core for recovery testing. + */ + if (lastrun && g.c_abort) { + static char *core = NULL; + *core = 0; + } + tinfo->quit = true; + } + } + track("ops", 0ULL, &total); + if (!running) + break; + __wt_sleep(0, 250000); /* 1/4th of a second */ + if (fourths != -1) + --fourths; + if (quit_fourths != -1 && --quit_fourths == 0) { + fprintf(stderr, "%s\n", + "format run more than 15 minutes past the maximum " + "time"); + fprintf(stderr, "%s\n", + "format run dumping cache and transaction state, " + "then aborting the process"); + + /* + * If the library is deadlocked, we might just join the mess, set a timer to limit our + * exposure. + */ + set_alarm(); + + (void)conn->debug_info(conn, "txn"); + (void)conn->debug_info(conn, "cache"); + + __wt_abort(NULL); + } + } + + /* Wait for the other threads. */ + g.workers_finished = true; + if (g.c_alter) + testutil_check(__wt_thread_join(NULL, &alter_tid)); + if (g.c_backups) + testutil_check(__wt_thread_join(NULL, &backup_tid)); + if (g.c_checkpoint_flag == CHECKPOINT_ON) + testutil_check(__wt_thread_join(NULL, &checkpoint_tid)); + if (g.c_compact) + testutil_check(__wt_thread_join(NULL, &compact_tid)); + if (!SINGLETHREADED && g.c_long_running_txn) + testutil_check(__wt_thread_join(NULL, &lrt_tid)); + if (g.c_txn_timestamps) + testutil_check(__wt_thread_join(NULL, ×tamp_tid)); + g.workers_finished = false; + + logop(session, "%s", "=============== thread ops stop"); + if (g.logging) + testutil_check(session->close(session, NULL)); + + for (i = 0; i < g.c_threads; ++i) + free(tinfo_list[i]); + free(tinfo_list); } /* * begin_transaction_ts -- - * Begin a timestamped transaction. + * Begin a timestamped transaction. */ static void begin_transaction_ts(TINFO *tinfo, u_int *iso_configp) { - TINFO **tlp; - WT_DECL_RET; - WT_SESSION *session; - uint64_t ts; - const char *config; - char buf[64]; - - session = tinfo->session; - - config = "isolation=snapshot"; - *iso_configp = ISOLATION_SNAPSHOT; - - /* - * Transaction reads are normally repeatable, but WiredTiger timestamps - * allow rewriting commits, that is, applications can specify at commit - * time the timestamp at which the commit happens. If that happens, our - * read might no longer be repeatable. Test in both modes: pick a read - * timestamp we know is repeatable (because it's at least as old as the - * oldest resolved commit timestamp in any thread), and pick a current - * timestamp, 50% of the time. - */ - ts = 0; - if (mmrand(&tinfo->rnd, 1, 2) == 1) - for (ts = UINT64_MAX, tlp = tinfo_list; *tlp != NULL; ++tlp) - ts = WT_MIN(ts, (*tlp)->commit_ts); - if (ts != 0) { - wiredtiger_begin_transaction(session, config); - - /* - * If the timestamp has aged out of the system, we'll get EINVAL - * when we try and set it. That kills the transaction, we have - * to restart. - */ - testutil_check(__wt_snprintf( - buf, sizeof(buf), "read_timestamp=%" PRIx64, ts)); - ret = session->timestamp_transaction(session, buf); - if (ret == 0) { - snap_init(tinfo, ts, true); - logop(session, - "begin snapshot read-ts=%" PRIu64 " (repeatable)", - ts); - return; - } - if (ret != EINVAL) - testutil_check(ret); - - testutil_check(session->rollback_transaction(session, NULL)); - } - - wiredtiger_begin_transaction(session, config); - - /* - * Otherwise, pick a current timestamp. - * - * Prepare returns an error if the prepare timestamp is less - * than any active read timestamp, single-thread transaction - * prepare and begin. - * - * Lock out the oldest timestamp update. - */ - testutil_check(pthread_rwlock_wrlock(&g.ts_lock)); - - ts = __wt_atomic_addv64(&g.timestamp, 1); - testutil_check(__wt_snprintf( - buf, sizeof(buf), "read_timestamp=%" PRIx64, ts)); - testutil_check(session->timestamp_transaction(session, buf)); - - testutil_check(pthread_rwlock_unlock(&g.ts_lock)); - - snap_init(tinfo, ts, false); - logop(session, - "begin snapshot read-ts=%" PRIu64 " (not repeatable)", ts); + TINFO **tlp; + WT_DECL_RET; + WT_SESSION *session; + uint64_t ts; + const char *config; + char buf[64]; + + session = tinfo->session; + + config = "isolation=snapshot"; + *iso_configp = ISOLATION_SNAPSHOT; + + /* + * Transaction reads are normally repeatable, but WiredTiger timestamps allow rewriting commits, + * that is, applications can specify at commit time the timestamp at which the commit happens. + * If that happens, our read might no longer be repeatable. Test in both modes: pick a read + * timestamp we know is repeatable (because it's at least as old as the oldest resolved commit + * timestamp in any thread), and pick a current timestamp, 50% of the time. + */ + ts = 0; + if (mmrand(&tinfo->rnd, 1, 2) == 1) + for (ts = UINT64_MAX, tlp = tinfo_list; *tlp != NULL; ++tlp) + ts = WT_MIN(ts, (*tlp)->commit_ts); + if (ts != 0) { + wiredtiger_begin_transaction(session, config); + + /* + * If the timestamp has aged out of the system, we'll get EINVAL when we try and set it. + * That kills the transaction, we have to restart. + */ + testutil_check(__wt_snprintf(buf, sizeof(buf), "read_timestamp=%" PRIx64, ts)); + ret = session->timestamp_transaction(session, buf); + if (ret == 0) { + snap_init(tinfo, ts, true); + logop(session, "begin snapshot read-ts=%" PRIu64 " (repeatable)", ts); + return; + } + if (ret != EINVAL) + testutil_check(ret); + + testutil_check(session->rollback_transaction(session, NULL)); + } + + wiredtiger_begin_transaction(session, config); + + /* + * Otherwise, pick a current timestamp. + * + * Prepare returns an error if the prepare timestamp is less + * than any active read timestamp, single-thread transaction + * prepare and begin. + * + * Lock out the oldest timestamp update. + */ + testutil_check(pthread_rwlock_wrlock(&g.ts_lock)); + + ts = __wt_atomic_addv64(&g.timestamp, 1); + testutil_check(__wt_snprintf(buf, sizeof(buf), "read_timestamp=%" PRIx64, ts)); + testutil_check(session->timestamp_transaction(session, buf)); + + testutil_check(pthread_rwlock_unlock(&g.ts_lock)); + + snap_init(tinfo, ts, false); + logop(session, "begin snapshot read-ts=%" PRIu64 " (not repeatable)", ts); } /* * begin_transaction -- - * Choose an isolation configuration and begin a transaction. + * Choose an isolation configuration and begin a transaction. */ static void begin_transaction(TINFO *tinfo, u_int *iso_configp) { - WT_SESSION *session; - u_int v; - const char *config, *log; - - session = tinfo->session; - - if ((v = g.c_isolation_flag) == ISOLATION_RANDOM) - v = mmrand(&tinfo->rnd, 1, 3); - switch (v) { - case 1: - v = ISOLATION_READ_UNCOMMITTED; - log = "read-uncommitted"; - config = "isolation=read-uncommitted"; - break; - case 2: - v = ISOLATION_READ_COMMITTED; - log = "read-committed"; - config = "isolation=read-committed"; - break; - case 3: - default: - v = ISOLATION_SNAPSHOT; - log = "snapshot"; - config = "isolation=snapshot"; - break; - } - *iso_configp = v; - - wiredtiger_begin_transaction(session, config); - - snap_init(tinfo, WT_TS_NONE, false); - logop(session, "begin %s", log); + WT_SESSION *session; + u_int v; + const char *config, *log; + + session = tinfo->session; + + if ((v = g.c_isolation_flag) == ISOLATION_RANDOM) + v = mmrand(&tinfo->rnd, 1, 3); + switch (v) { + case 1: + v = ISOLATION_READ_UNCOMMITTED; + log = "read-uncommitted"; + config = "isolation=read-uncommitted"; + break; + case 2: + v = ISOLATION_READ_COMMITTED; + log = "read-committed"; + config = "isolation=read-committed"; + break; + case 3: + default: + v = ISOLATION_SNAPSHOT; + log = "snapshot"; + config = "isolation=snapshot"; + break; + } + *iso_configp = v; + + wiredtiger_begin_transaction(session, config); + + snap_init(tinfo, WT_TS_NONE, false); + logop(session, "begin %s", log); } /* @@ -423,41 +401,37 @@ begin_transaction(TINFO *tinfo, u_int *iso_configp) static void commit_transaction(TINFO *tinfo, bool prepared) { - WT_SESSION *session; - uint64_t ts; - char buf[64]; + WT_SESSION *session; + uint64_t ts; + char buf[64]; - ++tinfo->commit; + ++tinfo->commit; - session = tinfo->session; + session = tinfo->session; - ts = 0; /* -Wconditional-uninitialized */ - if (g.c_txn_timestamps) { - /* Lock out the oldest timestamp update. */ - testutil_check(pthread_rwlock_wrlock(&g.ts_lock)); + ts = 0; /* -Wconditional-uninitialized */ + if (g.c_txn_timestamps) { + /* Lock out the oldest timestamp update. */ + testutil_check(pthread_rwlock_wrlock(&g.ts_lock)); - ts = __wt_atomic_addv64(&g.timestamp, 1); - testutil_check(__wt_snprintf( - buf, sizeof(buf), "commit_timestamp=%" PRIx64, ts)); - testutil_check(session->timestamp_transaction(session, buf)); + ts = __wt_atomic_addv64(&g.timestamp, 1); + testutil_check(__wt_snprintf(buf, sizeof(buf), "commit_timestamp=%" PRIx64, ts)); + testutil_check(session->timestamp_transaction(session, buf)); - if (prepared) { - testutil_check(__wt_snprintf(buf, sizeof(buf), - "durable_timestamp=%" PRIx64, ts)); - testutil_check( - session->timestamp_transaction(session, buf)); - } + if (prepared) { + testutil_check(__wt_snprintf(buf, sizeof(buf), "durable_timestamp=%" PRIx64, ts)); + testutil_check(session->timestamp_transaction(session, buf)); + } - testutil_check(pthread_rwlock_unlock(&g.ts_lock)); - } - testutil_check(session->commit_transaction(session, NULL)); + testutil_check(pthread_rwlock_unlock(&g.ts_lock)); + } + testutil_check(session->commit_transaction(session, NULL)); - /* Remember our oldest commit timestamp. */ - tinfo->commit_ts = ts; + /* Remember our oldest commit timestamp. */ + tinfo->commit_ts = ts; - logop(session, - "commit read-ts=%" PRIu64 ", commit-ts=%" PRIu64, - tinfo->read_ts, tinfo->commit_ts); + logop( + session, "commit read-ts=%" PRIu64 ", commit-ts=%" PRIu64, tinfo->read_ts, tinfo->commit_ts); } /* @@ -467,16 +441,15 @@ commit_transaction(TINFO *tinfo, bool prepared) static void rollback_transaction(TINFO *tinfo) { - WT_SESSION *session; + WT_SESSION *session; - session = tinfo->session; + session = tinfo->session; - ++tinfo->rollback; + ++tinfo->rollback; - testutil_check(session->rollback_transaction(session, NULL)); + testutil_check(session->rollback_transaction(session, NULL)); - logop(session, - "abort read-ts=%" PRIu64, tinfo->read_ts); + logop(session, "abort read-ts=%" PRIu64, tinfo->read_ts); } /* @@ -486,131 +459,130 @@ rollback_transaction(TINFO *tinfo) static int prepare_transaction(TINFO *tinfo) { - WT_DECL_RET; - WT_SESSION *session; - uint64_t ts; - char buf[64]; - - session = tinfo->session; - - ++tinfo->prepare; - - /* - * Prepare timestamps must be less than or equal to the eventual commit - * timestamp. Set the prepare timestamp to whatever the global value is - * now. The subsequent commit will increment it, ensuring correctness. - * - * Prepare returns an error if the prepare timestamp is less than any - * active read timestamp, single-thread transaction prepare and begin. - * - * Lock out the oldest timestamp update. - */ - testutil_check(pthread_rwlock_wrlock(&g.ts_lock)); - - ts = __wt_atomic_addv64(&g.timestamp, 1); - testutil_check(__wt_snprintf( - buf, sizeof(buf), "prepare_timestamp=%" PRIx64, ts)); - ret = session->prepare_transaction(session, buf); - - testutil_check(pthread_rwlock_unlock(&g.ts_lock)); - - return (ret); + WT_DECL_RET; + WT_SESSION *session; + uint64_t ts; + char buf[64]; + + session = tinfo->session; + + ++tinfo->prepare; + + /* + * Prepare timestamps must be less than or equal to the eventual commit + * timestamp. Set the prepare timestamp to whatever the global value is + * now. The subsequent commit will increment it, ensuring correctness. + * + * Prepare returns an error if the prepare timestamp is less than any + * active read timestamp, single-thread transaction prepare and begin. + * + * Lock out the oldest timestamp update. + */ + testutil_check(pthread_rwlock_wrlock(&g.ts_lock)); + + ts = __wt_atomic_addv64(&g.timestamp, 1); + testutil_check(__wt_snprintf(buf, sizeof(buf), "prepare_timestamp=%" PRIx64, ts)); + ret = session->prepare_transaction(session, buf); + + testutil_check(pthread_rwlock_unlock(&g.ts_lock)); + + return (ret); } /* * OP_FAILED -- * General error handling. */ -#define OP_FAILED(notfound_ok) do { \ - positioned = false; \ - if (intxn && (ret == WT_CACHE_FULL || ret == WT_ROLLBACK)) \ - goto rollback; \ - testutil_assert((notfound_ok && ret == WT_NOTFOUND) || \ - ret == WT_CACHE_FULL || ret == WT_ROLLBACK); \ -} while (0) +#define OP_FAILED(notfound_ok) \ + do { \ + positioned = false; \ + if (intxn && (ret == WT_CACHE_FULL || ret == WT_ROLLBACK)) \ + goto rollback; \ + testutil_assert( \ + (notfound_ok && ret == WT_NOTFOUND) || ret == WT_CACHE_FULL || ret == WT_ROLLBACK); \ + } while (0) /* - * Rollback updates returning prepare-conflict, they're unlikely to succeed - * unless the prepare aborts. Reads wait out the error, so it's unexpected. + * Rollback updates returning prepare-conflict, they're unlikely to succeed unless the prepare + * aborts. Reads wait out the error, so it's unexpected. */ -#define READ_OP_FAILED(notfound_ok) \ - OP_FAILED(notfound_ok) -#define WRITE_OP_FAILED(notfound_ok) do { \ - if (ret == WT_PREPARE_CONFLICT) \ - ret = WT_ROLLBACK; \ - OP_FAILED(notfound_ok); \ -} while (0) +#define READ_OP_FAILED(notfound_ok) OP_FAILED(notfound_ok) +#define WRITE_OP_FAILED(notfound_ok) \ + do { \ + if (ret == WT_PREPARE_CONFLICT) \ + ret = WT_ROLLBACK; \ + OP_FAILED(notfound_ok); \ + } while (0) /* - * When in a transaction on the live table with snapshot isolation, track - * operations for later repetition. + * When in a transaction on the live table with snapshot isolation, track operations for later + * repetition. */ -#define SNAP_TRACK(tinfo, op) do { \ - if (intxn && !ckpt_handle && iso_config == ISOLATION_SNAPSHOT) \ - snap_track(tinfo, op); \ -} while (0) +#define SNAP_TRACK(tinfo, op) \ + do { \ + if (intxn && !ckpt_handle && iso_config == ISOLATION_SNAPSHOT) \ + snap_track(tinfo, op); \ + } while (0) /* * ops_open_session -- - * Create a new session/cursor pair for the thread. + * Create a new session/cursor pair for the thread. */ static void ops_open_session(TINFO *tinfo, bool *ckpt_handlep) { - WT_CONNECTION *conn; - WT_CURSOR *cursor; - WT_DECL_RET; - WT_SESSION *session; - - conn = g.wts_conn; - - /* Close any open session/cursor. */ - if ((session = tinfo->session) != NULL) - testutil_check(session->close(session, NULL)); - - testutil_check(conn->open_session(conn, NULL, NULL, &session)); - - /* - * 10% of the time, perform some read-only operations from a checkpoint. - * Skip if we are using data-sources or LSM, they don't support reading - * from checkpoints. - */ - cursor = NULL; - if (!DATASOURCE("lsm") && mmrand(&tinfo->rnd, 1, 10) == 1) { - /* - * WT_SESSION.open_cursor can return EBUSY if concurrent with a - * metadata operation, retry. - */ - while ((ret = session->open_cursor(session, g.uri, NULL, - "checkpoint=WiredTigerCheckpoint", &cursor)) == EBUSY) - __wt_yield(); - - /* - * If the checkpoint hasn't been created yet, ignore the error. - */ - if (ret != ENOENT) { - testutil_check(ret); - *ckpt_handlep = true; - } - } - if (cursor == NULL) { - /* - * Configure "append", in the case of column stores, we append - * when inserting new rows. - * - * WT_SESSION.open_cursor can return EBUSY if concurrent with a - * metadata operation, retry. - */ - while ((ret = session->open_cursor(session, - g.uri, NULL, "append", &cursor)) == EBUSY) - __wt_yield(); - - testutil_check(ret); - *ckpt_handlep = false; - } - - tinfo->session = session; - tinfo->cursor = cursor; + WT_CONNECTION *conn; + WT_CURSOR *cursor; + WT_DECL_RET; + WT_SESSION *session; + + conn = g.wts_conn; + + /* Close any open session/cursor. */ + if ((session = tinfo->session) != NULL) + testutil_check(session->close(session, NULL)); + + testutil_check(conn->open_session(conn, NULL, NULL, &session)); + + /* + * 10% of the time, perform some read-only operations from a checkpoint. + * Skip if we are using data-sources or LSM, they don't support reading + * from checkpoints. + */ + cursor = NULL; + if (!DATASOURCE("lsm") && mmrand(&tinfo->rnd, 1, 10) == 1) { + /* + * WT_SESSION.open_cursor can return EBUSY if concurrent with a metadata operation, retry. + */ + while ((ret = session->open_cursor( + session, g.uri, NULL, "checkpoint=WiredTigerCheckpoint", &cursor)) == EBUSY) + __wt_yield(); + + /* + * If the checkpoint hasn't been created yet, ignore the error. + */ + if (ret != ENOENT) { + testutil_check(ret); + *ckpt_handlep = true; + } + } + if (cursor == NULL) { + /* + * Configure "append", in the case of column stores, we append + * when inserting new rows. + * + * WT_SESSION.open_cursor can return EBUSY if concurrent with a + * metadata operation, retry. + */ + while ((ret = session->open_cursor(session, g.uri, NULL, "append", &cursor)) == EBUSY) + __wt_yield(); + + testutil_check(ret); + *ckpt_handlep = false; + } + + tinfo->session = session; + tinfo->cursor = cursor; } /* @@ -620,1238 +592,1171 @@ ops_open_session(TINFO *tinfo, bool *ckpt_handlep) static WT_THREAD_RET ops(void *arg) { - TINFO *tinfo; - WT_CURSOR *cursor; - WT_DECL_RET; - WT_SESSION *session; - thread_op op; - uint64_t reset_op, session_op, truncate_op; - uint32_t range, rnd; - u_int i, j, iso_config; - bool ckpt_handle, greater_than, intxn, next, positioned, prepared; - - tinfo = arg; - - iso_config = ISOLATION_RANDOM; /* -Wconditional-uninitialized */ - ckpt_handle = false; /* -Wconditional-uninitialized */ - - /* Tracking of transactional snapshot isolation operations. */ - tinfo->snap = tinfo->snap_first = tinfo->snap_list; - - /* Set up the default key and value buffers. */ - tinfo->key = &tinfo->_key; - key_gen_init(tinfo->key); - tinfo->value = &tinfo->_value; - val_gen_init(tinfo->value); - tinfo->lastkey = &tinfo->_lastkey; - key_gen_init(tinfo->lastkey); - tinfo->tbuf = &tinfo->_tbuf; - - /* Set the first operation where we'll create sessions and cursors. */ - cursor = NULL; - session = NULL; - session_op = 0; - - /* Set the first operation where we'll reset the session. */ - reset_op = mmrand(&tinfo->rnd, 100, 10000); - /* Set the first operation where we'll truncate a range. */ - truncate_op = g.c_truncate == 0 ? - UINT64_MAX : mmrand(&tinfo->rnd, 100, 10000); - - for (intxn = false; !tinfo->quit; ++tinfo->ops) { - /* Periodically open up a new session and cursors. */ - if (tinfo->ops > session_op || - session == NULL || cursor == NULL) { - /* - * We can't swap sessions/cursors if in a transaction, - * resolve any running transaction. - */ - if (intxn) { - commit_transaction(tinfo, false); - intxn = false; - } - - ops_open_session(tinfo, &ckpt_handle); - - /* Pick the next session/cursor close/open. */ - session_op += mmrand(&tinfo->rnd, 100, 5000); - - session = tinfo->session; - cursor = tinfo->cursor; - } - - /* - * If not in a transaction, reset the session now and then, just - * to make sure that operation gets tested. The test is not for - * equality, we have to do the reset outside of a transaction so - * we aren't likely to get an exact match. - */ - if (!intxn && tinfo->ops > reset_op) { - testutil_check(session->reset(session)); - - /* Pick the next reset operation. */ - reset_op += mmrand(&tinfo->rnd, 20000, 50000); - } - - /* - * If not in a transaction, have a live handle and running in a - * timestamp world, occasionally repeat a timestamped operation. - */ - if (!intxn && !ckpt_handle && - g.c_txn_timestamps && mmrand(&tinfo->rnd, 1, 15) == 1) { - ++tinfo->search; - snap_repeat_single(cursor, tinfo); - } - - /* - * If not in a transaction and have a live handle, choose an - * isolation level and start a transaction some percentage of - * the time. - */ - if (!intxn && (g.c_txn_timestamps || - mmrand(&tinfo->rnd, 1, 100) <= g.c_txn_freq)) { - if (g.c_txn_timestamps) - begin_transaction_ts(tinfo, &iso_config); - else - begin_transaction(tinfo, &iso_config); - intxn = true; - } - - /* Select an operation. */ - op = READ; - if (!ckpt_handle) { - i = mmrand(&tinfo->rnd, 1, 100); - if (i < g.c_delete_pct && tinfo->ops > truncate_op) { - op = TRUNCATE; - - /* Pick the next truncate operation. */ - truncate_op += - mmrand(&tinfo->rnd, 20000, 100000); - } else if (i < g.c_delete_pct) - op = REMOVE; - else if (i < g.c_delete_pct + g.c_insert_pct) - op = INSERT; - else if (i < g.c_delete_pct + - g.c_insert_pct + g.c_modify_pct) - op = MODIFY; - else if (i < g.c_delete_pct + - g.c_insert_pct + g.c_modify_pct + g.c_write_pct) - op = UPDATE; - } - - /* Select a row. */ - tinfo->keyno = mmrand(&tinfo->rnd, 1, (u_int)g.rows); - - /* - * Inserts, removes and updates can be done following a cursor - * set-key, or based on a cursor position taken from a previous - * search. If not already doing a read, position the cursor at - * an existing point in the tree 20% of the time. - */ - positioned = false; - if (op != READ && mmrand(&tinfo->rnd, 1, 5) == 1) { - ++tinfo->search; - ret = read_row(tinfo, cursor); - if (ret == 0) { - positioned = true; - SNAP_TRACK(tinfo, READ); - } else - READ_OP_FAILED(true); - } - - /* - * Optionally reserve a row. Reserving a row before a read isn't - * all that sensible, but not unexpected, either. - */ - if (intxn && !ckpt_handle && mmrand(&tinfo->rnd, 0, 20) == 1) { - switch (g.type) { - case ROW: - ret = row_reserve(tinfo, cursor, positioned); - break; - case FIX: - case VAR: - ret = col_reserve(tinfo, cursor, positioned); - break; - } - if (ret == 0) { - positioned = true; - - __wt_yield(); /* Let other threads proceed. */ - } else - WRITE_OP_FAILED(true); - } - - /* Perform the operation. */ - switch (op) { - case INSERT: - switch (g.type) { - case ROW: - ret = row_insert(tinfo, cursor, positioned); - break; - case FIX: - case VAR: - /* - * We can only append so many new records, once - * we reach that limit, update a record instead - * of inserting. - */ - if (g.append_cnt >= g.append_max) - goto update_instead_of_chosen_op; - - ret = col_insert(tinfo, cursor); - break; - } - - /* Insert never leaves the cursor positioned. */ - positioned = false; - if (ret == 0) { - ++tinfo->insert; - SNAP_TRACK(tinfo, INSERT); - } else - WRITE_OP_FAILED(false); - break; - case MODIFY: - /* - * Change modify into update if not part of a snapshot - * isolation transaction, modify isn't supported in - * those cases. - */ - if (!intxn || iso_config != ISOLATION_SNAPSHOT) - goto update_instead_of_chosen_op; - - ++tinfo->update; - switch (g.type) { - case ROW: - ret = row_modify(tinfo, cursor, positioned); - break; - case VAR: - ret = col_modify(tinfo, cursor, positioned); - break; - } - if (ret == 0) { - positioned = true; - SNAP_TRACK(tinfo, MODIFY); - } else - WRITE_OP_FAILED(true); - break; - case READ: - ++tinfo->search; - ret = read_row(tinfo, cursor); - if (ret == 0) { - positioned = true; - SNAP_TRACK(tinfo, READ); - } else - READ_OP_FAILED(true); - break; - case REMOVE: -remove_instead_of_truncate: - switch (g.type) { - case ROW: - ret = row_remove(tinfo, cursor, positioned); - break; - case FIX: - case VAR: - ret = col_remove(tinfo, cursor, positioned); - break; - } - if (ret == 0) { - ++tinfo->remove; - /* - * Don't set positioned: it's unchanged from the - * previous state, but not necessarily set. - */ - SNAP_TRACK(tinfo, REMOVE); - } else - WRITE_OP_FAILED(true); - break; - case TRUNCATE: - /* - * A maximum of 2 truncation operations at a time, more - * than that can lead to serious thrashing. - */ - if (__wt_atomic_addv64(&g.truncate_cnt, 1) > 2) { - (void)__wt_atomic_subv64(&g.truncate_cnt, 1); - goto remove_instead_of_truncate; - } - - if (!positioned) - tinfo->keyno = - mmrand(&tinfo->rnd, 1, (u_int)g.rows); - - /* - * Truncate up to 5% of the table. If the range overlaps - * the beginning/end of the table, set the key to 0 (the - * truncate function then sets a cursor to NULL so that - * code is tested). - * - * This gets tricky: there are 2 directions (truncating - * from lower keys to the current position or from - * the current position to higher keys), and collation - * order (truncating from lower keys to higher keys or - * vice-versa). - */ - greater_than = mmrand(&tinfo->rnd, 0, 1) == 1; - range = g.rows < 20 ? - 0 : mmrand(&tinfo->rnd, 0, (u_int)g.rows / 20); - tinfo->last = tinfo->keyno; - if (greater_than) { - if (g.c_reverse) { - if (tinfo->keyno <= range) - tinfo->last = 0; - else - tinfo->last -= range; - } else { - tinfo->last += range; - if (tinfo->last > g.rows) - tinfo->last = 0; - } - } else { - if (g.c_reverse) { - tinfo->keyno += range; - if (tinfo->keyno > g.rows) - tinfo->keyno = 0; - } else { - if (tinfo->keyno <= range) - tinfo->keyno = 0; - else - tinfo->keyno -= range; - } - } - switch (g.type) { - case ROW: - ret = row_truncate(tinfo, cursor); - break; - case FIX: - case VAR: - ret = col_truncate(tinfo, cursor); - break; - } - (void)__wt_atomic_subv64(&g.truncate_cnt, 1); - - /* Truncate never leaves the cursor positioned. */ - positioned = false; - if (ret == 0) { - ++tinfo->truncate; - SNAP_TRACK(tinfo, TRUNCATE); - } else - WRITE_OP_FAILED(false); - break; - case UPDATE: -update_instead_of_chosen_op: - ++tinfo->update; - switch (g.type) { - case ROW: - ret = row_update(tinfo, cursor, positioned); - break; - case FIX: - case VAR: - ret = col_update(tinfo, cursor, positioned); - break; - } - if (ret == 0) { - positioned = true; - SNAP_TRACK(tinfo, UPDATE); - } else - WRITE_OP_FAILED(false); - break; - } - - /* - * The cursor is positioned if we did any operation other than - * insert, do a small number of next/prev cursor operations in - * a random direction. - */ - if (positioned) { - next = mmrand(&tinfo->rnd, 0, 1) == 1; - j = mmrand(&tinfo->rnd, 1, 100); - for (i = 0; i < j; ++i) { - if ((ret = nextprev(tinfo, cursor, next)) == 0) - continue; - - READ_OP_FAILED(true); - break; - } - } - - /* Reset the cursor: there is no reason to keep pages pinned. */ - testutil_check(cursor->reset(cursor)); - - /* - * Continue if not in a transaction, else add more operations - * to the transaction half the time. - */ - if (!intxn || (rnd = mmrand(&tinfo->rnd, 1, 10)) > 5) - continue; - - /* - * Ending a transaction. If on a live handle and the transaction - * was configured for snapshot isolation, repeat the operations - * and confirm the results are unchanged. - */ - if (intxn && !ckpt_handle && iso_config == ISOLATION_SNAPSHOT) { - __wt_yield(); /* Encourage races */ - - ret = snap_repeat_txn(cursor, tinfo); - testutil_assert(ret == 0 || ret == WT_ROLLBACK); - if (ret == WT_ROLLBACK) - goto rollback; - } - - /* - * If prepare configured, prepare the transaction 10% of the - * time. - */ - prepared = false; - if (g.c_prepare && mmrand(&tinfo->rnd, 1, 10) == 1) { - ret = prepare_transaction(tinfo); - if (ret != 0) - WRITE_OP_FAILED(false); - - __wt_yield(); /* Encourage races */ - prepared = true; - } - - /* - * If we're in a transaction, commit 40% of the time and - * rollback 10% of the time. - */ - switch (rnd) { - case 1: case 2: case 3: case 4: /* 40% */ - commit_transaction(tinfo, prepared); - snap_repeat_update(tinfo, true); - break; - case 5: /* 10% */ -rollback: rollback_transaction(tinfo); - snap_repeat_update(tinfo, false); - break; - } - - intxn = false; - } - - if (session != NULL) - testutil_check(session->close(session, NULL)); - - for (i = 0; i < WT_ELEMENTS(tinfo->snap_list); ++i) { - free(tinfo->snap_list[i].kdata); - free(tinfo->snap_list[i].vdata); - } - key_gen_teardown(tinfo->key); - val_gen_teardown(tinfo->value); - key_gen_teardown(tinfo->lastkey); - free(tinfo->tbuf->mem); - - tinfo->state = TINFO_COMPLETE; - return (WT_THREAD_RET_VALUE); + TINFO *tinfo; + WT_CURSOR *cursor; + WT_DECL_RET; + WT_SESSION *session; + thread_op op; + uint64_t reset_op, session_op, truncate_op; + uint32_t range, rnd; + u_int i, j, iso_config; + bool ckpt_handle, greater_than, intxn, next, positioned, prepared; + + tinfo = arg; + + iso_config = ISOLATION_RANDOM; /* -Wconditional-uninitialized */ + ckpt_handle = false; /* -Wconditional-uninitialized */ + + /* Tracking of transactional snapshot isolation operations. */ + tinfo->snap = tinfo->snap_first = tinfo->snap_list; + + /* Set up the default key and value buffers. */ + tinfo->key = &tinfo->_key; + key_gen_init(tinfo->key); + tinfo->value = &tinfo->_value; + val_gen_init(tinfo->value); + tinfo->lastkey = &tinfo->_lastkey; + key_gen_init(tinfo->lastkey); + tinfo->tbuf = &tinfo->_tbuf; + + /* Set the first operation where we'll create sessions and cursors. */ + cursor = NULL; + session = NULL; + session_op = 0; + + /* Set the first operation where we'll reset the session. */ + reset_op = mmrand(&tinfo->rnd, 100, 10000); + /* Set the first operation where we'll truncate a range. */ + truncate_op = g.c_truncate == 0 ? UINT64_MAX : mmrand(&tinfo->rnd, 100, 10000); + + for (intxn = false; !tinfo->quit; ++tinfo->ops) { + /* Periodically open up a new session and cursors. */ + if (tinfo->ops > session_op || session == NULL || cursor == NULL) { + /* + * We can't swap sessions/cursors if in a transaction, resolve any running transaction. + */ + if (intxn) { + commit_transaction(tinfo, false); + intxn = false; + } + + ops_open_session(tinfo, &ckpt_handle); + + /* Pick the next session/cursor close/open. */ + session_op += mmrand(&tinfo->rnd, 100, 5000); + + session = tinfo->session; + cursor = tinfo->cursor; + } + + /* + * If not in a transaction, reset the session now and then, just to make sure that operation + * gets tested. The test is not for equality, we have to do the reset outside of a + * transaction so we aren't likely to get an exact match. + */ + if (!intxn && tinfo->ops > reset_op) { + testutil_check(session->reset(session)); + + /* Pick the next reset operation. */ + reset_op += mmrand(&tinfo->rnd, 20000, 50000); + } + + /* + * If not in a transaction, have a live handle and running in a timestamp world, + * occasionally repeat a timestamped operation. + */ + if (!intxn && !ckpt_handle && g.c_txn_timestamps && mmrand(&tinfo->rnd, 1, 15) == 1) { + ++tinfo->search; + snap_repeat_single(cursor, tinfo); + } + + /* + * If not in a transaction and have a live handle, choose an isolation level and start a + * transaction some percentage of the time. + */ + if (!intxn && (g.c_txn_timestamps || mmrand(&tinfo->rnd, 1, 100) <= g.c_txn_freq)) { + if (g.c_txn_timestamps) + begin_transaction_ts(tinfo, &iso_config); + else + begin_transaction(tinfo, &iso_config); + intxn = true; + } + + /* Select an operation. */ + op = READ; + if (!ckpt_handle) { + i = mmrand(&tinfo->rnd, 1, 100); + if (i < g.c_delete_pct && tinfo->ops > truncate_op) { + op = TRUNCATE; + + /* Pick the next truncate operation. */ + truncate_op += mmrand(&tinfo->rnd, 20000, 100000); + } else if (i < g.c_delete_pct) + op = REMOVE; + else if (i < g.c_delete_pct + g.c_insert_pct) + op = INSERT; + else if (i < g.c_delete_pct + g.c_insert_pct + g.c_modify_pct) + op = MODIFY; + else if (i < g.c_delete_pct + g.c_insert_pct + g.c_modify_pct + g.c_write_pct) + op = UPDATE; + } + + /* Select a row. */ + tinfo->keyno = mmrand(&tinfo->rnd, 1, (u_int)g.rows); + + /* + * Inserts, removes and updates can be done following a cursor set-key, or based on a cursor + * position taken from a previous search. If not already doing a read, position the cursor + * at an existing point in the tree 20% of the time. + */ + positioned = false; + if (op != READ && mmrand(&tinfo->rnd, 1, 5) == 1) { + ++tinfo->search; + ret = read_row(tinfo, cursor); + if (ret == 0) { + positioned = true; + SNAP_TRACK(tinfo, READ); + } else + READ_OP_FAILED(true); + } + + /* + * Optionally reserve a row. Reserving a row before a read isn't all that sensible, but not + * unexpected, either. + */ + if (intxn && !ckpt_handle && mmrand(&tinfo->rnd, 0, 20) == 1) { + switch (g.type) { + case ROW: + ret = row_reserve(tinfo, cursor, positioned); + break; + case FIX: + case VAR: + ret = col_reserve(tinfo, cursor, positioned); + break; + } + if (ret == 0) { + positioned = true; + + __wt_yield(); /* Let other threads proceed. */ + } else + WRITE_OP_FAILED(true); + } + + /* Perform the operation. */ + switch (op) { + case INSERT: + switch (g.type) { + case ROW: + ret = row_insert(tinfo, cursor, positioned); + break; + case FIX: + case VAR: + /* + * We can only append so many new records, once we reach that limit, update a record + * instead of inserting. + */ + if (g.append_cnt >= g.append_max) + goto update_instead_of_chosen_op; + + ret = col_insert(tinfo, cursor); + break; + } + + /* Insert never leaves the cursor positioned. */ + positioned = false; + if (ret == 0) { + ++tinfo->insert; + SNAP_TRACK(tinfo, INSERT); + } else + WRITE_OP_FAILED(false); + break; + case MODIFY: + /* + * Change modify into update if not part of a snapshot isolation transaction, modify + * isn't supported in those cases. + */ + if (!intxn || iso_config != ISOLATION_SNAPSHOT) + goto update_instead_of_chosen_op; + + ++tinfo->update; + switch (g.type) { + case ROW: + ret = row_modify(tinfo, cursor, positioned); + break; + case VAR: + ret = col_modify(tinfo, cursor, positioned); + break; + } + if (ret == 0) { + positioned = true; + SNAP_TRACK(tinfo, MODIFY); + } else + WRITE_OP_FAILED(true); + break; + case READ: + ++tinfo->search; + ret = read_row(tinfo, cursor); + if (ret == 0) { + positioned = true; + SNAP_TRACK(tinfo, READ); + } else + READ_OP_FAILED(true); + break; + case REMOVE: + remove_instead_of_truncate: + switch (g.type) { + case ROW: + ret = row_remove(tinfo, cursor, positioned); + break; + case FIX: + case VAR: + ret = col_remove(tinfo, cursor, positioned); + break; + } + if (ret == 0) { + ++tinfo->remove; + /* + * Don't set positioned: it's unchanged from the previous state, but not necessarily + * set. + */ + SNAP_TRACK(tinfo, REMOVE); + } else + WRITE_OP_FAILED(true); + break; + case TRUNCATE: + /* + * A maximum of 2 truncation operations at a time, more than that can lead to serious + * thrashing. + */ + if (__wt_atomic_addv64(&g.truncate_cnt, 1) > 2) { + (void)__wt_atomic_subv64(&g.truncate_cnt, 1); + goto remove_instead_of_truncate; + } + + if (!positioned) + tinfo->keyno = mmrand(&tinfo->rnd, 1, (u_int)g.rows); + + /* + * Truncate up to 5% of the table. If the range overlaps + * the beginning/end of the table, set the key to 0 (the + * truncate function then sets a cursor to NULL so that + * code is tested). + * + * This gets tricky: there are 2 directions (truncating + * from lower keys to the current position or from + * the current position to higher keys), and collation + * order (truncating from lower keys to higher keys or + * vice-versa). + */ + greater_than = mmrand(&tinfo->rnd, 0, 1) == 1; + range = g.rows < 20 ? 0 : mmrand(&tinfo->rnd, 0, (u_int)g.rows / 20); + tinfo->last = tinfo->keyno; + if (greater_than) { + if (g.c_reverse) { + if (tinfo->keyno <= range) + tinfo->last = 0; + else + tinfo->last -= range; + } else { + tinfo->last += range; + if (tinfo->last > g.rows) + tinfo->last = 0; + } + } else { + if (g.c_reverse) { + tinfo->keyno += range; + if (tinfo->keyno > g.rows) + tinfo->keyno = 0; + } else { + if (tinfo->keyno <= range) + tinfo->keyno = 0; + else + tinfo->keyno -= range; + } + } + switch (g.type) { + case ROW: + ret = row_truncate(tinfo, cursor); + break; + case FIX: + case VAR: + ret = col_truncate(tinfo, cursor); + break; + } + (void)__wt_atomic_subv64(&g.truncate_cnt, 1); + + /* Truncate never leaves the cursor positioned. */ + positioned = false; + if (ret == 0) { + ++tinfo->truncate; + SNAP_TRACK(tinfo, TRUNCATE); + } else + WRITE_OP_FAILED(false); + break; + case UPDATE: + update_instead_of_chosen_op: + ++tinfo->update; + switch (g.type) { + case ROW: + ret = row_update(tinfo, cursor, positioned); + break; + case FIX: + case VAR: + ret = col_update(tinfo, cursor, positioned); + break; + } + if (ret == 0) { + positioned = true; + SNAP_TRACK(tinfo, UPDATE); + } else + WRITE_OP_FAILED(false); + break; + } + + /* + * The cursor is positioned if we did any operation other than insert, do a small number of + * next/prev cursor operations in a random direction. + */ + if (positioned) { + next = mmrand(&tinfo->rnd, 0, 1) == 1; + j = mmrand(&tinfo->rnd, 1, 100); + for (i = 0; i < j; ++i) { + if ((ret = nextprev(tinfo, cursor, next)) == 0) + continue; + + READ_OP_FAILED(true); + break; + } + } + + /* Reset the cursor: there is no reason to keep pages pinned. */ + testutil_check(cursor->reset(cursor)); + + /* + * Continue if not in a transaction, else add more operations to the transaction half the + * time. + */ + if (!intxn || (rnd = mmrand(&tinfo->rnd, 1, 10)) > 5) + continue; + + /* + * Ending a transaction. If on a live handle and the transaction was configured for snapshot + * isolation, repeat the operations and confirm the results are unchanged. + */ + if (intxn && !ckpt_handle && iso_config == ISOLATION_SNAPSHOT) { + __wt_yield(); /* Encourage races */ + + ret = snap_repeat_txn(cursor, tinfo); + testutil_assert(ret == 0 || ret == WT_ROLLBACK); + if (ret == WT_ROLLBACK) + goto rollback; + } + + /* + * If prepare configured, prepare the transaction 10% of the time. + */ + prepared = false; + if (g.c_prepare && mmrand(&tinfo->rnd, 1, 10) == 1) { + ret = prepare_transaction(tinfo); + if (ret != 0) + WRITE_OP_FAILED(false); + + __wt_yield(); /* Encourage races */ + prepared = true; + } + + /* + * If we're in a transaction, commit 40% of the time and rollback 10% of the time. + */ + switch (rnd) { + case 1: + case 2: + case 3: + case 4: /* 40% */ + commit_transaction(tinfo, prepared); + snap_repeat_update(tinfo, true); + break; + case 5: /* 10% */ +rollback: + rollback_transaction(tinfo); + snap_repeat_update(tinfo, false); + break; + } + + intxn = false; + } + + if (session != NULL) + testutil_check(session->close(session, NULL)); + + for (i = 0; i < WT_ELEMENTS(tinfo->snap_list); ++i) { + free(tinfo->snap_list[i].kdata); + free(tinfo->snap_list[i].vdata); + } + key_gen_teardown(tinfo->key); + val_gen_teardown(tinfo->value); + key_gen_teardown(tinfo->lastkey); + free(tinfo->tbuf->mem); + + tinfo->state = TINFO_COMPLETE; + return (WT_THREAD_RET_VALUE); } /* * wts_read_scan -- - * Read and verify a subset of the elements in a file. + * Read and verify a subset of the elements in a file. */ void wts_read_scan(void) { - WT_CONNECTION *conn; - WT_CURSOR *cursor; - WT_DECL_RET; - WT_ITEM key, value; - WT_SESSION *session; - uint64_t keyno, last_keyno; - - conn = g.wts_conn; - - /* - * We're not configuring transactions or read timestamps, if there's a - * diagnostic check, skip the scan. - */ - if (g.c_assert_read_timestamp) - return; - - /* Set up the default key/value buffers. */ - key_gen_init(&key); - val_gen_init(&value); - - /* Open a session and cursor pair. */ - testutil_check(conn->open_session(conn, NULL, NULL, &session)); - /* - * open_cursor can return EBUSY if concurrent with a metadata - * operation, retry in that case. - */ - while ((ret = session->open_cursor( - session, g.uri, NULL, NULL, &cursor)) == EBUSY) - __wt_yield(); - testutil_check(ret); - - /* Check a random subset of the records using the key. */ - for (last_keyno = keyno = 0; keyno < g.key_cnt;) { - keyno += mmrand(NULL, 1, 17); - if (keyno > g.rows) - keyno = g.rows; - if (keyno - last_keyno > 1000) { - track("read row scan", keyno, NULL); - last_keyno = keyno; - } - - switch (ret = read_row_worker( - cursor, keyno, &key, &value, false)) { - case 0: - case WT_NOTFOUND: - case WT_ROLLBACK: - case WT_PREPARE_CONFLICT: - break; - default: - testutil_die( - ret, "wts_read_scan: read row %" PRIu64, keyno); - } - } - - testutil_check(session->close(session, NULL)); - - key_gen_teardown(&key); - val_gen_teardown(&value); + WT_CONNECTION *conn; + WT_CURSOR *cursor; + WT_DECL_RET; + WT_ITEM key, value; + WT_SESSION *session; + uint64_t keyno, last_keyno; + + conn = g.wts_conn; + + /* + * We're not configuring transactions or read timestamps, if there's a diagnostic check, skip + * the scan. + */ + if (g.c_assert_read_timestamp) + return; + + /* Set up the default key/value buffers. */ + key_gen_init(&key); + val_gen_init(&value); + + /* Open a session and cursor pair. */ + testutil_check(conn->open_session(conn, NULL, NULL, &session)); + /* + * open_cursor can return EBUSY if concurrent with a metadata operation, retry in that case. + */ + while ((ret = session->open_cursor(session, g.uri, NULL, NULL, &cursor)) == EBUSY) + __wt_yield(); + testutil_check(ret); + + /* Check a random subset of the records using the key. */ + for (last_keyno = keyno = 0; keyno < g.key_cnt;) { + keyno += mmrand(NULL, 1, 17); + if (keyno > g.rows) + keyno = g.rows; + if (keyno - last_keyno > 1000) { + track("read row scan", keyno, NULL); + last_keyno = keyno; + } + + switch (ret = read_row_worker(cursor, keyno, &key, &value, false)) { + case 0: + case WT_NOTFOUND: + case WT_ROLLBACK: + case WT_PREPARE_CONFLICT: + break; + default: + testutil_die(ret, "wts_read_scan: read row %" PRIu64, keyno); + } + } + + testutil_check(session->close(session, NULL)); + + key_gen_teardown(&key); + val_gen_teardown(&value); } /* * read_row_worker -- - * Read and verify a single element in a row- or column-store file. + * Read and verify a single element in a row- or column-store file. */ int -read_row_worker( - WT_CURSOR *cursor, uint64_t keyno, WT_ITEM *key, WT_ITEM *value, bool sn) +read_row_worker(WT_CURSOR *cursor, uint64_t keyno, WT_ITEM *key, WT_ITEM *value, bool sn) { - WT_SESSION *session; - uint8_t bitfield; - int exact, ret; - - session = cursor->session; - - /* Retrieve the key/value pair by key. */ - switch (g.type) { - case FIX: - case VAR: - cursor->set_key(cursor, keyno); - break; - case ROW: - key_gen(key, keyno); - cursor->set_key(cursor, key); - break; - } - - if (sn) { - ret = read_op(cursor, SEARCH_NEAR, &exact); - if (ret == 0 && exact != 0) - ret = WT_NOTFOUND; - } else - ret = read_op(cursor, SEARCH, NULL); - switch (ret) { - case 0: - if (g.type == FIX) { - testutil_check(cursor->get_value(cursor, &bitfield)); - *(uint8_t *)(value->data) = bitfield; - value->size = 1; - } else - testutil_check(cursor->get_value(cursor, value)); - break; - case WT_NOTFOUND: - /* - * In fixed length stores, zero values at the end of the key - * space are returned as not-found. Treat this the same as - * a zero value in the key space, to match BDB's behavior. - * The WiredTiger cursor has lost its position though, so - * we return not-found, the cursor movement can't continue. - */ - if (g.type == FIX) { - *(uint8_t *)(value->data) = 0; - value->size = 1; - } - break; - default: - return (ret); - } - - /* Log the operation */ - if (ret == 0) - switch (g.type) { - case FIX: - logop(session, "%-10s%" PRIu64 " {0x%02x}", - "read", keyno, ((char *)value->data)[0]); - break; - case ROW: - case VAR: - logop(session, "%-10s%" PRIu64 " {%.*s}", - "read", keyno, - (int)value->size, (char *)value->data); - break; - } - - return (ret); + WT_SESSION *session; + uint8_t bitfield; + int exact, ret; + + session = cursor->session; + + /* Retrieve the key/value pair by key. */ + switch (g.type) { + case FIX: + case VAR: + cursor->set_key(cursor, keyno); + break; + case ROW: + key_gen(key, keyno); + cursor->set_key(cursor, key); + break; + } + + if (sn) { + ret = read_op(cursor, SEARCH_NEAR, &exact); + if (ret == 0 && exact != 0) + ret = WT_NOTFOUND; + } else + ret = read_op(cursor, SEARCH, NULL); + switch (ret) { + case 0: + if (g.type == FIX) { + testutil_check(cursor->get_value(cursor, &bitfield)); + *(uint8_t *)(value->data) = bitfield; + value->size = 1; + } else + testutil_check(cursor->get_value(cursor, value)); + break; + case WT_NOTFOUND: + /* + * In fixed length stores, zero values at the end of the key space are returned as + * not-found. Treat this the same as a zero value in the key space, to match BDB's behavior. + * The WiredTiger cursor has lost its position though, so we return not-found, the cursor + * movement can't continue. + */ + if (g.type == FIX) { + *(uint8_t *)(value->data) = 0; + value->size = 1; + } + break; + default: + return (ret); + } + + /* Log the operation */ + if (ret == 0) + switch (g.type) { + case FIX: + logop(session, "%-10s%" PRIu64 " {0x%02x}", "read", keyno, ((char *)value->data)[0]); + break; + case ROW: + case VAR: + logop(session, "%-10s%" PRIu64 " {%.*s}", "read", keyno, (int)value->size, + (char *)value->data); + break; + } + + return (ret); } /* * read_row -- - * Read and verify a single element in a row- or column-store file. + * Read and verify a single element in a row- or column-store file. */ static int read_row(TINFO *tinfo, WT_CURSOR *cursor) { - /* 25% of the time we call search-near. */ - return (read_row_worker(cursor, tinfo->keyno, - tinfo->key, tinfo->value, mmrand(&tinfo->rnd, 0, 3) == 1)); + /* 25% of the time we call search-near. */ + return (read_row_worker( + cursor, tinfo->keyno, tinfo->key, tinfo->value, mmrand(&tinfo->rnd, 0, 3) == 1)); } /* * nextprev -- - * Read and verify the next/prev element in a row- or column-store file. + * Read and verify the next/prev element in a row- or column-store file. */ static int nextprev(TINFO *tinfo, WT_CURSOR *cursor, bool next) { - WT_DECL_RET; - WT_ITEM key, value; - uint64_t keyno, keyno_prev; - uint8_t bitfield; - int cmp; - const char *which; - bool incrementing, record_gaps; - - keyno = 0; - which = next ? "next" : "prev"; - - switch (ret = read_op(cursor, next ? NEXT : PREV, NULL)) { - case 0: - switch (g.type) { - case FIX: - if ((ret = cursor->get_key(cursor, &keyno)) == 0 && - (ret = cursor->get_value(cursor, &bitfield)) == 0) { - value.data = &bitfield; - value.size = 1; - } - break; - case ROW: - if ((ret = cursor->get_key(cursor, &key)) == 0) - ret = cursor->get_value(cursor, &value); - break; - case VAR: - if ((ret = cursor->get_key(cursor, &keyno)) == 0) - ret = cursor->get_value(cursor, &value); - break; - } - if (ret != 0) - testutil_die(ret, "nextprev: get_key/get_value"); - - /* Check that keys are never returned out-of-order. */ - /* - * XXX - * WT-3889 - * LSM has a bug that prevents cursor order checks from - * working, skip the test for now. - */ - if (DATASOURCE("lsm")) - break; - - /* - * Compare the returned key with the previously returned key, - * and assert the order is correct. If not deleting keys, and - * the rows aren't in the column-store insert name space, also - * assert we don't skip groups of records (that's a page-split - * bug symptom). - */ - record_gaps = g.c_delete_pct != 0; - switch (g.type) { - case FIX: - case VAR: - if (tinfo->keyno > g.c_rows || keyno > g.c_rows) - record_gaps = true; - if (!next) { - if (tinfo->keyno < keyno || - (!record_gaps && keyno != tinfo->keyno - 1)) - goto order_error_col; - } else - if (tinfo->keyno > keyno || - (!record_gaps && keyno != tinfo->keyno + 1)) - goto order_error_col; - if (0) { -order_error_col: - testutil_die(0, - "%s returned %" PRIu64 " then %" PRIu64, - which, tinfo->keyno, keyno); - } - - tinfo->keyno = keyno; - break; - case ROW: - incrementing = - (next && !g.c_reverse) || (!next && g.c_reverse); - cmp = memcmp(tinfo->key->data, key.data, - WT_MIN(tinfo->key->size, key.size)); - if (incrementing) { - if (cmp > 0 || - (cmp == 0 && tinfo->key->size < key.size)) - goto order_error_row; - } else - if (cmp < 0 || - (cmp == 0 && tinfo->key->size > key.size)) - goto order_error_row; - if (!record_gaps) { - /* - * Convert the keys to record numbers and then - * compare less-than-or-equal. (Not less-than, - * row-store inserts new rows in-between rows - * by append a new suffix to the row's key.) - */ - testutil_check(__wt_buf_fmt( - (WT_SESSION_IMPL *)cursor->session, - tinfo->tbuf, "%.*s", - (int)tinfo->key->size, - (char *)tinfo->key->data)); - keyno_prev = - strtoul(tinfo->tbuf->data, NULL, 10); - testutil_check(__wt_buf_fmt( - (WT_SESSION_IMPL *)cursor->session, - tinfo->tbuf, "%.*s", - (int)key.size, (char *)key.data)); - keyno = strtoul(tinfo->tbuf->data, NULL, 10); - if (incrementing) { - if (keyno_prev != keyno && - keyno_prev + 1 != keyno) - goto order_error_row; - } else - if (keyno_prev != keyno && - keyno_prev - 1 != keyno) - goto order_error_row; - } - if (0) { -order_error_row: - testutil_die(0, - "%s returned {%.*s} then {%.*s}", - which, - (int)tinfo->key->size, - (char *)tinfo->key->data, - (int)key.size, (char *)key.data); - } - - testutil_check(__wt_buf_set((WT_SESSION_IMPL *) - cursor->session, tinfo->key, key.data, key.size)); - break; - } - break; - case WT_NOTFOUND: - break; - default: - return (ret); - } - - if (ret == 0) - switch (g.type) { - case FIX: - logop(cursor->session, "%-10s%" PRIu64 " {0x%02x}", - which, keyno, ((char *)value.data)[0]); - break; - case ROW: - logop(cursor->session, - "%-10s%" PRIu64 " {%.*s}, {%.*s}", which, keyno, - (int)key.size, (char *)key.data, - (int)value.size, (char *)value.data); - break; - case VAR: - logop(cursor->session, "%-10s%" PRIu64 " {%.*s}", - which, keyno, (int)value.size, (char *)value.data); - break; - } - - return (ret); + WT_DECL_RET; + WT_ITEM key, value; + uint64_t keyno, keyno_prev; + uint8_t bitfield; + int cmp; + const char *which; + bool incrementing, record_gaps; + + keyno = 0; + which = next ? "next" : "prev"; + + switch (ret = read_op(cursor, next ? NEXT : PREV, NULL)) { + case 0: + switch (g.type) { + case FIX: + if ((ret = cursor->get_key(cursor, &keyno)) == 0 && + (ret = cursor->get_value(cursor, &bitfield)) == 0) { + value.data = &bitfield; + value.size = 1; + } + break; + case ROW: + if ((ret = cursor->get_key(cursor, &key)) == 0) + ret = cursor->get_value(cursor, &value); + break; + case VAR: + if ((ret = cursor->get_key(cursor, &keyno)) == 0) + ret = cursor->get_value(cursor, &value); + break; + } + if (ret != 0) + testutil_die(ret, "nextprev: get_key/get_value"); + + /* Check that keys are never returned out-of-order. */ + /* + * XXX WT-3889 LSM has a bug that prevents cursor order checks from working, skip the test + * for now. + */ + if (DATASOURCE("lsm")) + break; + + /* + * Compare the returned key with the previously returned key, and assert the order is + * correct. If not deleting keys, and the rows aren't in the column-store insert name space, + * also assert we don't skip groups of records (that's a page-split bug symptom). + */ + record_gaps = g.c_delete_pct != 0; + switch (g.type) { + case FIX: + case VAR: + if (tinfo->keyno > g.c_rows || keyno > g.c_rows) + record_gaps = true; + if (!next) { + if (tinfo->keyno < keyno || (!record_gaps && keyno != tinfo->keyno - 1)) + goto order_error_col; + } else if (tinfo->keyno > keyno || (!record_gaps && keyno != tinfo->keyno + 1)) + goto order_error_col; + if (0) { + order_error_col: + testutil_die( + 0, "%s returned %" PRIu64 " then %" PRIu64, which, tinfo->keyno, keyno); + } + + tinfo->keyno = keyno; + break; + case ROW: + incrementing = (next && !g.c_reverse) || (!next && g.c_reverse); + cmp = memcmp(tinfo->key->data, key.data, WT_MIN(tinfo->key->size, key.size)); + if (incrementing) { + if (cmp > 0 || (cmp == 0 && tinfo->key->size < key.size)) + goto order_error_row; + } else if (cmp < 0 || (cmp == 0 && tinfo->key->size > key.size)) + goto order_error_row; + if (!record_gaps) { + /* + * Convert the keys to record numbers and then compare less-than-or-equal. (Not + * less-than, row-store inserts new rows in-between rows by append a new suffix to + * the row's key.) + */ + testutil_check(__wt_buf_fmt((WT_SESSION_IMPL *)cursor->session, tinfo->tbuf, "%.*s", + (int)tinfo->key->size, (char *)tinfo->key->data)); + keyno_prev = strtoul(tinfo->tbuf->data, NULL, 10); + testutil_check(__wt_buf_fmt((WT_SESSION_IMPL *)cursor->session, tinfo->tbuf, "%.*s", + (int)key.size, (char *)key.data)); + keyno = strtoul(tinfo->tbuf->data, NULL, 10); + if (incrementing) { + if (keyno_prev != keyno && keyno_prev + 1 != keyno) + goto order_error_row; + } else if (keyno_prev != keyno && keyno_prev - 1 != keyno) + goto order_error_row; + } + if (0) { + order_error_row: + testutil_die(0, "%s returned {%.*s} then {%.*s}", which, (int)tinfo->key->size, + (char *)tinfo->key->data, (int)key.size, (char *)key.data); + } + + testutil_check( + __wt_buf_set((WT_SESSION_IMPL *)cursor->session, tinfo->key, key.data, key.size)); + break; + } + break; + case WT_NOTFOUND: + break; + default: + return (ret); + } + + if (ret == 0) + switch (g.type) { + case FIX: + logop( + cursor->session, "%-10s%" PRIu64 " {0x%02x}", which, keyno, ((char *)value.data)[0]); + break; + case ROW: + logop(cursor->session, "%-10s%" PRIu64 " {%.*s}, {%.*s}", which, keyno, (int)key.size, + (char *)key.data, (int)value.size, (char *)value.data); + break; + case VAR: + logop(cursor->session, "%-10s%" PRIu64 " {%.*s}", which, keyno, (int)value.size, + (char *)value.data); + break; + } + + return (ret); } /* * row_reserve -- - * Reserve a row in a row-store file. + * Reserve a row in a row-store file. */ static int row_reserve(TINFO *tinfo, WT_CURSOR *cursor, bool positioned) { - WT_DECL_RET; + WT_DECL_RET; - if (!positioned) { - key_gen(tinfo->key, tinfo->keyno); - cursor->set_key(cursor, tinfo->key); - } + if (!positioned) { + key_gen(tinfo->key, tinfo->keyno); + cursor->set_key(cursor, tinfo->key); + } - if ((ret = cursor->reserve(cursor)) != 0) - return (ret); + if ((ret = cursor->reserve(cursor)) != 0) + return (ret); - logop(cursor->session, - "%-10s%" PRIu64 " {%.*s}", "reserve", - tinfo->keyno, (int)tinfo->key->size, (char *)tinfo->key->data); + logop(cursor->session, "%-10s%" PRIu64 " {%.*s}", "reserve", tinfo->keyno, + (int)tinfo->key->size, (char *)tinfo->key->data); - return (0); + return (0); } /* * col_reserve -- - * Reserve a row in a column-store file. + * Reserve a row in a column-store file. */ static int col_reserve(TINFO *tinfo, WT_CURSOR *cursor, bool positioned) { - WT_DECL_RET; + WT_DECL_RET; - if (!positioned) - cursor->set_key(cursor, tinfo->keyno); + if (!positioned) + cursor->set_key(cursor, tinfo->keyno); - if ((ret = cursor->reserve(cursor)) != 0) - return (ret); + if ((ret = cursor->reserve(cursor)) != 0) + return (ret); - logop(cursor->session, "%-10s%" PRIu64, "reserve", tinfo->keyno); + logop(cursor->session, "%-10s%" PRIu64, "reserve", tinfo->keyno); - return (0); + return (0); } /* * modify_build -- - * Generate a set of modify vectors. + * Generate a set of modify vectors. */ static void modify_build(TINFO *tinfo, WT_MODIFY *entries, int *nentriesp) { - int i, nentries; - - /* Randomly select a number of byte changes, offsets and lengths. */ - nentries = (int)mmrand(&tinfo->rnd, 1, MAX_MODIFY_ENTRIES); - for (i = 0; i < nentries; ++i) { - entries[i].data.data = modify_repl + - mmrand(&tinfo->rnd, 1, sizeof(modify_repl) - 10); - entries[i].data.size = (size_t)mmrand(&tinfo->rnd, 0, 10); - /* - * Start at least 11 bytes into the buffer so we skip leading - * key information. - */ - entries[i].offset = (size_t)mmrand(&tinfo->rnd, 20, 40); - entries[i].size = (size_t)mmrand(&tinfo->rnd, 0, 10); - } - - *nentriesp = (int)nentries; + int i, nentries; + + /* Randomly select a number of byte changes, offsets and lengths. */ + nentries = (int)mmrand(&tinfo->rnd, 1, MAX_MODIFY_ENTRIES); + for (i = 0; i < nentries; ++i) { + entries[i].data.data = modify_repl + mmrand(&tinfo->rnd, 1, sizeof(modify_repl) - 10); + entries[i].data.size = (size_t)mmrand(&tinfo->rnd, 0, 10); + /* + * Start at least 11 bytes into the buffer so we skip leading key information. + */ + entries[i].offset = (size_t)mmrand(&tinfo->rnd, 20, 40); + entries[i].size = (size_t)mmrand(&tinfo->rnd, 0, 10); + } + + *nentriesp = (int)nentries; } /* * row_modify -- - * Modify a row in a row-store file. + * Modify a row in a row-store file. */ static int row_modify(TINFO *tinfo, WT_CURSOR *cursor, bool positioned) { - WT_DECL_RET; - WT_MODIFY entries[MAX_MODIFY_ENTRIES]; - int nentries; + WT_DECL_RET; + WT_MODIFY entries[MAX_MODIFY_ENTRIES]; + int nentries; - if (!positioned) { - key_gen(tinfo->key, tinfo->keyno); - cursor->set_key(cursor, tinfo->key); - } + if (!positioned) { + key_gen(tinfo->key, tinfo->keyno); + cursor->set_key(cursor, tinfo->key); + } - modify_build(tinfo, entries, &nentries); - if ((ret = cursor->modify(cursor, entries, nentries)) != 0) - return (ret); + modify_build(tinfo, entries, &nentries); + if ((ret = cursor->modify(cursor, entries, nentries)) != 0) + return (ret); - testutil_check(cursor->get_value(cursor, tinfo->value)); + testutil_check(cursor->get_value(cursor, tinfo->value)); - logop(cursor->session, "%-10s%" PRIu64 " {%.*s}, {%.*s}", "modify", - tinfo->keyno, - (int)tinfo->key->size, (char *)tinfo->key->data, - (int)tinfo->value->size, (char *)tinfo->value->data); + logop(cursor->session, "%-10s%" PRIu64 " {%.*s}, {%.*s}", "modify", tinfo->keyno, + (int)tinfo->key->size, (char *)tinfo->key->data, (int)tinfo->value->size, + (char *)tinfo->value->data); - return (0); + return (0); } /* * col_modify -- - * Modify a row in a column-store file. + * Modify a row in a column-store file. */ static int col_modify(TINFO *tinfo, WT_CURSOR *cursor, bool positioned) { - WT_DECL_RET; - WT_MODIFY entries[MAX_MODIFY_ENTRIES]; - int nentries; + WT_DECL_RET; + WT_MODIFY entries[MAX_MODIFY_ENTRIES]; + int nentries; - if (!positioned) - cursor->set_key(cursor, tinfo->keyno); + if (!positioned) + cursor->set_key(cursor, tinfo->keyno); - modify_build(tinfo, entries, &nentries); - if ((ret = cursor->modify(cursor, entries, nentries)) != 0) - return (ret); + modify_build(tinfo, entries, &nentries); + if ((ret = cursor->modify(cursor, entries, nentries)) != 0) + return (ret); - testutil_check(cursor->get_value(cursor, tinfo->value)); + testutil_check(cursor->get_value(cursor, tinfo->value)); - logop(cursor->session, "%-10s%" PRIu64 ", {%.*s}", "modify", - tinfo->keyno, (int)tinfo->value->size, (char *)tinfo->value->data); + logop(cursor->session, "%-10s%" PRIu64 ", {%.*s}", "modify", tinfo->keyno, + (int)tinfo->value->size, (char *)tinfo->value->data); - return (0); + return (0); } /* * row_truncate -- - * Truncate rows in a row-store file. + * Truncate rows in a row-store file. */ static int row_truncate(TINFO *tinfo, WT_CURSOR *cursor) { - WT_CURSOR *c2; - WT_DECL_RET; - WT_SESSION *session; - - session = cursor->session; - - /* - * The code assumes we're never truncating the entire object, assert - * that fact. - */ - testutil_assert(tinfo->keyno != 0 || tinfo->last != 0); - - c2 = NULL; - if (tinfo->keyno == 0) { - key_gen(tinfo->key, tinfo->last); - cursor->set_key(cursor, tinfo->key); - ret = session->truncate(session, NULL, NULL, cursor, NULL); - } else if (tinfo->last == 0) { - key_gen(tinfo->key, tinfo->keyno); - cursor->set_key(cursor, tinfo->key); - ret = session->truncate(session, NULL, cursor, NULL, NULL); - } else { - key_gen(tinfo->key, tinfo->keyno); - cursor->set_key(cursor, tinfo->key); - - testutil_check( - session->open_cursor(session, g.uri, NULL, NULL, &c2)); - key_gen(tinfo->lastkey, tinfo->last); - cursor->set_key(c2, tinfo->lastkey); - - ret = session->truncate(session, NULL, cursor, c2, NULL); - testutil_check(c2->close(c2)); - } - - if (ret != 0) - return (ret); - - logop(session, "%-10s%" PRIu64 ", %" PRIu64, - "truncate", tinfo->keyno, tinfo->last); - - return (0); + WT_CURSOR *c2; + WT_DECL_RET; + WT_SESSION *session; + + session = cursor->session; + + /* + * The code assumes we're never truncating the entire object, assert that fact. + */ + testutil_assert(tinfo->keyno != 0 || tinfo->last != 0); + + c2 = NULL; + if (tinfo->keyno == 0) { + key_gen(tinfo->key, tinfo->last); + cursor->set_key(cursor, tinfo->key); + ret = session->truncate(session, NULL, NULL, cursor, NULL); + } else if (tinfo->last == 0) { + key_gen(tinfo->key, tinfo->keyno); + cursor->set_key(cursor, tinfo->key); + ret = session->truncate(session, NULL, cursor, NULL, NULL); + } else { + key_gen(tinfo->key, tinfo->keyno); + cursor->set_key(cursor, tinfo->key); + + testutil_check(session->open_cursor(session, g.uri, NULL, NULL, &c2)); + key_gen(tinfo->lastkey, tinfo->last); + cursor->set_key(c2, tinfo->lastkey); + + ret = session->truncate(session, NULL, cursor, c2, NULL); + testutil_check(c2->close(c2)); + } + + if (ret != 0) + return (ret); + + logop(session, "%-10s%" PRIu64 ", %" PRIu64, "truncate", tinfo->keyno, tinfo->last); + + return (0); } /* * col_truncate -- - * Truncate rows in a column-store file. + * Truncate rows in a column-store file. */ static int col_truncate(TINFO *tinfo, WT_CURSOR *cursor) { - WT_CURSOR *c2; - WT_DECL_RET; - WT_SESSION *session; - - session = cursor->session; - - /* - * The code assumes we're never truncating the entire object, assert - * that fact. - */ - testutil_assert(tinfo->keyno != 0 || tinfo->last != 0); - - c2 = NULL; - if (tinfo->keyno == 0) { - cursor->set_key(cursor, tinfo->last); - ret = session->truncate(session, NULL, NULL, cursor, NULL); - } else if (tinfo->last == 0) { - cursor->set_key(cursor, tinfo->keyno); - ret = session->truncate(session, NULL, cursor, NULL, NULL); - } else { - cursor->set_key(cursor, tinfo->keyno); - - testutil_check( - session->open_cursor(session, g.uri, NULL, NULL, &c2)); - cursor->set_key(c2, tinfo->last); - - ret = session->truncate(session, NULL, cursor, c2, NULL); - testutil_check(c2->close(c2)); - } - if (ret != 0) - return (ret); - - logop(session, - "%-10s%" PRIu64 "-%" PRIu64, "truncate", tinfo->keyno, tinfo->last); - - return (0); + WT_CURSOR *c2; + WT_DECL_RET; + WT_SESSION *session; + + session = cursor->session; + + /* + * The code assumes we're never truncating the entire object, assert that fact. + */ + testutil_assert(tinfo->keyno != 0 || tinfo->last != 0); + + c2 = NULL; + if (tinfo->keyno == 0) { + cursor->set_key(cursor, tinfo->last); + ret = session->truncate(session, NULL, NULL, cursor, NULL); + } else if (tinfo->last == 0) { + cursor->set_key(cursor, tinfo->keyno); + ret = session->truncate(session, NULL, cursor, NULL, NULL); + } else { + cursor->set_key(cursor, tinfo->keyno); + + testutil_check(session->open_cursor(session, g.uri, NULL, NULL, &c2)); + cursor->set_key(c2, tinfo->last); + + ret = session->truncate(session, NULL, cursor, c2, NULL); + testutil_check(c2->close(c2)); + } + if (ret != 0) + return (ret); + + logop(session, "%-10s%" PRIu64 "-%" PRIu64, "truncate", tinfo->keyno, tinfo->last); + + return (0); } /* * row_update -- - * Update a row in a row-store file. + * Update a row in a row-store file. */ static int row_update(TINFO *tinfo, WT_CURSOR *cursor, bool positioned) { - WT_DECL_RET; + WT_DECL_RET; - if (!positioned) { - key_gen(tinfo->key, tinfo->keyno); - cursor->set_key(cursor, tinfo->key); - } - val_gen(&tinfo->rnd, tinfo->value, tinfo->keyno); - cursor->set_value(cursor, tinfo->value); + if (!positioned) { + key_gen(tinfo->key, tinfo->keyno); + cursor->set_key(cursor, tinfo->key); + } + val_gen(&tinfo->rnd, tinfo->value, tinfo->keyno); + cursor->set_value(cursor, tinfo->value); - if ((ret = cursor->update(cursor)) != 0) - return (ret); + if ((ret = cursor->update(cursor)) != 0) + return (ret); - logop(cursor->session, "%-10s%" PRIu64 " {%.*s}, {%.*s}", "update", - tinfo->keyno, - (int)tinfo->key->size, (char *)tinfo->key->data, - (int)tinfo->value->size, (char *)tinfo->value->data); + logop(cursor->session, "%-10s%" PRIu64 " {%.*s}, {%.*s}", "update", tinfo->keyno, + (int)tinfo->key->size, (char *)tinfo->key->data, (int)tinfo->value->size, + (char *)tinfo->value->data); - return (0); + return (0); } /* * col_update -- - * Update a row in a column-store file. + * Update a row in a column-store file. */ static int col_update(TINFO *tinfo, WT_CURSOR *cursor, bool positioned) { - WT_DECL_RET; - - if (!positioned) - cursor->set_key(cursor, tinfo->keyno); - val_gen(&tinfo->rnd, tinfo->value, tinfo->keyno); - if (g.type == FIX) - cursor->set_value(cursor, *(uint8_t *)tinfo->value->data); - else - cursor->set_value(cursor, tinfo->value); - - if ((ret = cursor->update(cursor)) != 0) - return (ret); - - if (g.type == FIX) - logop(cursor->session, "%-10s%" PRIu64 " {0x%02" PRIx8 "}", - "update", tinfo->keyno, ((uint8_t *)tinfo->value->data)[0]); - else - logop(cursor->session, "%-10s%" PRIu64 " {%.*s}", - "update", tinfo->keyno, - (int)tinfo->value->size, (char *)tinfo->value->data); - - return (0); + WT_DECL_RET; + + if (!positioned) + cursor->set_key(cursor, tinfo->keyno); + val_gen(&tinfo->rnd, tinfo->value, tinfo->keyno); + if (g.type == FIX) + cursor->set_value(cursor, *(uint8_t *)tinfo->value->data); + else + cursor->set_value(cursor, tinfo->value); + + if ((ret = cursor->update(cursor)) != 0) + return (ret); + + if (g.type == FIX) + logop(cursor->session, "%-10s%" PRIu64 " {0x%02" PRIx8 "}", "update", tinfo->keyno, + ((uint8_t *)tinfo->value->data)[0]); + else + logop(cursor->session, "%-10s%" PRIu64 " {%.*s}", "update", tinfo->keyno, + (int)tinfo->value->size, (char *)tinfo->value->data); + + return (0); } /* * table_append_init -- - * Re-initialize the appended records list. + * Re-initialize the appended records list. */ static void table_append_init(void) { - /* Append up to 10 records per thread before waiting on resolution. */ - g.append_max = (size_t)g.c_threads * 10; - g.append_cnt = 0; + /* Append up to 10 records per thread before waiting on resolution. */ + g.append_max = (size_t)g.c_threads * 10; + g.append_cnt = 0; - free(g.append); - g.append = dcalloc(g.append_max, sizeof(uint64_t)); + free(g.append); + g.append = dcalloc(g.append_max, sizeof(uint64_t)); } /* * table_append -- - * Resolve the appended records. + * Resolve the appended records. */ static void table_append(uint64_t keyno) { - uint64_t *ep, *p; - int done; - - ep = g.append + g.append_max; - - /* - * We don't want to ignore records we append, which requires we update - * the "last row" as we insert new records. Threads allocating record - * numbers can race with other threads, so the thread allocating record - * N may return after the thread allocating N + 1. We can't update a - * record before it's been inserted, and so we can't leave gaps when the - * count of records in the table is incremented. - * - * The solution is the append table, which contains an unsorted list of - * appended records. Every time we finish appending a record, process - * the table, trying to update the total records in the object. - * - * First, enter the new key into the append list. - * - * It's technically possible to race: we allocated space for 10 records - * per thread, but the check for the maximum number of records being - * appended doesn't lock. If a thread allocated a new record and went - * to sleep (so the append table fills up), then N threads of control - * used the same g.append_cnt value to decide there was an available - * slot in the append table and both allocated new records, we could run - * out of space in the table. It's unfortunately not even unlikely in - * the case of a large number of threads all inserting as fast as they - * can and a single thread going to sleep for an unexpectedly long time. - * If it happens, sleep and retry until earlier records are resolved - * and we find a slot. - */ - for (done = 0;;) { - testutil_check(pthread_rwlock_wrlock(&g.append_lock)); - - /* - * If this is the thread we've been waiting for, and its record - * won't fit, we'd loop infinitely. If there are many append - * operations and a thread goes to sleep for a little too long, - * it can happen. - */ - if (keyno == g.rows + 1) { - g.rows = keyno; - done = 1; - - /* - * Clean out the table, incrementing the total count of - * records until we don't find the next key. - */ - for (;;) { - for (p = g.append; p < ep; ++p) - if (*p == g.rows + 1) { - g.rows = *p; - *p = 0; - --g.append_cnt; - break; - } - if (p == ep) - break; - } - } else - /* Enter the key into the table. */ - for (p = g.append; p < ep; ++p) - if (*p == 0) { - *p = keyno; - ++g.append_cnt; - done = 1; - break; - } - - testutil_check(pthread_rwlock_unlock(&g.append_lock)); - - if (done) - break; - __wt_sleep(1, 0); - } + uint64_t *ep, *p; + int done; + + ep = g.append + g.append_max; + + /* + * We don't want to ignore records we append, which requires we update + * the "last row" as we insert new records. Threads allocating record + * numbers can race with other threads, so the thread allocating record + * N may return after the thread allocating N + 1. We can't update a + * record before it's been inserted, and so we can't leave gaps when the + * count of records in the table is incremented. + * + * The solution is the append table, which contains an unsorted list of + * appended records. Every time we finish appending a record, process + * the table, trying to update the total records in the object. + * + * First, enter the new key into the append list. + * + * It's technically possible to race: we allocated space for 10 records + * per thread, but the check for the maximum number of records being + * appended doesn't lock. If a thread allocated a new record and went + * to sleep (so the append table fills up), then N threads of control + * used the same g.append_cnt value to decide there was an available + * slot in the append table and both allocated new records, we could run + * out of space in the table. It's unfortunately not even unlikely in + * the case of a large number of threads all inserting as fast as they + * can and a single thread going to sleep for an unexpectedly long time. + * If it happens, sleep and retry until earlier records are resolved + * and we find a slot. + */ + for (done = 0;;) { + testutil_check(pthread_rwlock_wrlock(&g.append_lock)); + + /* + * If this is the thread we've been waiting for, and its record won't fit, we'd loop + * infinitely. If there are many append operations and a thread goes to sleep for a little + * too long, it can happen. + */ + if (keyno == g.rows + 1) { + g.rows = keyno; + done = 1; + + /* + * Clean out the table, incrementing the total count of records until we don't find the + * next key. + */ + for (;;) { + for (p = g.append; p < ep; ++p) + if (*p == g.rows + 1) { + g.rows = *p; + *p = 0; + --g.append_cnt; + break; + } + if (p == ep) + break; + } + } else + /* Enter the key into the table. */ + for (p = g.append; p < ep; ++p) + if (*p == 0) { + *p = keyno; + ++g.append_cnt; + done = 1; + break; + } + + testutil_check(pthread_rwlock_unlock(&g.append_lock)); + + if (done) + break; + __wt_sleep(1, 0); + } } /* * row_insert -- - * Insert a row in a row-store file. + * Insert a row in a row-store file. */ static int row_insert(TINFO *tinfo, WT_CURSOR *cursor, bool positioned) { - WT_DECL_RET; - - /* - * If we positioned the cursor already, it's a test of an update using - * the insert method. Otherwise, generate a unique key and insert. - */ - if (!positioned) { - key_gen_insert(&tinfo->rnd, tinfo->key, tinfo->keyno); - cursor->set_key(cursor, tinfo->key); - } - val_gen(&tinfo->rnd, tinfo->value, tinfo->keyno); - cursor->set_value(cursor, tinfo->value); - - if ((ret = cursor->insert(cursor)) != 0) - return (ret); - - /* Log the operation */ - logop(cursor->session, "%-10s%" PRIu64 " {%.*s}, {%.*s}", "insert", - tinfo->keyno, - (int)tinfo->key->size, (char *)tinfo->key->data, - (int)tinfo->value->size, (char *)tinfo->value->data); - - return (0); + WT_DECL_RET; + + /* + * If we positioned the cursor already, it's a test of an update using the insert method. + * Otherwise, generate a unique key and insert. + */ + if (!positioned) { + key_gen_insert(&tinfo->rnd, tinfo->key, tinfo->keyno); + cursor->set_key(cursor, tinfo->key); + } + val_gen(&tinfo->rnd, tinfo->value, tinfo->keyno); + cursor->set_value(cursor, tinfo->value); + + if ((ret = cursor->insert(cursor)) != 0) + return (ret); + + /* Log the operation */ + logop(cursor->session, "%-10s%" PRIu64 " {%.*s}, {%.*s}", "insert", tinfo->keyno, + (int)tinfo->key->size, (char *)tinfo->key->data, (int)tinfo->value->size, + (char *)tinfo->value->data); + + return (0); } /* * col_insert -- - * Insert an element in a column-store file. + * Insert an element in a column-store file. */ static int col_insert(TINFO *tinfo, WT_CURSOR *cursor) { - WT_DECL_RET; + WT_DECL_RET; - val_gen(&tinfo->rnd, tinfo->value, g.rows + 1); - if (g.type == FIX) - cursor->set_value(cursor, *(uint8_t *)tinfo->value->data); - else - cursor->set_value(cursor, tinfo->value); + val_gen(&tinfo->rnd, tinfo->value, g.rows + 1); + if (g.type == FIX) + cursor->set_value(cursor, *(uint8_t *)tinfo->value->data); + else + cursor->set_value(cursor, tinfo->value); - if ((ret = cursor->insert(cursor)) != 0) - return (ret); + if ((ret = cursor->insert(cursor)) != 0) + return (ret); - testutil_check(cursor->get_key(cursor, &tinfo->keyno)); + testutil_check(cursor->get_key(cursor, &tinfo->keyno)); - table_append(tinfo->keyno); /* Extend the object. */ + table_append(tinfo->keyno); /* Extend the object. */ - if (g.type == FIX) - logop(cursor->session, "%-10s%" PRIu64 " {0x%02" PRIx8 "}", - "insert", tinfo->keyno, ((uint8_t *)tinfo->value->data)[0]); - else - logop(cursor->session, "%-10s%" PRIu64 " {%.*s}", - "insert", tinfo->keyno, - (int)tinfo->value->size, (char *)tinfo->value->data); + if (g.type == FIX) + logop(cursor->session, "%-10s%" PRIu64 " {0x%02" PRIx8 "}", "insert", tinfo->keyno, + ((uint8_t *)tinfo->value->data)[0]); + else + logop(cursor->session, "%-10s%" PRIu64 " {%.*s}", "insert", tinfo->keyno, + (int)tinfo->value->size, (char *)tinfo->value->data); - return (0); + return (0); } /* * row_remove -- - * Remove an row from a row-store file. + * Remove an row from a row-store file. */ static int row_remove(TINFO *tinfo, WT_CURSOR *cursor, bool positioned) { - WT_DECL_RET; + WT_DECL_RET; - if (!positioned) { - key_gen(tinfo->key, tinfo->keyno); - cursor->set_key(cursor, tinfo->key); - } + if (!positioned) { + key_gen(tinfo->key, tinfo->keyno); + cursor->set_key(cursor, tinfo->key); + } - /* We use the cursor in overwrite mode, check for existence. */ - if ((ret = read_op(cursor, SEARCH, NULL)) == 0) - ret = cursor->remove(cursor); + /* We use the cursor in overwrite mode, check for existence. */ + if ((ret = read_op(cursor, SEARCH, NULL)) == 0) + ret = cursor->remove(cursor); - if (ret != 0 && ret != WT_NOTFOUND) - return (ret); + if (ret != 0 && ret != WT_NOTFOUND) + return (ret); - logop(cursor->session, "%-10s%" PRIu64, "remove", tinfo->keyno); + logop(cursor->session, "%-10s%" PRIu64, "remove", tinfo->keyno); - return (ret); + return (ret); } /* * col_remove -- - * Remove a row from a column-store file. + * Remove a row from a column-store file. */ static int col_remove(TINFO *tinfo, WT_CURSOR *cursor, bool positioned) { - WT_DECL_RET; + WT_DECL_RET; - if (!positioned) - cursor->set_key(cursor, tinfo->keyno); + if (!positioned) + cursor->set_key(cursor, tinfo->keyno); - /* We use the cursor in overwrite mode, check for existence. */ - if ((ret = read_op(cursor, SEARCH, NULL)) == 0) - ret = cursor->remove(cursor); + /* We use the cursor in overwrite mode, check for existence. */ + if ((ret = read_op(cursor, SEARCH, NULL)) == 0) + ret = cursor->remove(cursor); - if (ret != 0 && ret != WT_NOTFOUND) - return (ret); + if (ret != 0 && ret != WT_NOTFOUND) + return (ret); - logop(cursor->session, "%-10s%" PRIu64, "remove", tinfo->keyno); + logop(cursor->session, "%-10s%" PRIu64, "remove", tinfo->keyno); - return (ret); + return (ret); } |