/* * __wt_txn_get_snapshot -- * Set up a snapshot in the current transaction, without allocating an ID. */ void __wt_txn_get_snapshot( WT_SESSION_IMPL *session, wt_txnid_t my_id, wt_txnid_t max_id, int force) { WT_CONNECTION_IMPL *conn; WT_TXN *txn; WT_TXN_GLOBAL *txn_global; WT_TXN_STATE *s, *txn_state; wt_txnid_t current_id, id, oldest_snap_min; uint32_t i, n, session_cnt; conn = S2C(session); txn = &session->txn; txn_global = &conn->txn_global; txn_state = &txn_global->states[session->id]; /* If nothing has changed since last time, we're done. */ if (!force && txn->last_id == txn_global->current && txn->last_gen == txn_global->gen) { txn_state->snap_min = txn->snap_min; return; } do { /* Take a copy of the current session ID. */ txn->last_gen = txn->last_oldest_gen = txn_global->gen; txn->last_id = oldest_snap_min = current_id = txn_global->current; /* Copy the array of concurrent transactions. */ WT_ORDERED_READ(session_cnt, conn->session_cnt); for (i = n = 0, s = txn_global->states; i < session_cnt; i++, s++) { /* Ignore the session's own transaction. */ if (i == session->id) continue; if ((id = s->snap_min) != WT_TXN_NONE) if (TXNID_LT(id, oldest_snap_min)) oldest_snap_min = id; if ((id = s->id) == WT_TXN_NONE) continue; else if (max_id == WT_TXN_NONE || TXNID_LT(id, max_id)) txn->snapshot[n++] = id; } /* * Ensure the snapshot reads are scheduled before re-checking * the global current ID. */ WT_READ_BARRIER(); } while (current_id != txn_global->current); __txn_sort_snapshot(session, n, (max_id != WT_TXN_NONE) ? max_id : current_id, oldest_snap_min); txn_state->snap_min = (my_id == WT_TXN_NONE || TXNID_LT(txn->snap_min, my_id)) ? txn->snap_min : my_id; }
/* * __wt_txn_get_oldest -- * Update the current transaction's cached copy of the oldest snap_min * value. */ void __wt_txn_get_oldest(WT_SESSION_IMPL *session) { WT_CONNECTION_IMPL *conn; WT_TXN *txn; WT_TXN_GLOBAL *txn_global; WT_TXN_STATE *s; wt_txnid_t id, oldest_snap_min; uint32_t i, session_cnt; conn = S2C(session); txn = &session->txn; txn_global = &conn->txn_global; /* If nothing has changed since last time, we're done. */ if (txn->last_oldest_gen == txn_global->gen) return; txn->last_oldest_gen = txn_global->gen; oldest_snap_min = (txn->id != WT_TXN_NONE) ? txn->id : txn_global->current; WT_ORDERED_READ(session_cnt, conn->session_cnt); for (i = 0, s = txn_global->states; i < session_cnt; i++, s++) { if ((id = s->snap_min) != WT_TXN_NONE && TXNID_LT(id, oldest_snap_min)) oldest_snap_min = id; } txn->oldest_snap_min = oldest_snap_min; }
/* * __wt_txnid_cmp -- * Compare transaction IDs for sorting / searching. */ int __wt_txnid_cmp(const void *v1, const void *v2) { uint64_t id1, id2; id1 = *(uint64_t *)v1; id2 = *(uint64_t *)v2; return ((id1 == id2) ? 0 : TXNID_LT(id1, id2) ? -1 : 1); }
/* * __txn_sort_snapshot -- * Sort a snapshot for faster searching and set the min/max bounds. */ static void __txn_sort_snapshot(WT_SESSION_IMPL *session, uint32_t n, wt_txnid_t id, wt_txnid_t oldest_snap_min) { WT_TXN *txn; txn = &session->txn; if (n > 1) qsort(txn->snapshot, n, sizeof(wt_txnid_t), __wt_txnid_cmp); txn->snapshot_count = n; txn->snap_min = (n == 0) ? id : txn->snapshot[0]; txn->snap_max = id; WT_ASSERT(session, txn->snap_min != WT_TXN_NONE); txn->oldest_snap_min = TXNID_LT(oldest_snap_min, txn->snap_min) ? oldest_snap_min : txn->snap_min; }
/* * __wt_txn_refresh -- * Allocate a transaction ID and/or a snapshot. */ void __wt_txn_refresh(WT_SESSION_IMPL *session, uint64_t max_id, int get_snapshot) { WT_CONNECTION_IMPL *conn; WT_TXN *txn; WT_TXN_GLOBAL *txn_global; WT_TXN_STATE *s, *txn_state; uint64_t current_id, id, snap_min, oldest_id, prev_oldest_id; uint32_t i, n, session_cnt; int32_t count; conn = S2C(session); txn = &session->txn; txn_global = &conn->txn_global; txn_state = &txn_global->states[session->id]; prev_oldest_id = txn_global->oldest_id; current_id = snap_min = txn_global->current; /* For pure read-only workloads, avoid updates to shared state. */ if (!get_snapshot) { /* * If we are trying to update the oldest ID and it is already * equal to the current ID, there is no point scanning. */ if (prev_oldest_id == current_id) return; } else if (txn->id == max_id && txn->snapshot_count == 0 && txn->snap_min == snap_min && TXNID_LE(prev_oldest_id, snap_min)) { txn_state->snap_min = txn->snap_min; /* If nothing has changed in the meantime, we're done. */ if (txn_global->scan_count == 0 && txn_global->oldest_id == prev_oldest_id) return; } /* * We're going to scan. Increment the count of scanners to prevent the * oldest ID from moving forwards. Spin if the count is negative, * which indicates that some thread is moving the oldest ID forwards. */ do { if ((count = txn_global->scan_count) < 0) WT_PAUSE(); } while (count < 0 || !WT_ATOMIC_CAS(txn_global->scan_count, count, count + 1)); /* The oldest ID cannot change until the scan count goes to zero. */ prev_oldest_id = txn_global->oldest_id; current_id = snap_min = txn_global->current; /* If the maximum ID is constrained, so is the oldest. */ oldest_id = (max_id != WT_TXN_NONE) ? max_id : snap_min; /* Walk the array of concurrent transactions. */ WT_ORDERED_READ(session_cnt, conn->session_cnt); for (i = n = 0, s = txn_global->states; i < session_cnt; i++, s++) { /* * Ignore the ID if we are committing (indicated by max_id * being set): it is about to be released. * * Also ignore the ID if it is older than the oldest ID we saw. * This can happen if we race with a thread that is allocating * an ID -- the ID will not be used because the thread will * keep spinning until it gets a valid one. */ if ((id = s->id) != WT_TXN_NONE && id + 1 != max_id && TXNID_LE(prev_oldest_id, id)) { if (get_snapshot) txn->snapshot[n++] = id; if (TXNID_LT(id, snap_min)) snap_min = id; } /* * Ignore the session's own snap_min if we are in the process * of updating it. */ if (get_snapshot && s == txn_state) continue; /* * !!! * Note: Don't ignore snap_min values older than the previous * oldest ID. Read-uncommitted operations publish snap_min * values without incrementing scan_count to protect the global * table. See the comment in __wt_txn_cursor_op for * more details. */ if ((id = s->snap_min) != WT_TXN_NONE && TXNID_LT(id, oldest_id)) oldest_id = id; } if (TXNID_LT(snap_min, oldest_id)) oldest_id = snap_min; if (get_snapshot) { WT_ASSERT(session, TXNID_LE(prev_oldest_id, snap_min)); WT_ASSERT(session, prev_oldest_id == txn_global->oldest_id); txn_state->snap_min = snap_min; } /* * Update the last running ID if we have a much newer value or we are * forcing an update. */ if (!get_snapshot || snap_min > txn_global->last_running + 100) txn_global->last_running = snap_min; /* * Update the oldest ID if we have a newer ID and we can get exclusive * access. During normal snapshot refresh, only do this if we have a * much newer value. Once we get exclusive access, do another pass to * make sure nobody else is using an earlier ID. */ if (max_id == WT_TXN_NONE && TXNID_LT(prev_oldest_id, oldest_id) && (!get_snapshot || oldest_id - prev_oldest_id > 100) && WT_ATOMIC_CAS(txn_global->scan_count, 1, -1)) { WT_ORDERED_READ(session_cnt, conn->session_cnt); for (i = 0, s = txn_global->states; i < session_cnt; i++, s++) { if ((id = s->id) != WT_TXN_NONE && TXNID_LT(id, oldest_id)) oldest_id = id; if ((id = s->snap_min) != WT_TXN_NONE && TXNID_LT(id, oldest_id)) oldest_id = id; } if (TXNID_LT(txn_global->oldest_id, oldest_id)) txn_global->oldest_id = oldest_id; txn_global->scan_count = 0; } else { WT_ASSERT(session, txn_global->scan_count > 0); (void)WT_ATOMIC_SUB(txn_global->scan_count, 1); } if (get_snapshot) __txn_sort_snapshot(session, n, current_id); }
/* * __clsm_enter -- * Start an operation on an LSM cursor, update if the tree has changed. */ static inline int __clsm_enter(WT_CURSOR_LSM *clsm, int reset, int update) { WT_CURSOR *c; WT_DECL_RET; WT_LSM_CHUNK *chunk; WT_SESSION_IMPL *session; uint64_t *txnid_maxp; uint64_t id, myid, snap_min; session = (WT_SESSION_IMPL *)clsm->iface.session; /* Merge cursors never update. */ if (F_ISSET(clsm, WT_CLSM_MERGE)) return (0); if (reset) { c = &clsm->iface; /* Copy out data before resetting chunk cursors. */ if (F_ISSET(c, WT_CURSTD_KEY_INT) && !WT_DATA_IN_ITEM(&c->key)) WT_RET(__wt_buf_set( session, &c->key, c->key.data, c->key.size)); if (F_ISSET(c, WT_CURSTD_VALUE_INT) && !WT_DATA_IN_ITEM(&c->value)) WT_RET(__wt_buf_set( session, &c->value, c->value.data, c->value.size)); WT_RET(__clsm_reset_cursors(clsm, NULL)); } for (;;) { /* * If the cursor looks up-to-date, check if the cache is full. * In case this call blocks, the check will be repeated before * proceeding. */ if (clsm->dsk_gen != clsm->lsm_tree->dsk_gen) goto open; WT_RET(__wt_cache_full_check(session)); if (clsm->dsk_gen != clsm->lsm_tree->dsk_gen) goto open; /* Update the maximum transaction ID in the primary chunk. */ if (update && (chunk = clsm->primary_chunk) != NULL) { WT_RET(__wt_txn_autocommit_check(session)); for (id = chunk->txnid_max, myid = session->txn.id; !TXNID_LE(myid, id); id = chunk->txnid_max) { WT_ASSERT(session, myid != WT_TXN_NONE); (void)WT_ATOMIC_CAS( chunk->txnid_max, id, myid); } } /* * Figure out how many updates are required for snapshot * isolation. * * This is not a normal visibility check on the maximum * transaction ID in each chunk: any transaction ID that * overlaps with our snapshot is a potential conflict. */ clsm->nupdates = 1; if (session->txn.isolation == TXN_ISO_SNAPSHOT && F_ISSET(clsm, WT_CLSM_OPEN_SNAPSHOT)) { snap_min = session->txn.snap_min; for (txnid_maxp = &clsm->txnid_max[clsm->nchunks - 2]; clsm->nupdates < clsm->nchunks; clsm->nupdates++, txnid_maxp--) if (TXNID_LT(*txnid_maxp, snap_min)) break; } /* * Stop when we are up-to-date, as long as this is: * - a snapshot isolation update and the cursor is set up for * that; * - an update operation with a primary chunk, or * - a read operation and the cursor is open for reading. */ if ((!update || session->txn.isolation != TXN_ISO_SNAPSHOT || F_ISSET(clsm, WT_CLSM_OPEN_SNAPSHOT)) && ((update && clsm->primary_chunk != NULL) || (!update && F_ISSET(clsm, WT_CLSM_OPEN_READ)))) break; open: WT_WITH_SCHEMA_LOCK(session, ret = __clsm_open_cursors(clsm, update, 0, 0)); WT_RET(ret); } if (!F_ISSET(clsm, WT_CLSM_ACTIVE)) { WT_RET(__cursor_enter(session)); F_SET(clsm, WT_CLSM_ACTIVE); } return (0); }
/* * __wt_txn_begin -- * Begin a transaction. */ int __wt_txn_begin(WT_SESSION_IMPL *session, const char *cfg[]) { WT_CONFIG_ITEM cval; WT_CONNECTION_IMPL *conn; WT_TXN *txn; WT_TXN_GLOBAL *txn_global; WT_TXN_STATE *s, *txn_state; wt_txnid_t id, oldest_snap_min; uint32_t i, n, session_cnt; conn = S2C(session); txn = &session->txn; txn_global = &conn->txn_global; txn_state = &txn_global->states[session->id]; WT_ASSERT(session, txn_state->id == WT_TXN_NONE); WT_RET(__wt_config_gets_defno(session, cfg, "isolation", &cval)); if (cval.len == 0) txn->isolation = session->isolation; else txn->isolation = WT_STRING_MATCH("snapshot", cval.str, cval.len) ? TXN_ISO_SNAPSHOT : WT_STRING_MATCH("read-committed", cval.str, cval.len) ? TXN_ISO_READ_COMMITTED : TXN_ISO_READ_UNCOMMITTED; F_SET(txn, TXN_RUNNING); do { /* * Allocate a transaction ID. * * We use an atomic increment to ensure that we get a unique * ID, then publish that to the global state table. * * If two threads race to allocate an ID, only the latest ID * will proceed. The winning thread can be sure its snapshot * contains all of the earlier active IDs. Threads that race * and get an earlier ID may not appear in the snapshot, * but they will loop and allocate a new ID before proceeding * to make any updates. * * This potentially wastes transaction IDs when threads race to * begin transactions, but that is the price we pay to keep * this path latch free. */ do { txn->id = WT_ATOMIC_ADD(txn_global->current, 1); } while (txn->id == WT_TXN_NONE || txn->id == WT_TXN_ABORTED); WT_PUBLISH(txn_state->id, txn->id); /* * If we are starting a snapshot isolation transaction, get * a snapshot of the running transactions. * * If we already have a snapshot (e.g., for an auto-commit * operation), update it so that the newly-allocated ID is * visible. */ if (txn->isolation == TXN_ISO_SNAPSHOT) { txn->last_gen = txn->last_oldest_gen = txn_global->gen; oldest_snap_min = txn->id; /* Copy the array of concurrent transactions. */ WT_ORDERED_READ(session_cnt, conn->session_cnt); for (i = n = 0, s = txn_global->states; i < session_cnt; i++, s++) { if ((id = s->snap_min) != WT_TXN_NONE) if (TXNID_LT(id, oldest_snap_min)) oldest_snap_min = id; if ((id = s->id) == WT_TXN_NONE) continue; else txn->snapshot[n++] = id; } __txn_sort_snapshot( session, n, txn->id, oldest_snap_min); txn_state->snap_min = txn->snap_min; } /* * Ensure the snapshot reads are complete before re-checking * the global current ID. */ WT_READ_BARRIER(); } while (txn->id != txn_global->current); return (0); }