-
Type: Bug
-
Resolution: Fixed
-
Priority: Major - P3
-
Affects Version/s: None
-
Component/s: None
-
Storage Engines
-
5
-
2023-11-28 - Anthill Tiger, 2023-12-12 - Heisenbug
Calling next() on a bounded cursor after receiving a WT_PREPARE_CONFLICT continues to advance the cursor past the conflicting key. This is in contrast to the behavior of cursors without bounds, which return WT_PREPARE_CONFLICT after every call to next() until the conflict is resolved.
Passing test (no cursor bounds):
TEST_F(WiredTigerUtilTest, SkipPreparedUpdateNoBound) { // Initialize WiredTiger. WiredTigerEventHandler eventHandler; WiredTigerUtilHarnessHelper harnessHelper("", &eventHandler); WT_SESSION* session1; ASSERT_OK(wtRCToStatus( harnessHelper.getSessionCache()->conn()->open_session( harnessHelper.getSessionCache()->conn(), nullptr, "isolation=snapshot", &session1), nullptr)); WT_SESSION* session2; ASSERT_OK(wtRCToStatus( harnessHelper.getSessionCache()->conn()->open_session( harnessHelper.getSessionCache()->conn(), nullptr, "isolation=snapshot", &session2), nullptr)); const std::string uri = "table:test"; ASSERT_OK(wtRCToStatus(session1->create(session1, uri.c_str(), "key_format=S,value_format=S"), session1)); WT_CURSOR* cursor1; ASSERT_EQ(0, session1->begin_transaction(session1, "ignore_prepare=false")); ASSERT_EQ(0, session1->open_cursor(session1, uri.c_str(), nullptr, nullptr, &cursor1)); cursor1->set_key(cursor1, "abc"); cursor1->set_value(cursor1, "test"); ASSERT_EQ(0, cursor1->insert(cursor1)); session1->prepare_transaction(session1, "prepare_timestamp=1"); WT_CURSOR* cursor2; ASSERT_EQ(0, session2->begin_transaction(session2, "ignore_prepare=false")); ASSERT_EQ(0, session2->open_cursor(session2, uri.c_str(), nullptr, nullptr, &cursor2)); // Continuously returns WT_PREPARE_CONFLICT ASSERT_EQ(WT_PREPARE_CONFLICT, cursor2->next(cursor2)); ASSERT_EQ(WT_PREPARE_CONFLICT, cursor2->next(cursor2)); ASSERT_EQ(WT_PREPARE_CONFLICT, cursor2->next(cursor2)); }
Failing test (with cursor bounds):
TEST_F(WiredTigerUtilTest, SkipPreparedUpdateBounded) { // Initialize WiredTiger. WiredTigerEventHandler eventHandler; WiredTigerUtilHarnessHelper harnessHelper("", &eventHandler); WT_SESSION* session1; ASSERT_OK(wtRCToStatus( harnessHelper.getSessionCache()->conn()->open_session( harnessHelper.getSessionCache()->conn(), nullptr, "isolation=snapshot", &session1), nullptr)); WT_SESSION* session2; ASSERT_OK(wtRCToStatus( harnessHelper.getSessionCache()->conn()->open_session( harnessHelper.getSessionCache()->conn(), nullptr, "isolation=snapshot", &session2), nullptr)); const std::string uri = "table:test"; ASSERT_OK(wtRCToStatus(session1->create(session1, uri.c_str(), "key_format=S,value_format=S"), session1)); WT_CURSOR* cursor1; ASSERT_EQ(0, session1->begin_transaction(session1, "ignore_prepare=false")); ASSERT_EQ(0, session1->open_cursor(session1, uri.c_str(), nullptr, nullptr, &cursor1)); cursor1->set_key(cursor1, "abc"); cursor1->set_value(cursor1, "test"); ASSERT_EQ(0, cursor1->insert(cursor1)); session1->prepare_transaction(session1, "prepare_timestamp=1"); WT_CURSOR* cursor2; ASSERT_EQ(0, session2->begin_transaction(session2, "ignore_prepare=false")); ASSERT_EQ(0, session2->open_cursor(session2, uri.c_str(), nullptr, nullptr, &cursor2)); cursor2->set_key(cursor2, "abc"); cursor2->bound(cursor2, "bound=lower"); ASSERT_EQ(WT_PREPARE_CONFLICT, cursor2->next(cursor2)); ASSERT_EQ(WT_PREPARE_CONFLICT, cursor2->next(cursor2)); // Returns WT_NOTFOUND }
This is problematic because we depend on being able to retry WT API calls as many times as necessary until they stop returning WT_PREPARE_CONFLICT. This pattern is everywhere in our code:
do { auto ret = cursor->next(); } while (ret == WT_PREPARE_CONFLICT);
Not being able to repeatedly call a cursor API call after a prepare conflict means that we may skip records because we don't know when it is safe to stop.
- is depended on by
-
SERVER-68380 Always use bounded WT cursors instead of search_near
- Closed