summaryrefslogtreecommitdiff
path: root/src/third_party/wiredtiger/test/suite
diff options
context:
space:
mode:
Diffstat (limited to 'src/third_party/wiredtiger/test/suite')
-rw-r--r--src/third_party/wiredtiger/test/suite/test_backup29.py206
-rw-r--r--src/third_party/wiredtiger/test/suite/test_bug029.py112
-rw-r--r--src/third_party/wiredtiger/test/suite/test_bug030.py125
-rwxr-xr-xsrc/third_party/wiredtiger/test/suite/test_checkpoint04.py5
-rw-r--r--src/third_party/wiredtiger/test/suite/test_checkpoint_snapshot02.py33
-rw-r--r--src/third_party/wiredtiger/test/suite/test_checkpoint_snapshot05.py11
-rwxr-xr-xsrc/third_party/wiredtiger/test/suite/test_hs11.py80
-rwxr-xr-xsrc/third_party/wiredtiger/test/suite/test_hs20.py7
-rwxr-xr-xsrc/third_party/wiredtiger/test/suite/test_hs32.py160
-rw-r--r--src/third_party/wiredtiger/test/suite/test_prepare20.py16
-rw-r--r--src/third_party/wiredtiger/test/suite/test_prepare21.py135
-rw-r--r--src/third_party/wiredtiger/test/suite/test_prepare22.py137
-rw-r--r--src/third_party/wiredtiger/test/suite/test_prepare23.py131
-rw-r--r--src/third_party/wiredtiger/test/suite/test_prepare24.py126
-rw-r--r--src/third_party/wiredtiger/test/suite/test_prepare25.py137
-rw-r--r--src/third_party/wiredtiger/test/suite/test_prepare26.py134
-rw-r--r--src/third_party/wiredtiger/test/suite/test_rollback_to_stable39.py172
-rwxr-xr-xsrc/third_party/wiredtiger/test/suite/test_rollback_to_stable40.py164
-rw-r--r--src/third_party/wiredtiger/test/suite/test_stat08.py49
-rwxr-xr-xsrc/third_party/wiredtiger/test/suite/test_sweep04.py252
-rw-r--r--src/third_party/wiredtiger/test/suite/test_tiered05.py3
-rwxr-xr-xsrc/third_party/wiredtiger/test/suite/test_tiered06.py14
-rw-r--r--src/third_party/wiredtiger/test/suite/test_tiered14.py6
-rwxr-xr-xsrc/third_party/wiredtiger/test/suite/wttest.py68
24 files changed, 2224 insertions, 59 deletions
diff --git a/src/third_party/wiredtiger/test/suite/test_backup29.py b/src/third_party/wiredtiger/test/suite/test_backup29.py
new file mode 100644
index 00000000000..5cbce78e321
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_backup29.py
@@ -0,0 +1,206 @@
+#!/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 os, re, time
+from wtscenario import make_scenarios
+from wtbackup import backup_base
+from wiredtiger import stat
+
+# test_backup29.py
+# Test interaction between checkpoint and incremental backup. There was a bug in
+# maintaining the incremental backup bitmaps correctly after opening an uncached dhandle.
+# This test reconstructs the failure scenario and verifies correct behavior both when a
+# restart and when dhandle sweep lead to opening an uncached dhandle.
+class test_backup29(backup_base):
+ conn_config = 'file_manager=(close_handle_minimum=0,' + \
+ 'close_idle_time=3,close_scan_interval=1),' + \
+ 'statistics=(fast)'
+ create_config = 'allocation_size=512,key_format=i,value_format=S'
+ # Backup directory name. Uncomment if actually taking a backup.
+ # dir='backup.dir'
+ uri1 = 'test_first'
+ uri2 = 'test_second'
+ file1_uri = 'file:' + uri1 + '.wt'
+ file2_uri = 'file:' + uri2 + '.wt'
+ table1_uri = 'table:' + uri1
+ table2_uri = 'table:' + uri2
+ active_uri = 'table:active.wt'
+
+ value_base = '-abcdefghijkl'
+
+ few = 100
+ nentries = 5000
+
+ def get_open_file_count(self):
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+ n = stat_cursor[stat.conn.file_open][2]
+ stat_cursor.close()
+ return n
+
+ def parse_blkmods(self, uri):
+ meta_cursor = self.session.open_cursor('metadata:')
+ config = meta_cursor[uri]
+ meta_cursor.close()
+ # The search string looks like: ,blocks=feffff1f000000000000000000000000
+ # Obtain just the hex string.
+ b = re.search(',blocks=(\w+)', config)
+ self.assertTrue(b is not None)
+ # The bitmap string after the = is in group 1.
+ blocks = b.group(1)
+ self.pr("block bitmap: " + blocks)
+ return blocks
+
+ def compare_bitmap(self, orig, new):
+ # Compare the bitmaps from the metadata. Once a bit is set, it should never
+ # be cleared. But new bits could be set. So the check is only: if the original
+ # bitmap has a bit set then the current bitmap must be set for that bit also.
+ #
+ # First convert both bitmaps to a binary string, accounting for any possible leading
+ # zeroes (that would be truncated off). Then compare bit by bit.
+ orig_bits = bin(int('1'+orig, 16))[3:]
+ new_bits = bin(int('1'+new, 16))[3:]
+ self.pr("Original bitmap in binary: " + orig_bits)
+ self.pr("Reopened bitmap in binary: " + new_bits)
+ for o_bit, n_bit in zip(orig_bits, new_bits):
+ if o_bit != '0':
+ self.assertTrue(n_bit != '0')
+
+ def setup_test(self):
+ # Create and populate the tables.
+ self.session.create(self.table1_uri, self.create_config)
+ self.session.create(self.table2_uri, self.create_config)
+ c1 = self.session.open_cursor(self.table1_uri)
+ c2 = self.session.open_cursor(self.table2_uri)
+ # Only add a few entries.
+ self.pr("Write: " + str(self.few) + " initial data items")
+ for i in range(1, self.few):
+ val = str(i) + self.value_base
+ c1[i] = val
+ c2[i] = val
+ self.session.checkpoint()
+
+ # Take the initial full backup for incremental. We don't actually need to
+ # take the backup, we only need to open and close the backup cursor to have
+ # the library keep track of the bitmaps.
+ config = 'incremental=(enabled,granularity=4k,this_id="ID1")'
+ bkup_c = self.session.open_cursor('backup:', None, config)
+ # Uncomment these lines if actually taking the full backup is helpful for debugging.
+ # os.mkdir(self.dir)
+ # self.take_full_backup(self.dir, bkup_c)
+ bkup_c.close()
+
+ # Add a lot more data to both tables to generate a filled-in block mod bitmap.
+ last_i = self.few
+ self.pr("Write: " + str(self.nentries) + " additional data items")
+ for i in range(self.few, self.nentries):
+ val = str(i) + self.value_base
+ c1[i] = val
+ c2[i] = val
+ c1.close()
+ c2.close()
+ self.session.checkpoint()
+ # Get the block mod bitmap from the file URI.
+ self.orig1_bitmap = self.parse_blkmods(self.file1_uri)
+ self.orig2_bitmap = self.parse_blkmods(self.file2_uri)
+
+
+ def incr_backup_and_validate(self):
+ # After reopening we want to open both tables, but only modify one of them for
+ # the first checkpoint. Then modify the other table, checkpoint, and then check the
+ # that the block mod bitmap remains correct for the other table.
+ c1 = self.session.open_cursor(self.table1_uri)
+ c2 = self.session.open_cursor(self.table2_uri)
+ last_i = self.nentries
+
+ # Change the first table and checkpoint. Keep the second table clean.
+ self.pr("Update only table 1: " + str(last_i))
+ val = str(last_i) + self.value_base
+ c1[last_i] = val
+ self.session.checkpoint()
+ new1_bitmap = self.parse_blkmods(self.file1_uri)
+
+ # Now change the second table and checkpoint again.
+ self.pr("Update second table: " + str(last_i))
+ c2[last_i] = val
+ self.session.checkpoint()
+ new2_bitmap = self.parse_blkmods(self.file2_uri)
+
+ c1.close()
+ c2.close()
+
+ self.compare_bitmap(self.orig1_bitmap, new1_bitmap)
+ self.compare_bitmap(self.orig2_bitmap, new2_bitmap)
+
+ def test_backup29_reopen(self):
+ self.setup_test()
+
+ self.pr("CLOSE and REOPEN conn")
+ self.reopen_conn()
+ self.pr("Reopened conn")
+
+ self.incr_backup_and_validate()
+
+ def test_backup29_sweep(self):
+ self.setup_test()
+
+ self.pr("Waiting to sweep handles")
+ # Create another table and populate it, and checkpoint.
+ self.session.create(self.active_uri, self.create_config)
+ c = self.session.open_cursor(self.active_uri)
+ for i in range(1, self.few):
+ c[i] = str(i) + self.value_base
+ self.session.checkpoint()
+
+ sleep = 0
+ max = 20
+ # The only files sweep won't close should be the metadata, the history store, the
+ # lock file, and our active file.
+ final_nfile = 4
+
+ # Keep updating and checkpointing this table until all other handles have been swept.
+ # The checkpoints have the side effect of sweeping the session cache, which will allow
+ # dhandles to be closed.
+ while sleep < max:
+ i = i + 1
+ c[i] = str(i) + self.value_base
+ self.session.checkpoint()
+ sleep += 0.5
+ time.sleep(0.5)
+ nfile = self.get_open_file_count()
+ if nfile == final_nfile:
+ break
+ c.close()
+
+ # Make sure we swept everything before we ran out of time.
+ self.assertEqual(nfile, final_nfile)
+ self.pr("Sweep done")
+
+ self.incr_backup_and_validate()
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_bug029.py b/src/third_party/wiredtiger/test/suite/test_bug029.py
new file mode 100644
index 00000000000..b2d9e1c6ac4
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_bug029.py
@@ -0,0 +1,112 @@
+
+#!/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.
+#
+# [TEST_TAGS]
+# checkpoint:recovery
+# [END_TAGS]
+
+import wttest
+import os, shutil
+
+# test_bug029.py
+#
+# Test that WT correctly propogates the most recent checkpoint time
+# across restarts. We validate this by reproducing the original bug
+# from WT-9457: frequent checkpoints pushed the checkpoint clock time
+# into the future such that immediately after a restart a backup could
+# see its checkpoint deleted out from under it. The result was fatal
+# read errors when restoring the backup.
+
+class test_bug029(wttest.WiredTigerTestCase):
+ conn_config = ("cache_size=50MB")
+ uri = "table:test_bug029"
+ bigvalue = "WiredTiger" * 100
+ backup_dir = "backup_dir"
+
+ def add_data(self, uri, start, count):
+ cursor = self.session.open_cursor(uri, None)
+ for i in range(start, start + count):
+ cursor[i] = self.bigvalue
+ cursor.close()
+
+ def test_bug029(self):
+ # Create and populate table
+ self.session.create(self.uri, "key_format=i,value_format=S")
+ self.add_data(self.uri, 0, 2000)
+ self.session.checkpoint()
+
+ # Force the checkpoint time forward with a lot of quick checkpoints.
+ for i in range(100):
+ self.session.checkpoint("force=1")
+
+ # Add more data and checkpoint again. This creates a bunch of pages
+ # in the final checkpoint that can be deleted and reused if we hit
+ # the bug.
+ self.add_data(self.uri, 2000, 2000)
+ self.session.checkpoint()
+
+ # Shutdown and reopen.
+ self.reopen_conn()
+
+ self.add_data(self.uri, 0, 100)
+
+ # Open a backup cursor and force a few checkpoints. This will allow
+ # WT to delete older checkpoints, but as long as the backup cursor
+ # is open, it shouldn't delete the backup checkpoint---unless we hit
+ # the bug.
+ backup_cursor = self.session.open_cursor('backup:')
+
+ for i in range(10):
+ self.session.checkpoint("force=1")
+
+ # Write and checkpoint a bunch of data. If we erroneously deleted our
+ # backup checkpoint this should overwrite some of that checkpoint's
+ # blocks.
+ self.add_data(self.uri, 1000, 2000)
+ self.session.checkpoint()
+
+ # Now do the backup.
+ os.mkdir(self.backup_dir)
+ while True:
+ ret = backup_cursor.next()
+ if ret != 0:
+ break
+ shutil.copy(backup_cursor.get_key(), self.backup_dir)
+ backup_cursor.close()
+
+ # Open the backup and read data. If the backup snapshot was corrupted
+ # we will panic and die here.
+ backup_conn = self.wiredtiger_open(self.backup_dir, self.conn_config)
+ session = backup_conn.open_session()
+ cur1 = session.open_cursor(self.uri)
+ for i in range(0, 4000, 10):
+ self.assertEqual(cur1[i], self.bigvalue)
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_bug030.py b/src/third_party/wiredtiger/test/suite/test_bug030.py
new file mode 100644
index 00000000000..3969d71de9d
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_bug030.py
@@ -0,0 +1,125 @@
+#!/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
+from helper import simulate_crash_restart
+from wiredtiger import stat
+from wtdataset import SimpleDataSet
+from wtscenario import make_scenarios
+
+# test_bug030.py
+# This tests for the scenario in WT-10522 where we could return early when
+# appending a key's original value to its update list due to checking some
+# flags that must be ignored when looking at an aborted tombstone.
+class test_bug_030(wttest.WiredTigerTestCase):
+ format_values = [
+ ('column', dict(key_format='r', value_format='S')),
+ ('column_fix', dict(key_format='r', value_format='8t')),
+ ('row_integer', dict(key_format='i', value_format='S')),
+ ]
+
+ scenarios = make_scenarios(format_values)
+
+ def conn_config(self):
+ config = 'debug_mode=(update_restore_evict=true)'
+ return config
+
+ def test_bug030(self):
+ nrows = 10
+ uri = "table:test_bug030"
+
+ if self.value_format == '8t':
+ valuea = 97
+ valueb = 98
+ else:
+ valuea = "abcdef" * 3
+ valueb = "ghijkl" * 3
+
+ self.session.create(uri, 'key_format={},value_format={}'.format(
+ self.key_format, self.value_format))
+
+ # Stable insert
+ self.session.begin_transaction()
+ cursor = self.session.open_cursor(uri)
+ for i in range(1, nrows + 1):
+ cursor[i] = valuea
+ cursor.close()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(10))
+
+ self.conn.set_timestamp('oldest_timestamp={},stable_timestamp={}'.format(
+ self.timestamp_str(10), self.timestamp_str(20)))
+
+ # Unstable delete
+ self.session.begin_transaction()
+ cursor = self.session.open_cursor(uri)
+ for i in range(1, nrows + 1):
+ cursor.set_key(i)
+ cursor.remove()
+ cursor.close()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(30))
+
+ # Evict everything
+ self.session.begin_transaction()
+ evict_cursor = self.session.open_cursor(uri, None, 'debug=(release_evict)')
+ for i in range(1, nrows + 1):
+ evict_cursor.set_key(i)
+ evict_cursor.search()
+ evict_cursor.reset()
+ evict_cursor.close()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(40))
+
+ # Unstable, uncommitted update
+ self.session.begin_transaction()
+ cursor = self.session.open_cursor(uri)
+ for i in range(1, nrows + 1):
+ cursor[i] = valueb
+ cursor.close()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(50))
+
+ self.session.checkpoint()
+ self.conn.rollback_to_stable()
+
+ # Another delete, committed this time
+ self.session.begin_transaction()
+ cursor = self.session.open_cursor(uri)
+ for i in range(1, nrows + 1):
+ cursor.set_key(i)
+ cursor.remove()
+ cursor.close()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(60))
+
+ # Finally, evict everything. At this point, our key(s) have an
+ # update chain with a single entry that's both aborted and restored
+ # from the data store, that we attempt to reconcile.
+ self.session.begin_transaction()
+ evict_cursor = self.session.open_cursor(uri, None, 'debug=(release_evict)')
+ for i in range(1, nrows + 1):
+ evict_cursor.set_key(i)
+ evict_cursor.search()
+ evict_cursor.reset()
+ evict_cursor.close()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(70))
diff --git a/src/third_party/wiredtiger/test/suite/test_checkpoint04.py b/src/third_party/wiredtiger/test/suite/test_checkpoint04.py
index e9411acfe0d..5f7d7f9a38c 100755
--- a/src/third_party/wiredtiger/test/suite/test_checkpoint04.py
+++ b/src/third_party/wiredtiger/test/suite/test_checkpoint04.py
@@ -117,7 +117,7 @@ class test_checkpoint04(wttest.WiredTigerTestCase):
time_total = self.get_stat(stat.conn.txn_checkpoint_time_total)
self.pr('txn_checkpoint_time_total ' + str(time_total))
- self.assertEqual(num_ckpt, 2 * multiplier)
+ self.assertEqual(num_ckpt, 2)
self.assertEqual(running, 0)
self.assertEqual(prep_running, 0)
# Assert if this loop continues for more than 100 iterations.
@@ -129,6 +129,9 @@ class test_checkpoint04(wttest.WiredTigerTestCase):
break
else:
multiplier += 1
+ # Reopen the connection to reset statistics.
+ # We don't want stats from earlier runs to interfere with later runs.
+ self.reopen_conn()
if __name__ == '__main__':
wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_checkpoint_snapshot02.py b/src/third_party/wiredtiger/test/suite/test_checkpoint_snapshot02.py
index 89a7faddfc0..7ee28a240cc 100644
--- a/src/third_party/wiredtiger/test/suite/test_checkpoint_snapshot02.py
+++ b/src/third_party/wiredtiger/test/suite/test_checkpoint_snapshot02.py
@@ -161,8 +161,15 @@ class test_checkpoint_snapshot02(wttest.WiredTigerTestCase):
ckpt = checkpoint_thread(self.conn, done)
try:
ckpt.start()
- # Sleep for sometime so that checkpoint starts before committing last transaction.
- time.sleep(2)
+
+ # Wait for checkpoint to start and acquire its snapshot before committing.
+ ckpt_snapshot = 0
+ while not ckpt_snapshot:
+ time.sleep(1)
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+ ckpt_snapshot = stat_cursor[stat.conn.txn_checkpoint_snapshot_acquired][2]
+ stat_cursor.close()
+
session1.commit_transaction()
finally:
@@ -215,8 +222,15 @@ class test_checkpoint_snapshot02(wttest.WiredTigerTestCase):
ckpt = checkpoint_thread(self.conn, done)
try:
ckpt.start()
- # Sleep for sometime so that checkpoint starts before committing last transaction.
- time.sleep(2)
+
+ # Wait for checkpoint to start and acquire its snapshot before committing.
+ ckpt_snapshot = 0
+ while not ckpt_snapshot:
+ time.sleep(1)
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+ ckpt_snapshot = stat_cursor[stat.conn.txn_checkpoint_snapshot_acquired][2]
+ stat_cursor.close()
+
session1.commit_transaction()
finally:
@@ -272,8 +286,15 @@ class test_checkpoint_snapshot02(wttest.WiredTigerTestCase):
ckpt = checkpoint_thread(self.conn, done)
try:
ckpt.start()
- # Sleep for sometime so that checkpoint starts before committing last transaction.
- time.sleep(2)
+
+ # Wait for checkpoint to start and acquire its snapshot before committing.
+ ckpt_snapshot = 0
+ while not ckpt_snapshot:
+ time.sleep(1)
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+ ckpt_snapshot = stat_cursor[stat.conn.txn_checkpoint_snapshot_acquired][2]
+ stat_cursor.close()
+
session2.commit_transaction()
finally:
diff --git a/src/third_party/wiredtiger/test/suite/test_checkpoint_snapshot05.py b/src/third_party/wiredtiger/test/suite/test_checkpoint_snapshot05.py
index e8ff0067f61..583808915b6 100644
--- a/src/third_party/wiredtiger/test/suite/test_checkpoint_snapshot05.py
+++ b/src/third_party/wiredtiger/test/suite/test_checkpoint_snapshot05.py
@@ -147,8 +147,15 @@ class test_checkpoint_snapshot05(wttest.WiredTigerTestCase):
ckpt = checkpoint_thread(self.conn, done)
try:
ckpt.start()
- # Sleep for sometime so that checkpoint starts before committing last transaction.
- time.sleep(2)
+
+ # Wait for checkpoint to start and acquire its snapshot before committing.
+ ckpt_snapshot = 0
+ while not ckpt_snapshot:
+ time.sleep(1)
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+ ckpt_snapshot = stat_cursor[stat.conn.txn_checkpoint_snapshot_acquired][2]
+ stat_cursor.close()
+
session1.commit_transaction()
self.evict(self.uri, ds, self.nrows)
finally:
diff --git a/src/third_party/wiredtiger/test/suite/test_hs11.py b/src/third_party/wiredtiger/test/suite/test_hs11.py
index 647a7bfece5..417d37cca29 100755
--- a/src/third_party/wiredtiger/test/suite/test_hs11.py
+++ b/src/third_party/wiredtiger/test/suite/test_hs11.py
@@ -44,8 +44,20 @@ class test_hs11(wttest.WiredTigerTestCase):
('deletion', dict(update_type='deletion')),
('update', dict(update_type='update'))
]
- scenarios = make_scenarios(format_values, update_type_values)
- nrows = 10000
+ long_running_txn_values = [
+ ('long-running', dict(long_run_txn=True)),
+ ('no-long-running', dict(long_run_txn=False))
+ ]
+ last_update_type_values = [
+ ('modify', dict(modify=True)),
+ ('no-modify', dict(modify=False))
+ ]
+ nrows = [
+ ('small-nrows', dict(nrows=100)),
+ ('large-nrows', dict(nrows=10000))
+ ]
+ scenarios = make_scenarios(format_values, update_type_values,long_running_txn_values, last_update_type_values, nrows)
+ timestamps = 5
def create_key(self, i):
if self.key_format == 'S':
@@ -58,6 +70,18 @@ class test_hs11(wttest.WiredTigerTestCase):
stat_cursor.close()
return val
+ def evict_cursor(self, uri, nrows):
+ s = self.conn.open_session()
+ s.begin_transaction()
+ # Configure debug behavior on a cursor to evict the page positioned on when the reset API is used.
+ evict_cursor = s.open_cursor(uri, None, "debug=(release_evict)")
+ for i in range(1, nrows + 1):
+ evict_cursor.set_key(self.create_key(i))
+ evict_cursor.search()
+ evict_cursor.reset()
+ s.rollback_transaction()
+ evict_cursor.close()
+
def test_non_ts_updates_clears_hs(self):
uri = 'table:test_hs11'
create_params = 'key_format={},value_format={}'.format(self.key_format, self.value_format)
@@ -69,13 +93,14 @@ class test_hs11(wttest.WiredTigerTestCase):
else:
value1 = 'a' * 500
value2 = 'b' * 500
+ mod_value = 'm' + 'a' * 499
# FIXME-WT-9063 revisit the use of self.retry() throughout this file.
# Apply a series of updates from timestamps 1-4.
self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1))
cursor = self.session.open_cursor(uri)
- for ts in range(1, 5):
+ for ts in range(1, self.timestamps):
for i in range(1, self.nrows):
for retry in self.retry():
with retry.transaction(commit_timestamp = ts):
@@ -83,8 +108,24 @@ class test_hs11(wttest.WiredTigerTestCase):
# Reconcile and flush versions 1-3 to the history store.
self.session.checkpoint()
+ self.evict_cursor(uri, self.nrows)
+
+ # Apply a modify update at timestamp 5.
+ if self.modify and self.value_format != '8t':
+ for i in range(1, self.nrows):
+ for retry in self.retry():
+ with retry.transaction(commit_timestamp = 5):
+ cursor.set_key(self.create_key(i))
+ cursor.modify([wiredtiger.Modify("m", 0, 1)])
+ self.timestamps += 1
+
+ # Start a long running transaction at timestamp 5.
+ if self.long_run_txn:
+ session2 = self.conn.open_session()
+ session2.begin_transaction('read_timestamp=5')
- # Apply an update without timestamp.
+ # Apply an update without timestamp. If we have a long running transaction this update
+ # should not be globally visible until that transaction has ended.
for i in range(1, self.nrows):
if i % 2 == 0:
if self.update_type == 'deletion':
@@ -95,6 +136,11 @@ class test_hs11(wttest.WiredTigerTestCase):
# Reconcile and remove the obsolete entries.
self.session.checkpoint()
+ if self.long_run_txn:
+ session2.rollback_transaction()
+
+ # At this point any updates with no timestamp should be globally visible.
+ self.evict_cursor(uri, self.nrows)
# Now apply an update at timestamp 10.
for i in range(1, self.nrows):
@@ -102,8 +148,10 @@ class test_hs11(wttest.WiredTigerTestCase):
with retry.transaction(commit_timestamp = 10):
cursor[self.create_key(i)] = value2
+ self.session.checkpoint()
+
# Ensure that we blew away history store content.
- for ts in range(1, 5):
+ for ts in range(1, self.timestamps):
for retry in self.retry():
with retry.transaction(read_timestamp = ts, rollback = True):
for i in range(1, self.nrows):
@@ -118,7 +166,10 @@ class test_hs11(wttest.WiredTigerTestCase):
else:
self.assertEqual(cursor[self.create_key(i)], value2)
else:
- self.assertEqual(cursor[self.create_key(i)], value1)
+ if ts == 5 and self.modify and self.value_format != '8t':
+ self.assertEqual(cursor[self.create_key(i)], mod_value)
+ else:
+ self.assertEqual(cursor[self.create_key(i)], value1)
if self.update_type == 'deletion':
hs_truncate = self.get_stat(stat.conn.cache_hs_key_truncate_onpage_removal)
@@ -135,11 +186,12 @@ class test_hs11(wttest.WiredTigerTestCase):
else:
value1 = 'a' * 500
value2 = 'b' * 500
+ mod_value = 'm' + 'a' * 499
# Apply a series of updates from timestamps 1-4.
self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1))
cursor = self.session.open_cursor(uri)
- for ts in range(1, 5):
+ for ts in range(1, self.timestamps):
for i in range(1, self.nrows):
for retry in self.retry():
with retry.transaction(commit_timestamp = ts):
@@ -148,6 +200,15 @@ class test_hs11(wttest.WiredTigerTestCase):
# Reconcile and flush versions 1-3 to the history store.
self.session.checkpoint()
+ # Apply a modify update at timestamp 5.
+ if self.modify and self.value_format != '8t':
+ for i in range(1, self.nrows):
+ for retry in self.retry():
+ with retry.transaction(commit_timestamp = 5):
+ cursor.set_key(self.create_key(i))
+ cursor.modify([wiredtiger.Modify("m", 0, 1)])
+ self.timestamps += 1
+
# Remove the key with timestamp 10.
for i in range(1, self.nrows):
if i % 2 == 0:
@@ -178,7 +239,10 @@ class test_hs11(wttest.WiredTigerTestCase):
else:
self.assertEqual(cursor.search(), wiredtiger.WT_NOTFOUND)
else:
- self.assertEqual(cursor[self.create_key(i)], value1)
+ if self.modify and self.value_format != '8t':
+ self.assertEqual(cursor[self.create_key(i)], mod_value)
+ else:
+ self.assertEqual(cursor[self.create_key(i)], value1)
hs_truncate = self.get_stat(stat.conn.cache_hs_key_truncate_onpage_removal)
self.assertEqual(hs_truncate, 0)
diff --git a/src/third_party/wiredtiger/test/suite/test_hs20.py b/src/third_party/wiredtiger/test/suite/test_hs20.py
index 071d33de9bd..e2b9d878517 100755
--- a/src/third_party/wiredtiger/test/suite/test_hs20.py
+++ b/src/third_party/wiredtiger/test/suite/test_hs20.py
@@ -26,7 +26,7 @@
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
-import wiredtiger, wttest
+import wiredtiger, wttest, sys
from wtscenario import make_scenarios
# test_hs20.py
@@ -103,3 +103,8 @@ class test_hs20(wttest.WiredTigerTestCase):
for retry in self.retry():
with retry.transaction(read_timestamp = 3, rollback = True):
self.assertEqual(cursor[self.make_key(i)], value1 + "B")
+
+ if (sys.platform.startswith('darwin')):
+ # Ignore the eviction generation drain warning as it is possible for eviction to take
+ # longer to evict pages due to overflow items on the page.
+ self.ignoreStdoutPatternIfExists('Eviction took more than 1 minute')
diff --git a/src/third_party/wiredtiger/test/suite/test_hs32.py b/src/third_party/wiredtiger/test/suite/test_hs32.py
new file mode 100755
index 00000000000..b90bbaa15db
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_hs32.py
@@ -0,0 +1,160 @@
+#!/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 wiredtiger, wttest
+from wtscenario import make_scenarios
+from wiredtiger import stat
+
+# test_hs32.py
+# Ensure that updates without timestamps clear the history store records.
+class test_hs32(wttest.WiredTigerTestCase):
+ conn_config = 'cache_size=500MB,statistics=(all)'
+ format_values = [
+ ('column', dict(key_format='r', value_format='S')),
+ ('column-fix', dict(key_format='r', value_format='8t')),
+ ('integer-row', dict(key_format='i', value_format='S')),
+ ('string-row', dict(key_format='S', value_format='S')),
+ ]
+ update_type_values = [
+ ('deletion', dict(update_type='deletion')),
+ ('update', dict(update_type='update'))
+ ]
+ long_running_txn_values = [
+ ('no-long-run-txn', dict(long_run_txn=False)),
+ ('long-run-txn', dict(long_run_txn=True))
+ ]
+ scenarios = make_scenarios(format_values, update_type_values,long_running_txn_values)
+ nrows = 100
+
+ def create_key(self, i):
+ if self.key_format == 'S':
+ return str(i)
+ return i
+
+ def get_stat(self, stat):
+ stat_cursor = self.session.open_cursor('statistics:')
+ val = stat_cursor[stat][2]
+ stat_cursor.close()
+ return val
+
+ def evict_cursor(self, uri, nrows):
+ s = self.conn.open_session()
+ s.begin_transaction()
+ # Configure debug behavior on a cursor to evict the page positioned on when the reset API is used.
+ evict_cursor = s.open_cursor(uri, None, "debug=(release_evict)")
+ for i in range(1, nrows + 1):
+ evict_cursor.set_key(self.create_key(i))
+ evict_cursor.search()
+ evict_cursor.reset()
+ s.rollback_transaction()
+ evict_cursor.close()
+
+ def test_non_ts_updates_tombstone_clears_hs(self):
+ uri = 'table:test_hs32'
+ create_params = 'key_format={},value_format={}'.format(self.key_format, self.value_format)
+ self.session.create(uri, create_params)
+
+ if self.value_format == '8t':
+ value1 = 97
+ value2 = 98
+ else:
+ value1 = 'a' * 500
+ value2 = 'b' * 500
+
+ # Apply a series of updates from timestamps 1-4.
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1))
+ cursor = self.session.open_cursor(uri)
+ for ts in range(1, 5):
+ for i in range(1, self.nrows):
+ for retry in self.retry():
+ with retry.transaction(commit_timestamp = ts):
+ cursor[self.create_key(i)] = value1
+
+ # Reconcile and flush versions 1-3 to the history store.
+ self.session.checkpoint()
+ self.evict_cursor(uri, self.nrows)
+
+ if self.long_run_txn:
+ # Apply a another update at timestamp 5.
+ for i in range(1, self.nrows):
+ for retry in self.retry():
+ with retry.transaction(commit_timestamp = 5):
+ cursor[self.create_key(i)] = value1
+
+ # Start a long running transaction to make tombstone not globally visible.
+ session2 = self.conn.open_session()
+ session2.begin_transaction('read_timestamp=5')
+
+ # Apply an update/delete without timestamp.
+ for i in range(1, self.nrows):
+ self.session.begin_transaction()
+ if i % 2 == 0:
+ if self.update_type == 'deletion':
+ cursor.set_key(self.create_key(i))
+ cursor.remove()
+ else:
+ cursor[self.create_key(i)] = value2
+ self.session.commit_transaction()
+
+ if self.long_run_txn:
+ # Reconcile and remove the obsolete entries.
+ self.session.checkpoint()
+ self.evict_cursor(uri, self.nrows)
+
+ # Rollback the long running transaction.
+ session2.rollback_transaction()
+
+ # Now apply an update at timestamp 10.
+ for i in range(1, self.nrows):
+ for retry in self.retry():
+ with retry.transaction(commit_timestamp = 10):
+ cursor[self.create_key(i)] = value2
+
+ self.session.checkpoint()
+
+ # Ensure that we blew away history store content.
+ for ts in range(1, 5):
+ for retry in self.retry():
+ with retry.transaction(read_timestamp = ts, rollback = True):
+ for i in range(1, self.nrows):
+ if i % 2 == 0:
+ if self.update_type == 'deletion':
+ cursor.set_key(self.create_key(i))
+ if self.value_format == '8t':
+ self.assertEqual(cursor.search(), 0)
+ self.assertEqual(cursor.get_value(), 0)
+ else:
+ self.assertEqual(cursor.search(), wiredtiger.WT_NOTFOUND)
+ else:
+ self.assertEqual(cursor[self.create_key(i)], value2)
+ else:
+ self.assertEqual(cursor[self.create_key(i)], value1)
+
+ if self.update_type == 'deletion':
+ cache_hs_key_truncate = self.get_stat(stat.conn.cache_hs_key_truncate)
+ self.assertGreater(cache_hs_key_truncate, 0)
diff --git a/src/third_party/wiredtiger/test/suite/test_prepare20.py b/src/third_party/wiredtiger/test/suite/test_prepare20.py
index 0b541d40730..6f219ea2c83 100644
--- a/src/third_party/wiredtiger/test/suite/test_prepare20.py
+++ b/src/third_party/wiredtiger/test/suite/test_prepare20.py
@@ -27,14 +27,14 @@
# OTHER DEALINGS IN THE SOFTWARE.
-# test_prepare19.py
+# test_prepare20.py
# Check that we can use an application-level log to replay unstable transactions.
-import wiredtiger, wttest
+import wttest
from wtscenario import make_scenarios
from helper import simulate_crash_restart
-class test_prepare19(wttest.WiredTigerTestCase):
+class test_prepare20(wttest.WiredTigerTestCase):
# Do write the logs immediately, but don't waste time fsyncing them.
conn_config = 'log=(enabled),transaction_sync=(enabled=true,method=none)'
@@ -131,7 +131,7 @@ class test_prepare19(wttest.WiredTigerTestCase):
# First pass: find prepared txns. Ignore (thus abort) any that didn't even prepare.
txns = {}
self.lcursor.reset()
- for lsn, op, k, oldv, newv in self.lcursor:
+ for _, op, k, oldv, newv in self.lcursor:
if op == self.BEGIN:
# "key" is the txnid
txns[k] = False
@@ -143,7 +143,7 @@ class test_prepare19(wttest.WiredTigerTestCase):
writing = False
committime = None
durabletime = None
- for lsn, op, k, oldv, newv in self.lcursor:
+ for _, op, k, oldv, newv in self.lcursor:
if op == self.BEGIN:
# "key" is the txnid
if txns[k]:
@@ -198,9 +198,9 @@ class test_prepare19(wttest.WiredTigerTestCase):
# Now the test.
- def test_prepare19(self):
- data_uri = 'file:prepare19data'
- log_uri = 'file:prepare19log'
+ def test_prepare20(self):
+ data_uri = 'file:prepare20data'
+ log_uri = 'file:prepare20log'
# Create one table for data and another to be an application-level log.
# The log's format is application-lsn -> operation, key, oldvalue, newvalue
diff --git a/src/third_party/wiredtiger/test/suite/test_prepare21.py b/src/third_party/wiredtiger/test/suite/test_prepare21.py
new file mode 100644
index 00000000000..ec61092d04c
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_prepare21.py
@@ -0,0 +1,135 @@
+#!/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 threading, time
+from helper import simulate_crash_restart
+from test_rollback_to_stable01 import test_rollback_to_stable_base
+from wiredtiger import stat
+from wtdataset import SimpleDataSet
+from wtscenario import make_scenarios
+from wtthread import checkpoint_thread
+
+# test_prepare21.py
+# Test prepare rollback doesn't crash because of triggering out of order fix.
+class test_prepare21(test_rollback_to_stable_base):
+
+ format_values = [
+ ('column', dict(key_format='r', value_format='S')),
+ ('column_fix', dict(key_format='r', value_format='8t')),
+ ('row_integer', dict(key_format='i', value_format='S')),
+ ]
+
+ scenarios = make_scenarios(format_values)
+
+ def conn_config(self):
+ config = 'cache_size=10MB,statistics=(all),timing_stress_for_test=[history_store_checkpoint_delay]'
+ return config
+
+ def evict_cursor(self, uri, nrows):
+ # Configure debug behavior on a cursor to evict the page positioned on when the reset API is used.
+ evict_cursor = self.session.open_cursor(uri, None, "debug=(release_evict)")
+ self.session.begin_transaction("ignore_prepare=true")
+ for i in range (1, nrows + 1):
+ evict_cursor.set_key(i)
+ evict_cursor.search()
+ evict_cursor.reset()
+ evict_cursor.close()
+ self.session.rollback_transaction()
+
+ def test_prepare_rollback(self):
+ nrows = 10
+
+ # Create a table.
+ uri = "table:prepare21"
+ ds = SimpleDataSet(self, uri, 0, key_format=self.key_format, value_format=self.value_format)
+ ds.populate()
+
+ if self.value_format == '8t':
+ value_a = 97
+ value_b = 98
+ value_c = 99
+ value_d = 100
+ else:
+ value_a = "aaaaa" * 100
+ value_b = "bbbbb" * 100
+ value_c = "ccccc" * 100
+ value_d = "ddddd" * 100
+
+ # Pin oldest and stable to timestamp 10.
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) +
+ ',stable_timestamp=' + self.timestamp_str(10))
+
+ self.large_updates(uri, value_a, ds, nrows, False, 20)
+ self.large_updates(uri, value_b, ds, nrows, False, 30)
+ self.large_removes(uri, ds, nrows, False, 40)
+
+ prepare_session = self.conn.open_session()
+ prepare_session.begin_transaction()
+ cursor = prepare_session.open_cursor(uri)
+ for i in range (1, nrows + 1):
+ cursor[i] = value_c
+ cursor.close()
+ prepare_session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(50))
+
+ # Verify data is visible and correct.
+ self.check(value_a, uri, nrows, None, 20)
+ self.check(value_b, uri, nrows, None, 30)
+
+ self.evict_cursor(uri, nrows)
+
+ # Pin stable to timestamp 40.
+ self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(40))
+
+ # Rollback the prepared update
+ prepare_session.rollback_transaction()
+ self.large_updates(uri, value_d, ds, nrows, False, 60)
+
+ done = threading.Event()
+ ckpt = checkpoint_thread(self.conn, done)
+ try:
+ ckpt.start()
+
+ # Wait for checkpoint to start before committing last transaction.
+ ckpt_started = 0
+ while not ckpt_started:
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+ ckpt_started = stat_cursor[stat.conn.txn_checkpoint_running][2]
+ stat_cursor.close()
+
+ self.evict_cursor(uri, nrows)
+ finally:
+ done.set()
+ ckpt.join()
+
+ # Verify data is visible and correct.
+ self.check(value_a, uri, nrows, None, 20)
+ self.check(value_b, uri, nrows, None, 30)
+ self.check(value_d, uri, nrows, None, 60)
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_prepare22.py b/src/third_party/wiredtiger/test/suite/test_prepare22.py
new file mode 100644
index 00000000000..f35f4a50052
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_prepare22.py
@@ -0,0 +1,137 @@
+#!/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 wiredtiger, wttest
+from wtscenario import make_scenarios
+
+# test_prepare22.py
+# Test prepare with rollback to stable without failed eviction.
+class test_prepare22(wttest.WiredTigerTestCase):
+
+ format_values = [
+ ('column', dict(key_format='r', value_format='S')),
+ ('column_fix', dict(key_format='r', value_format='8t')),
+ ('row_integer', dict(key_format='i', value_format='S')),
+ ]
+
+ delete = [
+ ('delete', dict(delete=True)),
+ ('non-delete', dict(delete=False)),
+ ]
+
+ scenarios = make_scenarios(format_values, delete)
+
+ def test_prepare22(self):
+ uri = "table:test_prepare22"
+ self.session.create(uri, 'key_format=' + self.key_format + ',value_format=' + self.value_format)
+
+ if self.value_format == '8t':
+ value_a = 97
+ value_b = 98
+ value_c = 99
+ else:
+ value_a = "a"
+ value_b = "b"
+ value_c = "c"
+
+ # Pin oldest timestamp to 1
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1))
+
+ # Do the first update
+ cursor = self.session.open_cursor(uri)
+ self.session.begin_transaction()
+ cursor[1] = value_a
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(10))
+
+ # Do the second update
+ self.session.begin_transaction()
+ cursor[1] = value_b
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(20))
+
+ if self.delete:
+ self.session.begin_transaction()
+ cursor.set_key(1)
+ cursor.remove()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(30))
+
+ # Do a prepared update
+ self.session.begin_transaction()
+ cursor[1] = value_c
+ self.session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(40))
+
+ # Evict the page
+ session2 = self.conn.open_session()
+ evict_cursor = session2.open_cursor(uri, None, 'debug=(release_evict)')
+ session2.begin_transaction('ignore_prepare=true,read_timestamp=' + self.timestamp_str(20))
+ self.assertEquals(evict_cursor[1], value_b)
+ evict_cursor.reset()
+ evict_cursor.close()
+ session2.rollback_transaction()
+
+ # Ensure the history store is checkpointed
+ session2.checkpoint()
+
+ # Rollback the prepared transaction
+ self.session.rollback_transaction()
+
+ # Set stable timestamp to 30
+ self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(30))
+
+ # Call rollback to stable
+ self.conn.rollback_to_stable()
+
+ # Evict the page again
+ evict_cursor = session2.open_cursor(uri, None, 'debug=(release_evict)')
+ session2.begin_transaction('read_timestamp=' + self.timestamp_str(20))
+ self.assertEquals(evict_cursor[1], value_b)
+ evict_cursor.reset()
+ evict_cursor.close()
+ session2.rollback_transaction()
+
+ # Verify we can still read back value a
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(10))
+ self.assertEquals(cursor[1], value_a)
+ self.session.rollback_transaction()
+
+ # Verify we can still read back value b
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(20))
+ self.assertEquals(cursor[1], value_b)
+ self.session.rollback_transaction()
+
+ # Verify we can still read back the deletion
+ if self.delete:
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(30))
+ if self.value_format == '8t':
+ self.assertEquals(cursor[1], 0)
+ else:
+ cursor.set_key(1)
+ self.assertEquals(cursor.search(), wiredtiger.WT_NOTFOUND)
+ self.session.rollback_transaction()
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_prepare23.py b/src/third_party/wiredtiger/test/suite/test_prepare23.py
new file mode 100644
index 00000000000..f7496a5d228
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_prepare23.py
@@ -0,0 +1,131 @@
+#!/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 wiredtiger, wttest
+from wtscenario import make_scenarios
+
+# test_prepare23.py
+# Test prepare rollback with rollback to stable and failed eviction.
+class test_prepare23(wttest.WiredTigerTestCase):
+ conn_config = 'timing_stress_for_test=[failpoint_eviction_fail_after_reconciliation]'
+
+ format_values = [
+ ('column', dict(key_format='r', value_format='S')),
+ ('column_fix', dict(key_format='r', value_format='8t')),
+ ('row_integer', dict(key_format='i', value_format='S')),
+ ]
+
+ delete = [
+ ('delete', dict(delete=True)),
+ ('non-delete', dict(delete=False)),
+ ]
+
+ scenarios = make_scenarios(format_values, delete)
+
+ def test_prepare23(self):
+ uri = "table:test_prepare23"
+ self.session.create(uri, 'key_format=' + self.key_format + ',value_format=' + self.value_format)
+
+ if self.value_format == '8t':
+ value_a = 97
+ value_b = 98
+ value_c = 99
+ else:
+ value_a = "a"
+ value_b = "b"
+ value_c = "c"
+
+ # Pin oldest timestamp to 1
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1))
+
+ cursor = self.session.open_cursor(uri)
+ session2 = self.conn.open_session()
+ evict_cursor = session2.open_cursor(uri, None, 'debug=(release_evict)')
+ ts = 0
+ for i in range (1, 1001):
+ # Do the first update
+ self.session.begin_transaction()
+ cursor[i] = value_a
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(ts + 10))
+
+ # Do the second update
+ self.session.begin_transaction()
+ cursor[i] = value_b
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(ts + 20))
+
+ if self.delete:
+ self.session.begin_transaction()
+ cursor.set_key(i)
+ cursor.remove()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(ts + 30))
+
+ # Do a prepared update
+ self.session.begin_transaction()
+ cursor[i] = value_c
+ self.session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(ts + 40))
+ cursor.reset()
+
+ # Evict the page
+ session2.begin_transaction('ignore_prepare=true,read_timestamp=' + self.timestamp_str(ts + 20))
+ self.assertEquals(evict_cursor[i], value_b)
+ evict_cursor.reset()
+ session2.rollback_transaction()
+
+ # Rollback the prepared transaction
+ self.session.rollback_transaction()
+
+ # Set stable timestamp to 30 * i
+ self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(ts + 30))
+
+ # Call rollback to stable
+ self.conn.rollback_to_stable()
+
+ # Verify we can still read back value a
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts + 10))
+ self.assertEquals(cursor[i], value_a)
+ self.session.rollback_transaction()
+
+ # Verify we can still read back value b
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts + 20))
+ self.assertEquals(cursor[i], value_b)
+ self.session.rollback_transaction()
+
+ # Verify we can still read back the deletion
+ if self.delete:
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts + 30))
+ if self.value_format == '8t':
+ self.assertEquals(cursor[i], 0)
+ else:
+ cursor.set_key(i)
+ self.assertEquals(cursor.search(), wiredtiger.WT_NOTFOUND)
+ self.session.rollback_transaction()
+
+ ts += 40
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_prepare24.py b/src/third_party/wiredtiger/test/suite/test_prepare24.py
new file mode 100644
index 00000000000..af367af87e5
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_prepare24.py
@@ -0,0 +1,126 @@
+#!/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 wiredtiger, wttest
+from wtscenario import make_scenarios
+
+# test_prepare24.py
+# Test prepare commit after eviction failure.
+class test_prepare24(wttest.WiredTigerTestCase):
+ conn_config = 'timing_stress_for_test=[failpoint_eviction_fail_after_reconciliation]'
+
+ format_values = [
+ ('column', dict(key_format='r', value_format='S')),
+ ('column_fix', dict(key_format='r', value_format='8t')),
+ ('row_integer', dict(key_format='i', value_format='S')),
+ ]
+
+ delete = [
+ ('delete', dict(delete=True)),
+ ('non-delete', dict(delete=False)),
+ ]
+
+ scenarios = make_scenarios(format_values, delete)
+
+ def test_prepare24(self):
+ uri = "table:test_prepare24"
+ self.session.create(uri, 'key_format=' + self.key_format + ',value_format=' + self.value_format)
+
+ if self.value_format == '8t':
+ value_a = 97
+ value_b = 98
+ else:
+ value_a = "a"
+ value_b = "b"
+
+ # Pin oldest timestamp to 1
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1))
+
+ cursor = self.session.open_cursor(uri)
+ session2 = self.conn.open_session()
+ evict_cursor = session2.open_cursor(uri, None, 'debug=(release_evict)')
+ ts = 0
+ for i in range (1, 1001):
+ # Insert a value
+ self.session.begin_transaction()
+ cursor[i] = value_a
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(ts + 10))
+
+ if self.delete:
+ self.session.begin_transaction()
+ cursor.set_key(i)
+ cursor.remove()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(ts + 20))
+
+ # Do a prepared update
+ self.session.begin_transaction()
+ cursor[i] = value_b
+ self.session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(ts + 30))
+ cursor.reset()
+
+ # Evict the page
+ session2.begin_transaction('ignore_prepare=true,read_timestamp=' + self.timestamp_str(ts + 10))
+ self.assertEquals(evict_cursor[i], value_a)
+ evict_cursor.reset()
+ session2.rollback_transaction()
+
+ # Commit the prepared transaction
+ self.session.timestamp_transaction('commit_timestamp=' + self.timestamp_str(ts + 30))
+ self.session.timestamp_transaction('durable_timestamp=' + self.timestamp_str(ts + 40))
+ self.session.commit_transaction()
+
+ # Evict the page again
+ session2.begin_transaction('ignore_prepare=true,read_timestamp=' + self.timestamp_str(ts + 10))
+ self.assertEquals(evict_cursor[i], value_a)
+ evict_cursor.reset()
+ session2.rollback_transaction()
+
+ # Verify we can still read back value a
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts + 10))
+ self.assertEquals(cursor[i], value_a)
+ self.session.rollback_transaction()
+
+ # Verify we can still read back the deletion
+ if self.delete:
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts + 20))
+ if self.value_format == '8t':
+ self.assertEquals(cursor[i], 0)
+ else:
+ cursor.set_key(i)
+ self.assertEquals(cursor.search(), wiredtiger.WT_NOTFOUND)
+ self.session.rollback_transaction()
+
+ # Verify we can still read back the prepared update
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts + 30))
+ self.assertEquals(cursor[i], value_b)
+ self.session.rollback_transaction()
+
+ ts += 40
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_prepare25.py b/src/third_party/wiredtiger/test/suite/test_prepare25.py
new file mode 100644
index 00000000000..e512c3c7fef
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_prepare25.py
@@ -0,0 +1,137 @@
+#!/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 wiredtiger, wttest
+from wtscenario import make_scenarios
+
+# test_prepare25.py
+# Test prepare rollback and then prepare commit with failed eviction.
+class test_prepare25(wttest.WiredTigerTestCase):
+ conn_config = 'timing_stress_for_test=[failpoint_eviction_fail_after_reconciliation]'
+
+ format_values = [
+ ('column', dict(key_format='r', value_format='S')),
+ ('column_fix', dict(key_format='r', value_format='8t')),
+ ('row_integer', dict(key_format='i', value_format='S')),
+ ]
+
+ delete = [
+ ('delete', dict(delete=True)),
+ ('non-delete', dict(delete=False)),
+ ]
+
+ scenarios = make_scenarios(format_values, delete)
+
+ def test_prepare25(self):
+ uri = "table:test_prepare25"
+ self.session.create(uri, 'key_format=' + self.key_format + ',value_format=' + self.value_format)
+
+ if self.value_format == '8t':
+ value_a = 97
+ value_b = 98
+ value_c = 99
+ else:
+ value_a = "a"
+ value_b = "b"
+ value_c = "c"
+
+ # Pin oldest timestamp to 1
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1))
+
+ cursor = self.session.open_cursor(uri)
+ session2 = self.conn.open_session()
+ evict_cursor = session2.open_cursor(uri, None, 'debug=(release_evict)')
+ ts = 0
+ for i in range (1, 1001):
+ # Insert an update
+ self.session.begin_transaction()
+ cursor[i] = value_a
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(ts + 10))
+
+ if self.delete:
+ self.session.begin_transaction()
+ cursor.set_key(i)
+ cursor.remove()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(ts + 20))
+
+ # Do a prepared update
+ self.session.begin_transaction()
+ cursor[i] = value_b
+ self.session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(ts + 30))
+ cursor.reset()
+
+ # Evict the page
+ session2.begin_transaction('ignore_prepare=true,read_timestamp=' + self.timestamp_str(ts + 10))
+ self.assertEquals(evict_cursor[i], value_a)
+ evict_cursor.reset()
+ session2.rollback_transaction()
+
+ # Rollback the prepared transaction
+ self.session.rollback_transaction()
+
+ # Do another prepared update
+ self.session.begin_transaction()
+ cursor[i] = value_c
+ self.session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(ts + 40))
+
+ # Commit the prepared update
+ self.session.timestamp_transaction('commit_timestamp=' + self.timestamp_str(ts + 40))
+ self.session.timestamp_transaction('durable_timestamp=' + self.timestamp_str(ts + 50))
+ self.session.commit_transaction()
+ cursor.reset()
+
+ # Evict the page again
+ session2.begin_transaction('ignore_prepare=true,read_timestamp=' + self.timestamp_str(ts + 10))
+ self.assertEquals(evict_cursor[i], value_a)
+ evict_cursor.reset()
+ session2.rollback_transaction()
+
+ # Verify we can still read back value a
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts + 10))
+ self.assertEquals(cursor[i], value_a)
+ self.session.rollback_transaction()
+
+ # Verify we can still read back the deletion
+ if self.delete:
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts + 20))
+ if self.value_format == '8t':
+ self.assertEquals(cursor[i], 0)
+ else:
+ cursor.set_key(i)
+ self.assertEquals(cursor.search(), wiredtiger.WT_NOTFOUND)
+ self.session.rollback_transaction()
+
+ # Verify we can still read back value c
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(ts + 40))
+ self.assertEquals(cursor[i], value_c)
+ self.session.rollback_transaction()
+
+ ts += 50
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_prepare26.py b/src/third_party/wiredtiger/test/suite/test_prepare26.py
new file mode 100644
index 00000000000..52e08cedff6
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_prepare26.py
@@ -0,0 +1,134 @@
+#!/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 wiredtiger, wttest
+from wtscenario import make_scenarios
+
+# test_prepare26.py
+# Test prepare rollback and then delete the key.
+class test_prepare26(wttest.WiredTigerTestCase):
+ format_values = [
+ ('column', dict(key_format='r', value_format='S')),
+ ('column_fix', dict(key_format='r', value_format='8t')),
+ ('row_integer', dict(key_format='i', value_format='S')),
+ ]
+
+ scenarios = make_scenarios(format_values)
+
+ def test_prepare26(self):
+ uri = "table:test_prepare26"
+ self.session.create(uri, 'key_format=' + self.key_format + ',value_format=' + self.value_format)
+
+ if self.value_format == '8t':
+ value_a = 97
+ value_b = 98
+ value_c = 99
+ else:
+ value_a = "a"
+ value_b = "b"
+ value_c = "c"
+
+ # Pin oldest timestamp to 1
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1))
+
+ # Insert a value
+ cursor = self.session.open_cursor(uri)
+ self.session.begin_transaction()
+ cursor[1] = value_a
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(10))
+
+ # Do a prepared update
+ self.session.begin_transaction()
+ cursor[1] = value_c
+ self.session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(20))
+
+ # Evict the page
+ session2 = self.conn.open_session()
+ evict_cursor = session2.open_cursor(uri, None, 'debug=(release_evict)')
+ session2.begin_transaction('ignore_prepare=true,read_timestamp=' + self.timestamp_str(10))
+ self.assertEquals(evict_cursor[1], value_a)
+ evict_cursor.reset()
+ evict_cursor.close()
+ session2.rollback_transaction()
+
+ # Rollback the prepared transaction
+ self.session.rollback_transaction()
+
+ # Delete the key
+ self.session.begin_transaction()
+ cursor.set_key(1)
+ cursor.remove()
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(30))
+
+ # Set oldest timestamp to 30
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(30))
+
+ # Evict the page again
+ evict_cursor = session2.open_cursor(uri, None, 'debug=(release_evict)')
+ session2.begin_transaction()
+ evict_cursor.set_key(1)
+ if self.value_format == '8t':
+ self.assertEquals(evict_cursor[1], 0)
+ else:
+ evict_cursor.set_key(1)
+ self.assertEquals(evict_cursor.search(), wiredtiger.WT_NOTFOUND)
+ evict_cursor.reset()
+ evict_cursor.close()
+ session2.rollback_transaction()
+
+ # Do another update
+ cursor = self.session.open_cursor(uri)
+ self.session.begin_transaction()
+ cursor[1] = value_b
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(40))
+
+ # Do another update
+ cursor = self.session.open_cursor(uri)
+ self.session.begin_transaction()
+ cursor[1] = value_c
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(50))
+
+ # Evict the page again
+ evict_cursor = session2.open_cursor(uri, None, 'debug=(release_evict)')
+ session2.begin_transaction('read_timestamp=' + self.timestamp_str(50))
+ self.assertEquals(evict_cursor[1], value_c)
+ evict_cursor.reset()
+ evict_cursor.close()
+ session2.rollback_transaction()
+
+ # Verify we read nothing at the oldest
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(30))
+ if self.value_format == '8t':
+ self.assertEquals(cursor[1], 0)
+ else:
+ cursor.set_key(1)
+ self.assertEquals(cursor.search(), wiredtiger.WT_NOTFOUND)
+ self.session.rollback_transaction()
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_rollback_to_stable39.py b/src/third_party/wiredtiger/test/suite/test_rollback_to_stable39.py
new file mode 100644
index 00000000000..90bf63935da
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_rollback_to_stable39.py
@@ -0,0 +1,172 @@
+#!/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 threading, time
+from helper import simulate_crash_restart
+from test_rollback_to_stable01 import test_rollback_to_stable_base
+from wiredtiger import stat
+from wtdataset import SimpleDataSet
+from wtscenario import make_scenarios
+from wtthread import checkpoint_thread
+
+# test_rollback_to_stable39.py
+# Test to delay checkpoint and perform eviction in parallel to ensure eviction moves the content from data store to history store
+# and then checkpoint history store to see the same content in data store and history store. Later use the checkpoint to restore
+# the database which will trigger eviction to insert the same record from data store to history store.
+class test_rollback_to_stable39(test_rollback_to_stable_base):
+ restart_config = False
+
+ format_values = [
+ ('column', dict(key_format='r', value_format='S', prepare_extraconfig='')),
+ ('column_fix', dict(key_format='r', value_format='8t',
+ prepare_extraconfig=',allocation_size=512,leaf_page_max=512')),
+ ('row_integer', dict(key_format='i', value_format='S', prepare_extraconfig='')),
+ ]
+
+ prepare_values = [
+ ('no_prepare', dict(prepare=False)),
+ ('prepare', dict(prepare=True))
+ ]
+
+ scenarios = make_scenarios(format_values, prepare_values)
+
+ def conn_config(self):
+ config = 'cache_size=25MB,statistics=(all),statistics_log=(json,on_close,wait=1)'
+ if self.restart_config:
+ config += ',timing_stress_for_test=[checkpoint_slow]'
+ else:
+ config += ',timing_stress_for_test=[history_store_checkpoint_delay]'
+ return config
+
+ def test_rollback_to_stable(self):
+ nrows = 1000
+
+ # Create a table.
+ uri = "table:rollback_to_stable39"
+ ds = SimpleDataSet(
+ self, uri, 0, key_format=self.key_format, value_format=self.value_format)
+ ds.populate()
+
+ if self.value_format == '8t':
+ value_a = 97
+ value_b = 98
+ value_c = 99
+ else:
+ value_a = "aaaaa" * 100
+ value_b = "bbbbb" * 100
+ value_c = "ccccc" * 100
+
+ # Pin oldest and stable to timestamp 10.
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) +
+ ',stable_timestamp=' + self.timestamp_str(10))
+
+ # Perform several updates.
+ self.large_updates(uri, value_a, ds, nrows, self.prepare, 20)
+ # Verify data is visible and correct.
+ self.check(value_a, uri, nrows, None, 21 if self.prepare else 20)
+
+ self.large_removes(uri, ds, nrows, self.prepare, 30)
+ # Verify no data is visible.
+ self.check(value_a, uri, 0, nrows, 31 if self.prepare else 30)
+
+ # Pin stable to timestamp 40 if prepare otherwise 30.
+ self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(40 if self.prepare else 30))
+
+ # Create a checkpoint thread
+ done = threading.Event()
+ ckpt = checkpoint_thread(self.conn, done)
+ try:
+ ckpt.start()
+
+ # Wait for checkpoint to start before committing.
+ ckpt_started = 0
+ while not ckpt_started:
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+ ckpt_started = stat_cursor[stat.conn.txn_checkpoint_running][2]
+ stat_cursor.close()
+ time.sleep(1)
+
+ # Perform several updates in parallel with checkpoint.
+ # Rollbacks may occur when checkpoint is running, so retry as needed.
+ self.retry_rollback('update ds, e', None,
+ lambda: self.large_updates(uri, value_b, ds, nrows, self.prepare, 50))
+ self.evict_cursor(uri, nrows, value_b)
+ finally:
+ done.set()
+ ckpt.join()
+
+ # Simulate a crash by copying to a new directory(RESTART).
+ self.restart_config = True
+ simulate_crash_restart(self, ".", "RESTART")
+
+ # Check that the correct data is seen at and after the stable timestamp.
+ self.check(value_a, uri, nrows, None, 21 if self.prepare else 20)
+ self.check(value_a, uri, 0, nrows, 40)
+
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+ hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2]
+ hs_sweep = stat_cursor[stat.conn.txn_rts_sweep_hs_keys][2]
+ keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2]
+ keys_restored = stat_cursor[stat.conn.txn_rts_keys_restored][2]
+ upd_aborted = stat_cursor[stat.conn.txn_rts_upd_aborted][2]
+ stat_cursor.close()
+
+ self.assertEqual(keys_removed, 0)
+ self.assertEqual(keys_restored, 0)
+ self.assertEqual(upd_aborted, 0)
+ self.assertEqual(hs_removed, 0)
+ self.assertEqual(hs_sweep, 0)
+
+ # Perform several updates.
+ self.large_updates(uri, value_c, ds, nrows, self.prepare, 60)
+
+ # Verify data is visible and correct.
+ self.check(value_c, uri, nrows, None, 61 if self.prepare else 60)
+
+ # Create a checkpoint thread
+ done = threading.Event()
+ ckpt = checkpoint_thread(self.conn, done)
+ try:
+ ckpt.start()
+
+ # Wait for checkpoint to start before committing.
+ ckpt_started = 0
+ while not ckpt_started:
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+ ckpt_started = stat_cursor[stat.conn.txn_checkpoint_running][2]
+ stat_cursor.close()
+ time.sleep(1)
+
+ self.evict_cursor(uri, nrows, value_c)
+ finally:
+ done.set()
+ ckpt.join()
+
+
+if __name__ == '__main__':
+ wttest.run()
+
diff --git a/src/third_party/wiredtiger/test/suite/test_rollback_to_stable40.py b/src/third_party/wiredtiger/test/suite/test_rollback_to_stable40.py
new file mode 100755
index 00000000000..07df57c2966
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_rollback_to_stable40.py
@@ -0,0 +1,164 @@
+#!/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 fnmatch, os, shutil, time
+from helper import copy_wiredtiger_home, simulate_crash_restart
+from test_rollback_to_stable01 import test_rollback_to_stable_base
+from wiredtiger import stat
+from wtdataset import SimpleDataSet
+from wtscenario import make_scenarios
+
+# test_rollback_to_stable40.py
+# Test the rollback to stable operation performs as expected following a server crash
+# and recovery. Verify that the on-disk value is replaced by the correct value from
+# the history store.
+class test_rollback_to_stable40(test_rollback_to_stable_base):
+ session_config = 'isolation=snapshot'
+
+ key_format_values = [
+ ('column', dict(key_format='r')),
+ ('integer_row', dict(key_format='i')),
+ ]
+
+ scenarios = make_scenarios(key_format_values)
+
+ def conn_config(self):
+ config = 'cache_size=1MB,statistics=(all),log=(enabled=true)'
+ return config
+
+ def test_rollback_to_stable(self):
+ nrows = 3
+
+ # Create a table without logging.
+ uri = "table:rollback_to_stable40"
+ ds = SimpleDataSet(
+ self, uri, 0, key_format=self.key_format, value_format="S", config='log=(enabled=false)')
+ ds.populate()
+
+ # Pin oldest and stable to timestamp 10.
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) +
+ ',stable_timestamp=' + self.timestamp_str(10))
+
+ value_a = "aaaaa" * 100
+ value_b = "bbbbb" * 100
+ value_c = "ccccc" * 100
+ value_d = "ddddd" * 100
+
+ # Insert 3 keys with same updates.
+ cursor = self.session.open_cursor(uri)
+ self.session.begin_transaction()
+ cursor[1] = value_a
+ cursor[2] = value_a
+ cursor[3] = value_a
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(20))
+
+ # Update the first and last key with another value with a large timestamp.
+ self.session.begin_transaction()
+ cursor[1] = value_d
+ cursor[3] = value_d
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(1000))
+
+ # Update the middle key with lot of updates to generate more history.
+ for i in range(21, 499):
+ self.session.begin_transaction()
+ cursor[2] = value_b + str(i)
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(i))
+
+ # With this checkpoint, all the updates in the history store are persisted to disk.
+ self.session.checkpoint()
+
+ self.session.begin_transaction()
+ cursor[2] = value_c
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(500))
+
+ # Pin oldest and stable to timestamp 500.
+ self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(500) +
+ ',stable_timestamp=' + self.timestamp_str(500))
+
+ # Evict the globally visible update to write to the disk, this will reset the time window.
+ evict_cursor = self.session.open_cursor(uri, None, "debug=(release_evict)")
+ self.session.begin_transaction("ignore_prepare=true")
+ evict_cursor.set_key(2)
+ self.assertEqual(evict_cursor[2], value_c)
+ evict_cursor.reset()
+ evict_cursor.close()
+ self.session.rollback_transaction()
+
+ self.session.begin_transaction()
+ cursor[2] = value_d
+ self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(501))
+
+ # 1. This checkpoint will move the globally visible update to the first of the key range.
+ # 2. The existing updates in the history store are having with a larger timestamp are
+ # obsolete, so they are not explicitly removed.
+ # 3. Any of the history store updates that are already evicted will not rewrite by the
+ # checkpoint.
+ self.session.checkpoint()
+
+ # Verify data is visible and correct.
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(1000))
+ for i in range (1, nrows + 1):
+ cursor.set_key(ds.key(i))
+ self.assertEqual(cursor.search(), 0)
+ self.assertEquals(cursor.get_value(), value_d)
+ self.session.rollback_transaction()
+ cursor.close()
+
+ # Simulate a server crash and restart.
+ simulate_crash_restart(self, ".", "RESTART")
+
+ # Verify data is visible and correct.
+ cursor = self.session.open_cursor(uri)
+ self.session.begin_transaction('read_timestamp=' + self.timestamp_str(1000))
+ for i in range (1, nrows + 1):
+ cursor.set_key(ds.key(i))
+ self.assertEqual(cursor.search(), 0)
+ if i % 2 == 0:
+ self.assertEquals(cursor.get_value(), value_c)
+ else:
+ self.assertEquals(cursor.get_value(), value_a)
+ self.session.rollback_transaction()
+
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+ calls = stat_cursor[stat.conn.txn_rts][2]
+ hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2]
+ keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2]
+ keys_restored = stat_cursor[stat.conn.txn_rts_keys_restored][2]
+ pages_visited = stat_cursor[stat.conn.txn_rts_pages_visited][2]
+ upd_aborted = stat_cursor[stat.conn.txn_rts_upd_aborted][2]
+ stat_cursor.close()
+
+ self.assertEqual(calls, 0)
+ self.assertEqual(keys_removed, 0)
+ self.assertEqual(keys_restored, 0)
+ self.assertGreaterEqual(upd_aborted, 0)
+ self.assertGreater(pages_visited, 0)
+ self.assertGreaterEqual(hs_removed, 3)
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_stat08.py b/src/third_party/wiredtiger/test/suite/test_stat08.py
index 7e91932d1d7..0b833c1934c 100644
--- a/src/third_party/wiredtiger/test/suite/test_stat08.py
+++ b/src/third_party/wiredtiger/test/suite/test_stat08.py
@@ -33,14 +33,29 @@ import wiredtiger, wttest
# Session statistics for bytes read into the cache.
class test_stat08(wttest.WiredTigerTestCase):
- nentries = 350000
- conn_config = 'cache_size=10MB,statistics=(all)'
- entry_value = "abcde" * 40
+ nentries = 100000
+ # Leave the cache size on the default setting to avoid filling up the cache
+ # too much and triggering unnecessary rollbacks. But make the value fairly
+ # large to make obvious change to the statistics.
+ conn_config = 'statistics=(all)'
+ entry_value = "abcde" * 400
BYTES_READ = wiredtiger.stat.session.bytes_read
READ_TIME = wiredtiger.stat.session.read_time
session_stats = { BYTES_READ : "session: bytes read into cache", \
READ_TIME : "session: page read from disk to cache time (usecs)"}
+ def get_stat(self, stat):
+ statc = self.session.open_cursor('statistics:session', None, None)
+ val = statc[stat][2]
+ statc.close()
+ return val
+
+ def get_cstat(self, stat):
+ statc = self.session.open_cursor('statistics:', None, None)
+ val = statc[stat][2]
+ statc.close()
+ return val
+
def check_stats(self, cur, k):
#
# Some Windows machines lack the time granularity to detect microseconds.
@@ -59,13 +74,33 @@ class test_stat08(wttest.WiredTigerTestCase):
self.assertTrue(value > 0)
def test_session_stats(self):
- self.session = self.conn.open_session()
- self.session.create("table:test_stat08",
- "key_format=i,value_format=S")
+ # We want to configure for pages to be explicitly evicted when we are done with them so
+ # that we can correctly verify the statistic measuring bytes read from cache.
+ self.session = self.conn.open_session("debug=(release_evict_page=true)")
+ self.session.create("table:test_stat08", "key_format=i,value_format=S")
cursor = self.session.open_cursor('table:test_stat08', None, None)
+ self.session.begin_transaction()
+ txn_dirty = self.get_stat(wiredtiger.stat.session.txn_bytes_dirty)
+ cache_dirty = self.get_cstat(wiredtiger.stat.conn.cache_bytes_dirty)
+ self.assertEqual(txn_dirty, 0)
+ self.assertLessEqual(txn_dirty, cache_dirty)
# Write the entries.
- for i in range(0, self.nentries):
+ for i in range(1, self.nentries):
+ txn_dirty_before = self.get_stat(wiredtiger.stat.session.txn_bytes_dirty)
cursor[i] = self.entry_value
+ txn_dirty_after = self.get_stat(wiredtiger.stat.session.txn_bytes_dirty)
+ self.assertLess(txn_dirty_before, txn_dirty_after)
+ # Since we're using an explicit transaction, we need to resolve somewhat frequently.
+ # So check the statistics and restart the transaction every 200 operations.
+ if i % 200 == 0:
+ cache_dirty_txn = self.get_cstat(wiredtiger.stat.conn.cache_bytes_dirty)
+ # Make sure the txn's dirty bytes doesn't exceed the cache.
+ self.assertLessEqual(txn_dirty_after, cache_dirty_txn)
+ self.session.rollback_transaction()
+ self.session.begin_transaction()
+ txn_dirty = self.get_stat(wiredtiger.stat.session.txn_bytes_dirty)
+ self.assertEqual(txn_dirty, 0)
+ self.session.commit_transaction()
cursor.reset()
# Read the entries.
diff --git a/src/third_party/wiredtiger/test/suite/test_sweep04.py b/src/third_party/wiredtiger/test/suite/test_sweep04.py
new file mode 100755
index 00000000000..a741639f82c
--- /dev/null
+++ b/src/third_party/wiredtiger/test/suite/test_sweep04.py
@@ -0,0 +1,252 @@
+#!/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.
+#
+# test_sweep04.py
+# Test lots of tables with more steadily created and dropped.
+# A core group of tables is used most often and hangs around.
+# Test that the total number of dhandles, while increasing,
+# starts to level off, that is, the sweeps are keeping up.
+# Then test that if we only access the core tables for a while,
+# the total number of dhandles comes back down to a small number.
+
+import time
+from suite_random import suite_random
+from wiredtiger import stat
+import wttest
+
+# Given a set of values corresponding to successive times,
+# we have an implied set of points in two dimensions.
+# Compute the average for the values and the slope for the
+# least squares regression of the line.
+#
+# We'd use numpy, but we aren't assured that it is always installed
+# for our python.
+def average_slope(y):
+ n = len(y)
+ if n == 0:
+ return [0, 0] # there's no average or slope
+ elif n == 1:
+ return [y[0], 0] # there's no slope
+
+ average = sum(y) / n
+ x = range(1, n + 1) # The implied x axis, steadily increasing integers.
+
+ # Here's a formula for least squares slope:
+ # https://www.mathsisfun.com/data/least-squares-regression.html
+ top = n * sum(x[i] * y[i] for i in range(n)) - sum(x) * sum(y)
+ bottom = n * sum(x[i]**2 for i in range(n)) - sum(x)**2
+ slope = top / bottom
+ return [average, slope]
+
+@wttest.longtest("lots of files")
+class test_sweep04(wttest.WiredTigerTestCase):
+ tablebase = 'test_sweep04'
+ uri = 'table:' + tablebase
+
+ # Configuration values for the run. If any of these values are changed,
+ # the simulation will run, but the acceptance criteria may fail at the end.
+ core_tables = 10 # Number of core tables that always exist.
+ transient_tables = 100 # Number of transient tables at any time.
+ transient_examined = 10 # Number of transient tables opened at a time.
+ ratio_examined = 0.01 # The chance that transient tables are examined.
+ transient_table_max = 10000 # Defines the length of the run.
+ numkv = 1 # Number of k/v pairs. Shouldn't matter for this test.
+ nsessions = 100 # Number of sessions in our pool.
+
+ conn_config = 'file_manager=(close_handle_minimum=0,' + \
+ 'close_idle_time=3,close_scan_interval=1),' + \
+ 'statistics=(fast),operation_tracking=(enabled=false),'
+
+ create_params = 'key_format=i,value_format=i'
+
+ # Create a uri for one of the core tables
+ def core_uri(self, i):
+ return '%s-c.%d' % (self.uri, i)
+
+ # Create a uri for one of the transient tables
+ def transient_uri(self, i):
+ return '%s-t.%d' % (self.uri, i)
+
+ def create_table(self, uri):
+ self.session.create(uri, self.create_params)
+ c = self.session.open_cursor(uri, None)
+ for k in range(self.numkv):
+ c[k+1] = 1
+ c.close()
+
+ # Access a set of tables in some minimal way that ensures its
+ # dhandle is at least momentarily in use. "uri_maker" is a
+ # function that is used to create the uri.
+ def examine(self, session, uri_maker, start, count):
+ for i in range(0, count):
+ c = session.open_cursor(uri_maker(start + i))
+ self.assertEquals(c[1], 1)
+ c.close()
+
+ def test_big_run(self):
+ # populate
+ r = suite_random()
+
+ # Create the set of core tables
+ for i in range(0, self.core_tables):
+ self.create_table(self.core_uri(i))
+
+ created_transient = 0 # Running total of transients created.
+ available_transient = 0 # The next available transient number.
+
+ # Create the initial batch of transient tables.
+ while created_transient < self.transient_tables:
+ self.create_table(self.transient_uri(created_transient))
+ created_transient += 1
+
+ # Open all the session we'll use in advance.
+ sessions = []
+ for i in range(self.nsessions):
+ sessions.append(self.conn.open_session())
+
+ # We keep the dhandle counts for each time we get stats.
+ dhandle_counts = []
+
+ # The big loop: For half the run, we are stressing by accessing both core tables
+ # and transient tables, and creating/dropping transient tables. The second half
+ # of the run, we stop creating/dropping tables and only access core tables
+ # just to see if all outstanding dhandles are swept.
+ maxloop = self.transient_table_max * 2
+ lasttime = time.time()
+ for loopcount in range(0, maxloop):
+ stressing = (created_transient < self.transient_table_max)
+ if loopcount % 100 == 0:
+ self.pr('{}/{} stressing={}'.format(loopcount, maxloop, stressing))
+
+ # Make sure at least 3 seconds elapses between each 100 times through
+ # the loop, to give the various sweeps time to operate.
+ thistime = time.time()
+ delta = thistime - lasttime
+ if delta < 3.0:
+ time.sleep(3.0 - delta)
+ lasttime = thistime
+
+ if stressing:
+ self.session.drop(self.transient_uri(available_transient), "force")
+ available_transient += 1
+ self.create_table(self.transient_uri(created_transient))
+ created_transient += 1
+
+ rand_session = sessions[r.rand_range(0, self.nsessions)]
+
+ # In the stress part of the run, some small fraction (given by ratio_examined) will
+ # look at the transient tables. Looking at these rarely makes them candidates for
+ # closing by the connection sweep.
+ big = 1000000 # Any large number works.
+ if stressing and r.rand32() % big > big * self.ratio_examined:
+ # Access "count" transient tables, starting at table "tnum".
+ count = self.transient_examined
+ tnum = r.rand_range(available_transient, available_transient + self.transient_tables - count)
+ self.examine(rand_session, self.transient_uri, tnum, count)
+ else:
+ # Access a single core table, numbered "tnum".
+ tnum = r.rand_range(0, self.core_tables)
+ self.examine(rand_session, self.core_uri, tnum, 1)
+
+ if loopcount % 100 == 99:
+ # Gather statistics about the number of dhandles, to be checked at
+ # the end of the run.
+ stat_cursor = self.session.open_cursor('statistics:', None, None)
+
+ # Enable for detailed output.
+ if False:
+ close = stat_cursor[stat.conn.dh_sweep_close][2]
+ remove = stat_cursor[stat.conn.dh_sweep_remove][2]
+ sweep = stat_cursor[stat.conn.dh_sweeps][2]
+ sclose = stat_cursor[stat.conn.dh_session_handles][2]
+ ssweep = stat_cursor[stat.conn.dh_session_sweeps][2]
+ tod = stat_cursor[stat.conn.dh_sweep_tod][2]
+ ref = stat_cursor[stat.conn.dh_sweep_ref][2]
+ self.pr(('DHANDLE STATS: close={}, remove={}, sweep={}, session_handles={}, '+
+ 'session_sweeps={}, sweep_tod={}, sweep_ref={}').format(
+ close, remove, sweep, sclose, ssweep, tod, ref))
+
+ dhandles = stat_cursor[stat.conn.dh_conn_handle_count][2]
+ files_open = stat_cursor[stat.conn.file_open][2]
+ self.pr(' dhandle_count={}'.format(dhandles))
+ self.pr(' file_open={}'.format(files_open))
+ dhandle_counts.append(dhandles)
+ stat_cursor.close()
+
+ # This (extremely verbose) debugging is disabled, as it writes to stdout,
+ # and that causes the test framework fail the test.
+ if False:
+ if loopcount % 1000 == 999:
+ self.conn.debug_info('handles=true')
+ self.pr(' average,slope={}'.format(str(average_slope(dhandle_counts))))
+
+ # Reset any sessions we've used. This may be necessary to trigger some session sweeps.
+ rand_session.reset()
+ self.session.reset()
+
+ # The run is finished, process and check the dhandle counts we've collected.
+ #
+ # Half the run is dhandle growth, and the second half should see a decline.
+ # If everything is working right in the first half of the run, we should start
+ # to see the number of dhandles start to reach an asymptote. So we check the slope of
+ # the dhandle line in the second quarter to see that is the case. In the second
+ # half of the run, where we aren't accessing the transient tables, those references
+ # should free up, and we should see an asymptote at the end much closer to the
+ # number of files.
+ half = len(dhandle_counts)//2
+ qtr = len(dhandle_counts)//4
+ tenth = len(dhandle_counts)//10
+
+ (q1_avg, q1_slope) = average_slope(dhandle_counts[0:qtr])
+ (q2_avg, q2_slope) = average_slope(dhandle_counts[qtr:half])
+ (q3_avg, q3_slope) = average_slope(dhandle_counts[half:-qtr])
+ (q4_avg, q4_slope) = average_slope(dhandle_counts[-qtr:])
+ (end_run_avg, end_run_slope) = average_slope(dhandle_counts[-tenth:])
+
+ self.pr('1st qtr: average={},slope={}'.format(q1_avg, q1_slope))
+ self.pr('2nd qtr: average={},slope={}'.format(q2_avg, q2_slope))
+ self.pr('3rd qtr: average={},slope={}'.format(q3_avg, q3_slope))
+ self.pr('4th qtr: average={},slope={}'.format(q4_avg, q4_slope))
+
+ self.pr('end run: average={},slope={}'.format(end_run_avg, end_run_slope))
+
+ # Note, we don't check the first half average, it's likely to be big, but its size
+ # depends on many factors. The important thing is that the slope has flattened out.
+ # Even with variations due to sweep timing, the slope shouldn't be greater than
+ # 15.0 (dhandles per 100 times through the loop).
+ self.assertLess(abs(q2_slope), q1_slope)
+ self.assertLess(abs(q2_slope), 15.0)
+
+ # At the end of the run, we expect a pretty flat slope and a pretty small number
+ # of dhandles. A slope of 5.0 (dhandles per 100 times though the loop) is rather
+ # flat and still leaves room for some variation.
+ self.assertLess(abs(end_run_slope), 5.0)
+ self.assertLess(end_run_avg, self.core_tables + self.transient_tables + 20)
+
+if __name__ == '__main__':
+ wttest.run()
diff --git a/src/third_party/wiredtiger/test/suite/test_tiered05.py b/src/third_party/wiredtiger/test/suite/test_tiered05.py
index 8b638788736..c109596361f 100644
--- a/src/third_party/wiredtiger/test/suite/test_tiered05.py
+++ b/src/third_party/wiredtiger/test/suite/test_tiered05.py
@@ -71,8 +71,7 @@ class test_tiered05(wttest.WiredTigerTestCase):
'tiered_storage=(auth_token=%s,' % self.auth_token + \
'bucket=%s,' % self.bucket + \
'bucket_prefix=%s,' % self.bucket_prefix + \
- 'name=%s,' % self.ss_name + \
- 'object_target_size=20M)'
+ 'name=%s)' % self.ss_name
# Test calling the flush_tier API with a tiered manager. Should get an error.
def test_tiered(self):
diff --git a/src/third_party/wiredtiger/test/suite/test_tiered06.py b/src/third_party/wiredtiger/test/suite/test_tiered06.py
index c4e931c7a3f..d876b305c75 100755
--- a/src/third_party/wiredtiger/test/suite/test_tiered06.py
+++ b/src/third_party/wiredtiger/test/suite/test_tiered06.py
@@ -58,7 +58,7 @@ class test_tiered06(wttest.WiredTigerTestCase):
config = ''
# S3 store is built as an optional loadable extension, not all test environments build S3.
if self.ss_name == 's3_store':
- #config = '=(config=\"(verbose=1)\")'
+ #config = '=(config=\"(verbose=[api:1,version,tiered:1])\")'
extlist.skip_if_missing = True
#if self.ss_name == 'dir_store':
#config = '=(config=\"(verbose=1,delay_ms=200,force_delay=3)\")'
@@ -104,22 +104,14 @@ class test_tiered06(wttest.WiredTigerTestCase):
self.get_fs_config(prefix))
# The object doesn't exist yet.
- if self.ss_name == 's3_store':
- with self.expectedStderrPattern('.*HTTP response code: 404.*'):
- self.assertFalse(fs.fs_exist(session, 'foobar'))
- else:
- self.assertFalse(fs.fs_exist(session, 'foobar'))
+ self.assertFalse(fs.fs_exist(session, 'foobar'))
# We cannot use the file system to create files, it is readonly.
# So use python I/O to build up the file.
f = open('foobar', 'wb')
# The object still doesn't exist yet.
- if self.ss_name == 's3_store':
- with self.expectedStderrPattern('.*HTTP response code: 404.*'):
- self.assertFalse(fs.fs_exist(session, 'foobar'))
- else:
- self.assertFalse(fs.fs_exist(session, 'foobar'))
+ self.assertFalse(fs.fs_exist(session, 'foobar'))
outbytes = ('MORE THAN ENOUGH DATA\n'*100000).encode()
f.write(outbytes)
diff --git a/src/third_party/wiredtiger/test/suite/test_tiered14.py b/src/third_party/wiredtiger/test/suite/test_tiered14.py
index 61e3a54cf91..c041bb11635 100644
--- a/src/third_party/wiredtiger/test/suite/test_tiered14.py
+++ b/src/third_party/wiredtiger/test/suite/test_tiered14.py
@@ -28,7 +28,7 @@
from helper_tiered import generate_s3_prefix, get_auth_token, get_bucket1_name
from wtscenario import make_scenarios
-import os, random, wtscenario, wttest
+import os, random, wttest
from wtdataset import TrackedSimpleDataSet, TrackedComplexDataSet
# test_tiered14.py
@@ -69,7 +69,7 @@ class test_tiered14(wttest.WiredTigerTestCase):
num_ops = 20,
ss_name = 's3_store')),
]
- scenarios = wtscenario.make_scenarios(multiplier, keyfmt, dataset, storage_sources)
+ scenarios = make_scenarios(multiplier, keyfmt, dataset, storage_sources)
def conn_config(self):
if self.ss_name == 'dir_store' and not os.path.exists(self.bucket):
@@ -86,7 +86,7 @@ class test_tiered14(wttest.WiredTigerTestCase):
config = ''
# S3 store is built as an optional loadable extension, not all test environments build S3.
if self.ss_name == 's3_store':
- #config = '=(config=\"(verbose=1)\")'
+ #config = '=(config=\"(verbose=[api:1,version,tiered:-3])\")'
extlist.skip_if_missing = True
#if self.ss_name == 'dir_store':
#config = '=(config=\"(verbose=1,delay_ms=200,force_delay=3)\")'
diff --git a/src/third_party/wiredtiger/test/suite/wttest.py b/src/third_party/wiredtiger/test/suite/wttest.py
index edd22e346c7..a751231f334 100755
--- a/src/third_party/wiredtiger/test/suite/wttest.py
+++ b/src/third_party/wiredtiger/test/suite/wttest.py
@@ -42,7 +42,7 @@ except ImportError:
import unittest
from contextlib import contextmanager
-import errno, glob, os, re, shutil, sys, time, traceback
+import errno, glob, os, re, shutil, sys, threading, time, traceback, types
import wiredtiger, wtscenario, wthooks
def shortenWithEllipsis(s, maxlen):
@@ -180,6 +180,38 @@ class ExtensionList(list):
ext = '' if extarg == None else '=' + extarg
self.append(dirname + '/' + name + ext)
+# Custom result class that will prefix the pid in text output (including if it's a child).
+# Only enabled when we are in verbose mode so we don't check that here.
+class PidAwareTextTestResult(unittest.TextTestResult):
+ _thread_prefix = threading.local()
+
+ def __init__(self, stream, descriptions, verbosity):
+ super(PidAwareTextTestResult, self).__init__(stream, descriptions, verbosity)
+ self._thread_prefix.value = "[pid:{}]: ".format(os.getpid())
+
+ def tags(self, new_tags, gone_tags):
+ # We attach the PID to the thread so we only need the new_tags.
+ for tag in new_tags:
+ if tag.startswith("pid:"):
+ pid = tag[len("pid:"):]
+ self._thread_prefix.value = "[pid:{}/{}]: ".format(os.getpid(), pid)
+
+ def startTest(self, test):
+ self.stream.write(self._thread_prefix.value)
+ super(PidAwareTextTestResult, self).startTest(test)
+
+ def getDescription(self, test):
+ return str(test.shortDescription())
+
+ def printErrorList(self, flavour, errors):
+ for test, err in errors:
+ self.stream.writeln(self.separator1)
+ self.stream.writeln("%s%s: %s" % (self._thread_prefix.value,
+ flavour, self.getDescription(test)))
+ self.stream.writeln(self.separator2)
+ self.stream.writeln("%s%s" % (self._thread_prefix.value, err))
+ self.stream.flush()
+
class WiredTigerTestCase(unittest.TestCase):
_globalSetup = False
_printOnceSeen = {}
@@ -536,8 +568,11 @@ class WiredTigerTestCase(unittest.TestCase):
# In addition, check to make sure exc_info is "clean", because
# the ConcurrencyTestSuite in Python2 indicates failures using that.
if hasattr(self, '_outcome'): # Python 3.4+
- result = self.defaultTestResult() # these 2 methods have no side effects
- self._feedErrorsToResult(result, self._outcome.errors)
+ if hasattr(self._outcome, 'errors'): # Python 3.4 - 3.10
+ result = self.defaultTestResult() # these 2 methods have no side effects
+ self._feedErrorsToResult(result, self._outcome.errors)
+ else: # Python 3.11+
+ result = self._outcome.result
else: # Python 3.2 - 3.3 or 3.0 - 3.1 and 2.7
result = getattr(self, '_outcomeForDoCleanups', self._resultForDoCleanups)
error = self.list2reason(result, 'errors')
@@ -586,9 +621,9 @@ class WiredTigerTestCase(unittest.TestCase):
elapsed = time.time() - self.starttime
if elapsed > 0.001 and WiredTigerTestCase._verbose >= 2:
- print("%s: %.2f seconds" % (str(self), elapsed))
+ print("[pid:{}]: {}: {:.2f} seconds".format(os.getpid(), str(self), elapsed))
if (not passed) and (not self.skipped):
- print("ERROR in " + str(self))
+ print("[pid:{}]: ERROR in {}".format(os.getpid(), str(self)))
self.pr('FAIL')
self.pr('preserving directory ' + self.testdir)
if WiredTigerTestCase._verbose > 2:
@@ -834,7 +869,7 @@ class WiredTigerTestCase(unittest.TestCase):
@staticmethod
def prout(s):
- os.write(WiredTigerTestCase._dupout, str.encode(s + '\n'))
+ os.write(WiredTigerTestCase._dupout, str.encode("[pid:{}]: {}\n".format(os.getpid(), s)))
def pr(self, s):
"""
@@ -873,7 +908,7 @@ class WiredTigerTestCase(unittest.TestCase):
def tty(message):
if WiredTigerTestCase._ttyDescriptor == None:
WiredTigerTestCase._ttyDescriptor = open('/dev/tty', 'w')
- WiredTigerTestCase._ttyDescriptor.write(message + '\n')
+ WiredTigerTestCase._ttyDescriptor.write("[pid:{}]: {}\n".format(os.getpid(), message))
def ttyVerbose(self, level, message):
WiredTigerTestCase.ttyVerbose(level, message)
@@ -935,6 +970,16 @@ def islongtest():
def getseed():
return WiredTigerTestCase._seeds
+# We have to override the ThreadsafeForwardingResult implementation of tags so it gets set immediately
+# which allows us to set the pid of the process on our output stream to make debugging easier.
+def immediate_tags(self, new_tags, gone_tags):
+ self.result.tags(new_tags, gone_tags)
+
+def wrap_result_for_tags(thread_safe_result, thread_number):
+ # We use this technique to override the method instead of extending the class as it allows for less changes.
+ thread_safe_result.tags = types.MethodType(immediate_tags, thread_safe_result)
+ return thread_safe_result
+
def runsuite(suite, parallel):
suite_to_run = suite
if parallel > 1:
@@ -942,16 +987,19 @@ def runsuite(suite, parallel):
if not WiredTigerTestCase._globalSetup:
WiredTigerTestCase.globalSetup()
WiredTigerTestCase._concurrent = True
- suite_to_run = ConcurrentTestSuite(suite, fork_for_tests(parallel))
+ suite_to_run = ConcurrentTestSuite(suite, fork_for_tests(parallel), wrap_result=wrap_result_for_tags)
try:
if WiredTigerTestCase._randomseed:
WiredTigerTestCase.prout("Starting test suite with seedw={0} and seedz={1}. Rerun this test with -seed {0}.{1} to get the same randomness"
.format(str(WiredTigerTestCase._seeds[0]), str(WiredTigerTestCase._seeds[1])))
+ result_class = None
+ if WiredTigerTestCase._verbose > 1:
+ result_class = PidAwareTextTestResult
return unittest.TextTestRunner(
- verbosity=WiredTigerTestCase._verbose).run(suite_to_run)
+ verbosity=WiredTigerTestCase._verbose, resultclass=result_class).run(suite_to_run)
except BaseException as e:
# This should not happen for regular test errors, unittest should catch everything
- print('ERROR: running test: ', e)
+ print("[pid:{}]: ERROR: running test: {}".format(os.getpid(), e))
raise e
def run(name='__main__'):