diff options
author | Luke Chen <luke.chen@mongodb.com> | 2022-02-22 13:40:17 +1100 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-02-22 03:06:00 +0000 |
commit | be4b2f2a05d31df77950d97800299e367e5fcf95 (patch) | |
tree | d97d2864b19854acea0d6b048ca5242af9d8a49e | |
parent | 0a8923221dd39a1a35c4ec2608d47849bbd87549 (diff) | |
download | mongo-be4b2f2a05d31df77950d97800299e367e5fcf95.tar.gz |
Import wiredtiger: 966d116414ba7ffcd86afb71cfc57641eb25aed3 from branch mongodb-5.3
ref: 6ccd5b7d25..966d116414
for: 5.3.0-rc1
WT-8753 Add tombstone when rolling back in-memory, prepared, reconciled updates
-rw-r--r-- | src/third_party/wiredtiger/import.data | 2 | ||||
-rw-r--r-- | src/third_party/wiredtiger/src/txn/txn.c | 73 | ||||
-rw-r--r-- | src/third_party/wiredtiger/test/suite/test_prepare19.py | 91 |
3 files changed, 145 insertions, 21 deletions
diff --git a/src/third_party/wiredtiger/import.data b/src/third_party/wiredtiger/import.data index e3876c41bfb..74fee8e842c 100644 --- a/src/third_party/wiredtiger/import.data +++ b/src/third_party/wiredtiger/import.data @@ -2,5 +2,5 @@ "vendor": "wiredtiger", "github": "wiredtiger/wiredtiger.git", "branch": "mongodb-5.3", - "commit": "6ccd5b7d25b76fcb4f04c014845e9815f4349fc0" + "commit": "966d116414ba7ffcd86afb71cfc57641eb25aed3" } diff --git a/src/third_party/wiredtiger/src/txn/txn.c b/src/third_party/wiredtiger/src/txn/txn.c index 99aaf0c9021..babf6a4bd51 100644 --- a/src/third_party/wiredtiger/src/txn/txn.c +++ b/src/third_party/wiredtiger/src/txn/txn.c @@ -1134,6 +1134,40 @@ __txn_search_prepared_op( } /* + * __txn_append_tombstone -- + * Append a tombstone to the end of a keys update chain. + */ +static int +__txn_append_tombstone(WT_SESSION_IMPL *session, WT_TXN_OP *op, WT_CURSOR_BTREE *cbt) +{ + WT_BTREE *btree; + WT_DECL_RET; + WT_UPDATE *tombstone; + size_t not_used; + tombstone = NULL; + btree = S2BT(session); + + WT_ERR(__wt_upd_alloc_tombstone(session, &tombstone, ¬_used)); +#ifdef HAVE_DIAGNOSTIC + WT_WITH_BTREE(session, op->btree, + ret = btree->type == BTREE_ROW ? + __wt_row_modify(cbt, &cbt->iface.key, NULL, tombstone, WT_UPDATE_INVALID, false, false) : + __wt_col_modify(cbt, cbt->recno, NULL, tombstone, WT_UPDATE_INVALID, false, false)); +#else + WT_WITH_BTREE(session, op->btree, + ret = btree->type == BTREE_ROW ? + __wt_row_modify(cbt, &cbt->iface.key, NULL, tombstone, WT_UPDATE_INVALID, false) : + __wt_col_modify(cbt, cbt->recno, NULL, tombstone, WT_UPDATE_INVALID, false)); +#endif + WT_ERR(ret); + tombstone = NULL; + +err: + __wt_free(session, tombstone); + return (ret); +} + +/* * __txn_resolve_prepared_op -- * Resolve a transaction's operations indirect references. */ @@ -1146,19 +1180,19 @@ __txn_resolve_prepared_op(WT_SESSION_IMPL *session, WT_TXN_OP *op, bool commit, WT_DECL_RET; WT_ITEM hs_recno_key; WT_PAGE *page; + WT_TIME_WINDOW tw; WT_TXN *txn; - WT_UPDATE *first_committed_upd, *fix_upd, *tombstone, *upd; + WT_UPDATE *first_committed_upd, *fix_upd, *upd; #ifdef HAVE_DIAGNOSTIC WT_UPDATE *head_upd; #endif - size_t not_used; uint8_t *p, hs_recno_key_buf[WT_INTPACK64_MAXSIZE]; char ts_string[3][WT_TS_INT_STRING_SIZE]; - bool first_committed_upd_in_hs, prepare_on_disk, upd_appended; + bool first_committed_upd_in_hs, prepare_on_disk, tw_found, upd_appended; hs_cursor = NULL; txn = session->txn; - fix_upd = tombstone = NULL; + fix_upd = NULL; upd_appended = false; WT_RET(__txn_search_prepared_op(session, op, cursorp, &upd)); @@ -1261,26 +1295,26 @@ __txn_resolve_prepared_op(WT_SESSION_IMPL *session, WT_TXN_OP *op, bool commit, * we don't copy the prepared cell, which is now associated with a rolled back prepare, * and instead write nothing. */ - WT_ERR(__wt_upd_alloc_tombstone(session, &tombstone, ¬_used)); -#ifdef HAVE_DIAGNOSTIC - WT_WITH_BTREE(session, op->btree, - ret = btree->type == BTREE_ROW ? - __wt_row_modify( - cbt, &cbt->iface.key, NULL, tombstone, WT_UPDATE_INVALID, false, false) : - __wt_col_modify(cbt, cbt->recno, NULL, tombstone, WT_UPDATE_INVALID, false, false)); -#else - WT_WITH_BTREE(session, op->btree, - ret = btree->type == BTREE_ROW ? - __wt_row_modify(cbt, &cbt->iface.key, NULL, tombstone, WT_UPDATE_INVALID, false) : - __wt_col_modify(cbt, cbt->recno, NULL, tombstone, WT_UPDATE_INVALID, false)); -#endif - WT_ERR(ret); - tombstone = NULL; + WT_ERR(__txn_append_tombstone(session, op, cbt)); } else if (ret == 0) WT_ERR(__txn_locate_hs_record( session, hs_cursor, page, upd, commit, &fix_upd, &upd_appended, first_committed_upd)); else ret = 0; + } else if (F_ISSET(S2C(session), WT_CONN_IN_MEMORY) && !commit && first_committed_upd == NULL) { + /* + * For in-memory configurations of WiredTiger if a prepared update is reconciled and then + * rolled back the on-page value will not be marked as aborted until the next eviction. In + * the special case where this rollback results in the update chain being entirely comprised + * of aborted updates other transactions attempting to write to the same key will look at + * the on-page value, think the prepared transaction is still active, and falsely report a + * write conflict. To prevent this scenario append a tombstone to the update chain when + * rolling back a prepared reconciled update would result in only aborted updates on the + * update chain. + */ + tw_found = __wt_read_cell_time_window(cbt, &tw); + if (tw_found && tw.prepare == WT_PREPARE_INPROGRESS) + WT_ERR(__txn_append_tombstone(session, op, cbt)); } for (; upd != NULL; upd = upd->next) { @@ -1384,7 +1418,6 @@ err: WT_TRET(hs_cursor->close(hs_cursor)); if (!upd_appended) __wt_free(session, fix_upd); - __wt_free(session, tombstone); return (ret); } diff --git a/src/third_party/wiredtiger/test/suite/test_prepare19.py b/src/third_party/wiredtiger/test/suite/test_prepare19.py new file mode 100644 index 00000000000..36bebcafbfe --- /dev/null +++ b/src/third_party/wiredtiger/test/suite/test_prepare19.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# +# Public Domain 2014-present MongoDB, Inc. +# Public Domain 2008-2014 WiredTiger, Inc. +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + + +import wttest +import wiredtiger + +# test_prepare19.py +# Test that for in-memory configurations of WiredTiger if rolling back a prepared, reconciled +# update results in an empty update chain then a tombstone is appended to the chain +class test_prepare19(wttest.WiredTigerTestCase): + + + def conn_config(self): + return 'in_memory=true' + + + def test_server_example(self): + uri = 'table:test_prepare19' + config = 'key_format=i,value_format=S' + + self.session.create(uri, config) + + # Place more than 1000 aborted updates on the update chain. + for i in range(1, 1100): + self.session.begin_transaction() + cursor = self.session.open_cursor(uri, None) + cursor[1] = "" + self.session.rollback_transaction() + + # Make a prepared update on key 1, force eviction, and rollback. + self.prepare_evict_rollback(uri, config, 1101) + + # If no tombstone is written the update will be aborted in the update chain but not in the btree. + # The transaction will see an active transaction on key 1 and raise a write conflict. + # Expect no error is raised. + self.session.begin_transaction() + cursor = self.session.open_cursor(uri, None) + cursor[1] = "" + + + def prepare_evict_rollback(self, uri, config, timestamp): + self.session.begin_transaction() + cursor = self.session.open_cursor(uri, None) + cursor[1] = "" + self.session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(timestamp)) + + # This write conflict on the same page as key 1 results in a forced + # eviction when the key has more than 1000 updates in its update chain. + write_conflict_session = self.conn.open_session() + write_conflict_session.create(uri, config) + write_conflict_session.begin_transaction() + write_conflict_cursor = write_conflict_session.open_cursor(uri, None) + try: + write_conflict_cursor[1] = "", + raise Exception + except wiredtiger.WiredTigerError: + pass + write_conflict_session.rollback_transaction() + write_conflict_session.close() + + self.session.rollback_transaction() + + +if __name__ == '__main__': + wttest.run() |