This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new a41e9436b59 branch-4.2: [fix](cloud) Restart incomplete delete bitmap 
reads after transaction expiry #68100 (#69307)
a41e9436b59 is described below

commit a41e9436b59597438c61b6915ad05f1d404f6dee
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Oct 20 14:44:00 2026 +0800

    branch-4.2: [fix](cloud) Restart incomplete delete bitmap reads after 
transaction expiry #68100 (#69307)
    
    Cherry-picked from #68100
    
    Co-authored-by: meiyi <[email protected]>
---
 cloud/src/meta-service/meta_service.cpp |  86 ++++----
 cloud/test/meta_service_test.cpp        | 336 ++++++++++++++++++++++++++++++++
 2 files changed, 385 insertions(+), 37 deletions(-)

diff --git a/cloud/src/meta-service/meta_service.cpp 
b/cloud/src/meta-service/meta_service.cpp
index dd5dc3cd05e..2a6e71fe35d 100644
--- a/cloud/src/meta-service/meta_service.cpp
+++ b/cloud/src/meta-service/meta_service.cpp
@@ -4573,10 +4573,15 @@ void 
MetaServiceImpl::get_delete_bitmap(google::protobuf::RpcController* control
             std::unique_ptr<RangeGetIterator> it;
             int64_t last_ver = -1;
             int64_t last_seg_id = -1;
-            std::string last_delete_bitmap_first_blob_key;
+            std::string bitmap_start_key = start_key;
+            int bitmap_response_size = response->rowset_ids_size();
+            int64_t bitmap_num_before = delete_bitmap_num;
+            int64_t bitmap_bytes_before = delete_bitmap_byte;
             uint16_t next_blob_sequence = 0;
+            size_t first_blob_size = 0;
             bool skip_last_delete_bitmap = false;
             int64_t round = 0;
+            int64_t retry = 0;
             while (it == nullptr /* may be not init */ || it->more()) {
                 if (test) {
                     LOG(INFO) << "test";
@@ -4585,8 +4590,21 @@ void 
MetaServiceImpl::get_delete_bitmap(google::protobuf::RpcController* control
                     err = txn->get(start_key, end_key, &it);
                 }
                 TEST_SYNC_POINT_CALLBACK("get_delete_bitmap_err", &round, 
&err);
-                int64_t retry = 0;
-                while (err == TxnErrorCode::TXN_TOO_OLD && retry < 3) {
+                if (err == TxnErrorCode::TXN_TOO_OLD && retry < 3) {
+                    // Only earlier bitmaps are known to be complete. Never 
carry the page-tail
+                    // bitmap across snapshots: aggregation can replace it 
under the same key.
+                    while (response->rowset_ids_size() > bitmap_response_size) 
{
+                        response->mutable_rowset_ids()->RemoveLast();
+                        response->mutable_segment_ids()->RemoveLast();
+                        response->mutable_versions()->RemoveLast();
+                        
response->mutable_segment_delete_bitmaps()->RemoveLast();
+                    }
+                    delete_bitmap_num = bitmap_num_before;
+                    delete_bitmap_byte = bitmap_bytes_before;
+                    start_key = bitmap_start_key;
+                    it.reset();
+                    last_ver = -1;
+                    last_seg_id = -1;
                     stats.get_bytes += txn->get_bytes();
                     stats.get_counter += txn->num_get_keys();
                     txn = nullptr;
@@ -4597,41 +4615,14 @@ void 
MetaServiceImpl::get_delete_bitmap(google::protobuf::RpcController* control
                         msg = ss.str();
                         return;
                     }
-                    if (!skip_last_delete_bitmap && 
!last_delete_bitmap_first_blob_key.empty()) {
-                        std::string first_blob_value;
-                        err = txn->get(last_delete_bitmap_first_blob_key, 
&first_blob_value);
-                        if (err == TxnErrorCode::TXN_KEY_NOT_FOUND) {
-                            delete_bitmap_byte -=
-                                    response->segment_delete_bitmaps(
-                                                    
response->segment_delete_bitmaps_size() - 1)
-                                            .size();
-                            delete_bitmap_num--;
-                            response->mutable_rowset_ids()->RemoveLast();
-                            response->mutable_segment_ids()->RemoveLast();
-                            response->mutable_versions()->RemoveLast();
-                            
response->mutable_segment_delete_bitmaps()->RemoveLast();
-                            skip_last_delete_bitmap = true;
-                            LOG(WARNING)
-                                    << "skip incomplete delete bitmap whose 
first blob key "
-                                       "disappeared after transaction retry"
-                                    << ", tablet_id=" << tablet_id
-                                    << ", rowset_id=" << rowset_ids[i] << ", 
version=" << last_ver
-                                    << ", segment_id=" << last_seg_id;
-                        } else if (err != TxnErrorCode::TXN_OK) {
-                            retry++;
-                            continue;
-                        }
-                    }
-                    if (test) {
-                        err = txn->get(start_key, end_key, &it, false, 2);
-                    } else {
-                        err = txn->get(start_key, end_key, &it);
-                    }
                     retry++;
                     LOG(INFO) << "retry get delete bitmap, tablet=" << 
tablet_id
+                              << ", rowset=" << rowset_ids[i]
+                              << ", bitmap_start_key=" << hex(bitmap_start_key)
                               << ", retry=" << retry << ", internal round=" << 
round
                               << ", delete_bitmap_num=" << delete_bitmap_num
                               << ", delete_bitmap_byte=" << delete_bitmap_byte;
+                    continue;
                 }
                 if (err != TxnErrorCode::TXN_OK) {
                     code = cast_as<ErrCategory::READ>(err);
@@ -4664,6 +4655,18 @@ void 
MetaServiceImpl::get_delete_bitmap(google::protobuf::RpcController* control
                     // FIXME: Don't expose the implementation details of 
splitting large value.
                     // merge splitted large values (>90*1000)
                     if (ver != last_ver || seg_id != last_seg_id) {
+                        auto key = meta_delete_bitmap_key(
+                                {instance_id, tablet_id, rowset_ids[i], ver, 
seg_id});
+                        // A successful page is not progress if a retry will 
reread the same
+                        // bitmap. Reset the budget only when its logical 
start moves forward.
+                        if (key != bitmap_start_key) {
+                            DCHECK(key > bitmap_start_key);
+                            retry = 0;
+                            bitmap_start_key = std::move(key);
+                        }
+                        bitmap_response_size = response->rowset_ids_size();
+                        bitmap_num_before = delete_bitmap_num;
+                        bitmap_bytes_before = delete_bitmap_byte;
                         auto sequence =
                                 out.size() == 8
                                         ? static_cast<uint16_t>(
@@ -4671,7 +4674,7 @@ void 
MetaServiceImpl::get_delete_bitmap(google::protobuf::RpcController* control
                                         : uint16_t {0};
                         last_ver = ver;
                         last_seg_id = seg_id;
-                        last_delete_bitmap_first_blob_key.clear();
+                        first_blob_size = v.size();
                         next_blob_sequence = static_cast<uint16_t>(sequence + 
1);
                         // Key-based pre-rowset cleanup may leave an obsolete 
tail if its size estimate is
                         // too small. The bitmap is already aggregated, so the 
tail can be skipped and will
@@ -4686,9 +4689,6 @@ void 
MetaServiceImpl::get_delete_bitmap(google::protobuf::RpcController* control
                                     << ", segment_id=" << seg_id << ", 
first_sequence=" << sequence;
                             continue;
                         }
-                        if (out.size() == 8) {
-                            last_delete_bitmap_first_blob_key.assign(k.data(), 
k.size());
-                        }
                         response->add_rowset_ids(rowset_ids[i]);
                         response->add_segment_ids(seg_id);
                         response->add_versions(ver);
@@ -4726,6 +4726,18 @@ void 
MetaServiceImpl::get_delete_bitmap(google::protobuf::RpcController* control
                         delete_bitmap_byte += v.length();
                         
response->mutable_segment_delete_bitmaps()->rbegin()->append(v);
                     }
+                    // blob_put uses equal-sized chunks except for the last, 
with a minimum
+                    // split size. A legacy value or a short chunk is 
therefore complete.
+                    if (out.size() == 7 || v.size() < MIN_BLOB_SPLIT_SIZE ||
+                        v.size() < first_blob_size) {
+                        // Skip this entire logical bitmap on retry, including 
any new fragments
+                        // written by a concurrent replacement after this 
snapshot.
+                        encode_int64(INT64_MAX, &bitmap_start_key);
+                        bitmap_response_size = response->rowset_ids_size();
+                        bitmap_num_before = delete_bitmap_num;
+                        bitmap_bytes_before = delete_bitmap_byte;
+                        retry = 0;
+                    }
                 }
                 check_delete_bitmap_bytes_exceed_limit();
                 if (code != MetaServiceCode::OK) return;
diff --git a/cloud/test/meta_service_test.cpp b/cloud/test/meta_service_test.cpp
index f66fb04c262..e0d91a4c863 100644
--- a/cloud/test/meta_service_test.cpp
+++ b/cloud/test/meta_service_test.cpp
@@ -8080,6 +8080,342 @@ TEST(MetaServiceTest, 
GetDeleteBitmapDropsPartialValueAfterAllBlobKeysAreRemoved
     test_get_delete_bitmap_during_point_cleanup(DEFAULT_BLOB_SPLIT_SIZE * 3 + 
1);
 }
 
+TEST(MetaServiceTest, GetDeleteBitmapReturnsCompleteReplacementAfterTxnTooOld) 
{
+    for (size_t replacement_size : {size_t {1}, DEFAULT_BLOB_SPLIT_SIZE * 3}) {
+        SCOPED_TRACE(fmt::format("replacement_size={}", replacement_size));
+        const bool read_v2 = replacement_size != 1;
+        auto meta_service = get_meta_service();
+        auto sp = SyncPoint::get_instance();
+        DORIS_CLOUD_DEFER {
+            sp->clear_all_call_backs();
+            sp->disable_processing();
+        };
+
+        extern std::string get_instance_id(const 
std::shared_ptr<ResourceManager>& rc_mgr,
+                                           const std::string& cloud_unique_id);
+        auto instance_id = get_instance_id(meta_service->resource_mgr(), 
"test_cloud_unique_id");
+        constexpr int64_t tablet_id = 652;
+        const std::string rowset_id = "replacement_during_paginated_read";
+        put_delete_bitmap_test_rowset(meta_service.get(), instance_id, 
tablet_id, 1, rowset_id);
+
+        auto bitmap_key = meta_delete_bitmap_key({instance_id, tablet_id, 
rowset_id, 2, 0});
+        std::string old_bitmap(DEFAULT_BLOB_SPLIT_SIZE, 'a');
+        old_bitmap.append(DEFAULT_BLOB_SPLIT_SIZE, 'b');
+        old_bitmap.append(DEFAULT_BLOB_SPLIT_SIZE, 'c');
+        const std::string previous_rowset = "completed_rowset";
+        const std::string following_rowset = "following_rowset";
+        const std::string completed_bitmap = "completed_bitmap";
+        DeleteBitmapStoragePB v2_storage;
+        v2_storage.set_store_in_fdb(true);
+        auto* v2_bitmap = v2_storage.mutable_delete_bitmap();
+        v2_bitmap->add_rowset_ids(previous_rowset);
+        v2_bitmap->add_segment_ids(0);
+        v2_bitmap->add_versions(1);
+        v2_bitmap->add_segment_delete_bitmaps(completed_bitmap);
+        std::unique_ptr<Transaction> txn;
+        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), 
TxnErrorCode::TXN_OK);
+        // Keep complete legacy values both in an earlier rowset and before 
the replaced bitmap.
+        for (const auto& id : {previous_rowset, rowset_id, following_rowset}) {
+            txn->put(meta_delete_bitmap_key({instance_id, tablet_id, id, 1, 
0}), completed_bitmap);
+        }
+        blob_put(txn.get(),
+                 versioned::meta_delete_bitmap_key({instance_id, tablet_id, 
previous_rowset}),
+                 v2_storage, 0);
+        blob_put(txn.get(), bitmap_key, old_bitmap, 0);
+        TabletStatsPB tablet_stats;
+        tablet_stats.set_base_compaction_cnt(9);
+        tablet_stats.set_cumulative_compaction_cnt(19);
+        tablet_stats.set_cumulative_point(20);
+        txn->put(stats_tablet_key({instance_id, 650, 653, 651, tablet_id}),
+                 tablet_stats.SerializeAsString());
+        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+
+        // The MS treats bitmap bytes as opaque; distinct chunks expose 
truncation and splicing.
+        const std::string replacement_bitmap(replacement_size, 'x');
+        UpdateDeleteBitmapRequest update_req;
+        update_req.set_cloud_unique_id("test_cloud_unique_id");
+        update_req.set_table_id(650);
+        update_req.set_partition_id(651);
+        update_req.set_tablet_id(tablet_id);
+        update_req.set_lock_id(-3);
+        update_req.set_without_lock(true);
+        update_req.set_initiator(tablet_id);
+        update_req.set_pre_rowset_agg_start_version(1);
+        update_req.set_pre_rowset_agg_end_version(2);
+        update_req.add_rowset_ids(rowset_id);
+        update_req.add_segment_ids(0);
+        update_req.add_versions(2);
+        update_req.add_segment_delete_bitmaps(replacement_bitmap);
+        update_req.add_pre_rowset_versions(1);
+
+        bool replacement_done = false;
+        sp->set_call_back("get_delete_bitmap_test",
+                          [&](auto&& args) { *try_any_cast<bool*>(args[0]) = 
true; });
+        sp->set_call_back("get_delete_bitmap_err", [&](auto&& args) {
+            auto round = *try_any_cast<int64_t*>(args[0]);
+            if (round != 1 || replacement_done) {
+                return;
+            }
+            replacement_done = true;
+            // Page 0 has already appended a complete bitmap and old chunk 0. 
Replace the same
+            // logical bitmap, then expire the read before page 1 is consumed. 
The new
+            // transaction still finds sequence 0, but must not keep the old 
prefix.
+            brpc::Controller update_cntl;
+            UpdateDeleteBitmapResponse update_res;
+            meta_service->update_delete_bitmap(
+                    
reinterpret_cast<google::protobuf::RpcController*>(&update_cntl), &update_req,
+                    &update_res, nullptr);
+            ASSERT_EQ(update_res.status().code(), MetaServiceCode::OK);
+            *try_any_cast<TxnErrorCode*>(args[1]) = TxnErrorCode::TXN_TOO_OLD;
+        });
+        sp->enable_processing();
+
+        brpc::Controller get_cntl;
+        GetDeleteBitmapRequest get_req;
+        GetDeleteBitmapResponse get_res;
+        get_req.set_cloud_unique_id("test_cloud_unique_id");
+        get_req.set_tablet_id(tablet_id);
+        get_req.set_store_version(read_v2 ? 3 : 1);
+        for (const auto& id : {previous_rowset, rowset_id, following_rowset}) {
+            get_req.add_rowset_ids(id);
+            get_req.add_begin_versions(id == previous_rowset ? 0 : 1);
+            get_req.add_end_versions(3);
+        }
+        auto* idx = get_req.mutable_idx();
+        idx->set_db_id(1);
+        idx->set_table_id(650);
+        idx->set_index_id(653);
+        idx->set_partition_id(651);
+        idx->set_tablet_id(tablet_id);
+        get_req.set_base_compaction_cnt(tablet_stats.base_compaction_cnt());
+        
get_req.set_cumulative_compaction_cnt(tablet_stats.cumulative_compaction_cnt());
+        get_req.set_cumulative_point(tablet_stats.cumulative_point());
+        // v1 must leave room for the following rowset after dropping the old 
prefix.
+        // Mixed reads must retain the earlier v2 bytes and stop exactly at 
the threshold.
+        get_req.set_dbm_bytes_threshold(completed_bitmap.size() * 2 + 
replacement_size +
+                                        (read_v2 ? v2_storage.ByteSizeLong() : 
1));
+        meta_service->get_delete_bitmap(
+                reinterpret_cast<google::protobuf::RpcController*>(&get_cntl), 
&get_req, &get_res,
+                nullptr);
+
+        ASSERT_TRUE(replacement_done);
+        ASSERT_EQ(get_res.status().code(), MetaServiceCode::OK);
+        const int expected_bitmap_count = read_v2 ? 3 : 4;
+        ASSERT_EQ(get_res.rowset_ids_size(), expected_bitmap_count);
+        ASSERT_EQ(get_res.segment_ids_size(), expected_bitmap_count);
+        ASSERT_EQ(get_res.versions_size(), expected_bitmap_count);
+        ASSERT_EQ(get_res.segment_delete_bitmaps_size(), 
expected_bitmap_count);
+        EXPECT_EQ(get_res.rowset_ids(0), previous_rowset);
+        EXPECT_EQ(get_res.rowset_ids(1), rowset_id);
+        EXPECT_EQ(get_res.rowset_ids(2), rowset_id);
+        if (!read_v2) {
+            EXPECT_EQ(get_res.rowset_ids(3), following_rowset);
+        }
+        for (int i = 0; i < expected_bitmap_count; ++i) {
+            EXPECT_EQ(get_res.segment_ids(i), 0);
+            EXPECT_EQ(get_res.versions(i), i == 2 ? 2 : 1);
+            EXPECT_EQ(get_res.segment_delete_bitmaps(i),
+                      i == 2 ? replacement_bitmap : completed_bitmap);
+        }
+        EXPECT_EQ(get_res.has_more(), read_v2);
+        EXPECT_EQ(get_res.returned_rowset_ids_size(), read_v2 ? 2 : 3);
+        ASSERT_EQ(get_res.delta_rowset_ids_size(), read_v2 ? 1 : 0);
+        ASSERT_EQ(get_res.delete_bitmap_storages_size(), read_v2 ? 1 : 0);
+        if (read_v2) {
+            EXPECT_EQ(get_res.delta_rowset_ids(0), previous_rowset);
+            EXPECT_EQ(get_res.delete_bitmap_storages(0).SerializeAsString(),
+                      v2_storage.SerializeAsString());
+        }
+    }
+}
+
+TEST(MetaServiceTest, GetDeleteBitmapRetryBudgetRequiresBitmapProgress) {
+    for (const std::string scenario : {"first_page", "same_bitmap", 
"next_bitmap"}) {
+        SCOPED_TRACE(scenario);
+        auto meta_service = get_meta_service();
+        auto sp = SyncPoint::get_instance();
+        const bool old_enable_txn_store_retry = config::enable_txn_store_retry;
+        config::enable_txn_store_retry = false;
+        DORIS_CLOUD_DEFER {
+            config::enable_txn_store_retry = old_enable_txn_store_retry;
+            sp->clear_all_call_backs();
+            sp->disable_processing();
+        };
+
+        extern std::string get_instance_id(const 
std::shared_ptr<ResourceManager>& rc_mgr,
+                                           const std::string& cloud_unique_id);
+        auto instance_id = get_instance_id(meta_service->resource_mgr(), 
"test_cloud_unique_id");
+        constexpr int64_t tablet_id = 662;
+        constexpr int bitmap_count = 5;
+        const std::string rowset_id = "retry_budget_rowset";
+        const std::string bitmap(DEFAULT_BLOB_SPLIT_SIZE * 5, 'a');
+        std::unique_ptr<Transaction> txn;
+        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), 
TxnErrorCode::TXN_OK);
+        for (int version = 1; version <= bitmap_count; ++version) {
+            blob_put(txn.get(),
+                     meta_delete_bitmap_key({instance_id, tablet_id, 
rowset_id, version, 0}),
+                     bitmap, 0);
+        }
+        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+
+        brpc::Controller cntl;
+        GetDeleteBitmapRequest req;
+        GetDeleteBitmapResponse res;
+        req.set_cloud_unique_id("test_cloud_unique_id");
+        req.set_tablet_id(tablet_id);
+        req.add_rowset_ids(rowset_id);
+        req.add_begin_versions(0);
+        req.add_end_versions(bitmap_count);
+        int injected_errors = 0;
+        int64_t last_retried_version = 0;
+        const int expected_errors = scenario == "next_bitmap" ? bitmap_count : 
4;
+        sp->set_call_back("get_delete_bitmap_test",
+                          [&](auto&& args) { *try_any_cast<bool*>(args[0]) = 
true; });
+        sp->set_call_back("get_delete_bitmap_err", [&](auto&& args) {
+            if (scenario != "first_page") {
+                // Read several pages successfully before expiring the 
transaction. Those
+                // pages must not reset the budget while retrying the same 
logical bitmap.
+                if (res.versions_size() == 0 ||
+                    
res.segment_delete_bitmaps(res.segment_delete_bitmaps_size() - 1).size() <
+                            DEFAULT_BLOB_SPLIT_SIZE * 4) {
+                    return;
+                }
+                auto version = res.versions(res.versions_size() - 1);
+                if (scenario == "next_bitmap" && version <= 
last_retried_version) {
+                    return;
+                }
+                last_retried_version = version;
+            }
+            ++injected_errors;
+            // Terminate even if a broken retry loop fails to enforce its own 
budget.
+            *try_any_cast<TxnErrorCode*>(args[1]) = injected_errors <= 
expected_errors
+                                                            ? 
TxnErrorCode::TXN_TOO_OLD
+                                                            : 
TxnErrorCode::TXN_INVALID_ARGUMENT;
+        });
+        sp->enable_processing();
+        
meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
+                                        &req, &res, nullptr);
+
+        EXPECT_EQ(injected_errors, expected_errors);
+        if (scenario != "next_bitmap") {
+            EXPECT_EQ(res.status().code(), MetaServiceCode::KV_TXN_TOO_OLD);
+        } else {
+            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
+            ASSERT_EQ(res.rowset_ids_size(), bitmap_count);
+            ASSERT_EQ(res.segment_ids_size(), bitmap_count);
+            ASSERT_EQ(res.versions_size(), bitmap_count);
+            ASSERT_EQ(res.segment_delete_bitmaps_size(), bitmap_count);
+            for (int i = 0; i < bitmap_count; ++i) {
+                EXPECT_EQ(res.rowset_ids(i), rowset_id);
+                EXPECT_EQ(res.segment_ids(i), 0);
+                EXPECT_EQ(res.versions(i), i + 1);
+                EXPECT_EQ(res.segment_delete_bitmaps(i), bitmap);
+            }
+        }
+    }
+}
+
+TEST(MetaServiceTest, GetDeleteBitmapResetsRetryBudgetAfterCompletePageTail) {
+    for (const std::string scenario : {"legacy", "single_blob", "short_tail", 
"full_chunks"}) {
+        SCOPED_TRACE(scenario);
+        auto meta_service = get_meta_service();
+        auto sp = SyncPoint::get_instance();
+        const bool old_enable_txn_store_retry = config::enable_txn_store_retry;
+        config::enable_txn_store_retry = false;
+        DORIS_CLOUD_DEFER {
+            sp->clear_all_call_backs();
+            sp->disable_processing();
+            config::enable_txn_store_retry = old_enable_txn_store_retry;
+        };
+
+        extern std::string get_instance_id(const 
std::shared_ptr<ResourceManager>& rc_mgr,
+                                           const std::string& cloud_unique_id);
+        auto instance_id = get_instance_id(meta_service->resource_mgr(), 
"test_cloud_unique_id");
+        constexpr int64_t tablet_id = 663;
+        const std::string rowset_id = "complete_page_tail";
+        const size_t split_size =
+                scenario == "full_chunks" ? MIN_BLOB_SPLIT_SIZE : 
DEFAULT_BLOB_SPLIT_SIZE;
+        const size_t bitmap_size = scenario == "short_tail" ? split_size + 
MIN_BLOB_SPLIT_SIZE * 2
+                                   : scenario == "full_chunks" ? split_size * 2
+                                                               : 
MIN_BLOB_SPLIT_SIZE / 2;
+        const std::string bitmap(bitmap_size, 'a');
+        const std::string following_bitmap = "following_bitmap";
+        auto bitmap_key = meta_delete_bitmap_key({instance_id, tablet_id, 
rowset_id, 1, 0});
+        auto bitmap_end_key = bitmap_key;
+        encode_int64(INT64_MAX, &bitmap_end_key);
+        std::unique_ptr<Transaction> txn;
+        ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), 
TxnErrorCode::TXN_OK);
+        if (scenario == "legacy") {
+            txn->put(bitmap_key, bitmap);
+        } else {
+            blob_put(txn.get(), bitmap_key, bitmap, 0, split_size);
+        }
+        txn->put(meta_delete_bitmap_key({instance_id, tablet_id, rowset_id, 2, 
0}),
+                 following_bitmap);
+        ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+
+        GetDeleteBitmapRequest req;
+        GetDeleteBitmapResponse res;
+        req.set_cloud_unique_id("test_cloud_unique_id");
+        req.set_tablet_id(tablet_id);
+        req.set_store_version(1);
+        req.add_rowset_ids(rowset_id);
+        req.add_begin_versions(1);
+        req.add_end_versions(2);
+        req.add_rowset_ids("next_rowset");
+        req.add_begin_versions(1);
+        req.add_end_versions(2);
+        req.set_dbm_bytes_threshold(bitmap.size() + following_bitmap.size());
+        // Put the last fragment of A at a page boundary, before any key from 
B is decoded.
+        sp->set_call_back("memkv::Transaction::get",
+                          [](auto&& args) { *try_any_cast<int*>(args[0]) = 1; 
});
+        int injected_errors = 0;
+        sp->set_call_back("get_delete_bitmap_err", [&](auto&& args) {
+            if (injected_errors < 3) {
+                ++injected_errors;
+                *try_any_cast<TxnErrorCode*>(args[1]) = 
TxnErrorCode::TXN_TOO_OLD;
+            } else if (injected_errors == 3 && 
res.segment_delete_bitmaps_size() == 1 &&
+                       res.segment_delete_bitmaps(0) == bitmap) {
+                // Grow A before expiring the continuation read. A completed 
checkpoint must
+                // retain the original A and skip all of its replacement's 
fragments.
+                ASSERT_EQ(meta_service->txn_kv()->create_txn(&txn), 
TxnErrorCode::TXN_OK);
+                txn->remove(bitmap_key, bitmap_end_key);
+                blob_put(txn.get(), bitmap_key, 
std::string(DEFAULT_BLOB_SPLIT_SIZE * 3, 'x'), 0);
+                ASSERT_EQ(txn->commit(), TxnErrorCode::TXN_OK);
+                ++injected_errors;
+                *try_any_cast<TxnErrorCode*>(args[1]) = 
TxnErrorCode::TXN_TOO_OLD;
+            }
+        });
+        sp->enable_processing();
+        brpc::Controller cntl;
+        
meta_service->get_delete_bitmap(reinterpret_cast<google::protobuf::RpcController*>(&cntl),
+                                        &req, &res, nullptr);
+
+        EXPECT_EQ(injected_errors, 4);
+        if (scenario == "full_chunks") {
+            // Full chunks below the default split size do not prove 
completion. Keep the
+            // exhausted budget rather than guessing that A has ended.
+            EXPECT_EQ(res.status().code(), MetaServiceCode::KV_TXN_TOO_OLD);
+        } else {
+            ASSERT_EQ(res.status().code(), MetaServiceCode::OK);
+            ASSERT_EQ(res.rowset_ids_size(), 2);
+            ASSERT_EQ(res.segment_ids_size(), 2);
+            ASSERT_EQ(res.versions_size(), 2);
+            ASSERT_EQ(res.segment_delete_bitmaps_size(), 2);
+            for (int i = 0; i < 2; ++i) {
+                EXPECT_EQ(res.rowset_ids(i), rowset_id);
+                EXPECT_EQ(res.segment_ids(i), 0);
+                EXPECT_EQ(res.versions(i), i + 1);
+            }
+            EXPECT_EQ(res.segment_delete_bitmaps(0), bitmap);
+            EXPECT_EQ(res.segment_delete_bitmaps(1), following_bitmap);
+            EXPECT_TRUE(res.has_more());
+            ASSERT_EQ(res.returned_rowset_ids_size(), 1);
+            EXPECT_EQ(res.returned_rowset_ids(0), rowset_id);
+        }
+    }
+}
+
 TEST(MetaServiceTest, RemovePreDeleteBitmapBatchesEachBlobKey) {
     auto meta_service = get_meta_service();
     auto sp = SyncPoint::get_instance();


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to