summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Chen <luke.chen@mongodb.com>2022-02-22 13:40:17 +1100
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-02-22 03:06:00 +0000
commitbe4b2f2a05d31df77950d97800299e367e5fcf95 (patch)
treed97d2864b19854acea0d6b048ca5242af9d8a49e
parent0a8923221dd39a1a35c4ec2608d47849bbd87549 (diff)
downloadmongo-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.data2
-rw-r--r--src/third_party/wiredtiger/src/txn/txn.c73
-rw-r--r--src/third_party/wiredtiger/test/suite/test_prepare19.py91
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, &not_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, &not_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()