@@ -3015,8 +3015,8 @@ void SingleThreadedActiveStreamTest::testProducerPrunesUserXattrsForDelete(
30153015 // only configurations that trigger user-xattr pruning in deletes.
30163016 ASSERT_TRUE ((flags & DcpOpenFlag::IncludeDeletedUserXattrs) == 0 );
30173017
3018- auto vb = engine->getVBucket (vbid);
3019- recreateProducerAndStream (* vb, flags);
3018+ auto & vb = * engine->getVBucket (vbid);
3019+ recreateProducerAndStream (vb, flags);
30203020
30213021 const auto currIncDelUserXattr =
30223022 (flags & DcpOpenFlag::IncludeDeletedUserXattrs) != 0
@@ -3045,9 +3045,34 @@ void SingleThreadedActiveStreamTest::testProducerPrunesUserXattrsForDelete(
30453045
30463046 auto * cookie = create_mock_cookie ();
30473047
3048- // Store a Deleted doc
3048+ struct Sizes {
3049+ Sizes (const Item& item) {
3050+ value = item.getNBytes ();
3051+
3052+ cb::char_buffer valBuf{const_cast <char *>(item.getData ()),
3053+ item.getNBytes ()};
3054+ cb::xattr::Blob xattrBlob (valBuf, false );
3055+ xattrs = xattrBlob.size ();
3056+ userXattrs = xattrBlob.get_user_size ();
3057+ sysXattrs = xattrBlob.get_system_size ();
3058+ body = item.getNBytes () - cb::xattr::get_body_offset (valBuf);
3059+ }
3060+
3061+ size_t value;
3062+ size_t xattrs;
3063+ size_t userXattrs;
3064+ size_t sysXattrs;
3065+ size_t body;
3066+ };
3067+
3068+ // Make an item..
30493069 auto item = makeCommittedItem (makeStoredDocKey (" keyD" ), value);
30503070 item->setDataType (bodyType | PROTOCOL_BINARY_DATATYPE_XATTR);
3071+ // .. and save the payload sizes for later checks.
3072+ const auto originalValue = value;
3073+ const auto originalSizes = Sizes (*item);
3074+
3075+ // Store the item as deleted
30513076 uint64_t cas = 0 ;
30523077 const auto expectedStoreRes = durReqs ? ENGINE_EWOULDBLOCK : ENGINE_SUCCESS;
30533078 ASSERT_EQ (expectedStoreRes,
@@ -3058,6 +3083,75 @@ void SingleThreadedActiveStreamTest::testProducerPrunesUserXattrsForDelete(
30583083 durReqs,
30593084 DocumentState::Deleted));
30603085
3086+ auto & readyQ = stream->public_readyQ ();
3087+ ASSERT_EQ (0 , readyQ.size ());
3088+
3089+ // Verfies that the payload pointed by the item in CM is the same as the
3090+ // original one
3091+ const auto checkPayloadInCM =
3092+ [&vb, &originalValue, &originalSizes, &durReqs]() -> void {
3093+ const auto & manager = *vb.checkpointManager ;
3094+ const auto & ckptList =
3095+ CheckpointManagerTestIntrospector::public_getCheckpointList (
3096+ manager);
3097+ // 1 checkpoint
3098+ ASSERT_EQ (1 , ckptList.size ());
3099+ const auto * ckpt = ckptList.front ().get ();
3100+ ASSERT_EQ (checkpoint_state::CHECKPOINT_OPEN, ckpt->getState ());
3101+ // empty-item
3102+ auto it = ckpt->begin ();
3103+ ASSERT_EQ (queue_op::empty, (*it)->getOperation ());
3104+ // 1 metaitem (checkpoint-start)
3105+ it++;
3106+ ASSERT_EQ (3 , ckpt->getNumMetaItems ());
3107+ EXPECT_EQ (queue_op::checkpoint_start, (*it)->getOperation ());
3108+ it++;
3109+ EXPECT_EQ (queue_op::set_vbucket_state, (*it)->getOperation ());
3110+ it++;
3111+ EXPECT_EQ (queue_op::set_vbucket_state, (*it)->getOperation ());
3112+ // 1 non-metaitem is our deletion
3113+ it++;
3114+ ASSERT_EQ (1 , ckpt->getNumItems ());
3115+ ASSERT_TRUE ((*it)->isDeleted ());
3116+ const auto expectedOp =
3117+ durReqs ? queue_op::pending_sync_write : queue_op::mutation;
3118+ EXPECT_EQ (expectedOp, (*it)->getOperation ());
3119+
3120+ // Byte-by-byte comparison
3121+ EXPECT_EQ (originalValue, (*it)->getValue ()->to_s ());
3122+
3123+ // The latest check should already fail if even a single byte in the
3124+ // payload has changed, but check also the sizes of the specific value
3125+ // chunks.
3126+ const auto cmSizes = Sizes (**it);
3127+ EXPECT_EQ (originalSizes.value , cmSizes.value );
3128+ EXPECT_EQ (originalSizes.xattrs , cmSizes.xattrs );
3129+ EXPECT_EQ (originalSizes.userXattrs , cmSizes.userXattrs );
3130+ EXPECT_EQ (originalSizes.sysXattrs , cmSizes.sysXattrs );
3131+ ASSERT_EQ (originalSizes.body , cmSizes.body );
3132+ };
3133+
3134+ // Verify that the value of the item in CM has not changed
3135+ {
3136+ SCOPED_TRACE (" " );
3137+ checkPayloadInCM ();
3138+ }
3139+
3140+ // Push items to the readyQ and check what we get
3141+ stream->nextCheckpointItemTask ();
3142+ ASSERT_EQ (2 , readyQ.size ());
3143+
3144+ // MB-41944: The call to Stream::nextCheckpointItemTask() has removed
3145+ // UserXattrs from the payload. Before the fix we modified the item's value
3146+ // (which is a reference-counted object in memory) rather that a copy of it.
3147+ // So here we check that the item's value in CM is still untouched.
3148+ {
3149+ SCOPED_TRACE (" " );
3150+ checkPayloadInCM ();
3151+ }
3152+
3153+ // Note: Doing this check after Stream::nextCheckpointItemTask() is another
3154+ // coverage for MB-41944, so I move it here.
30613155 if (persistent ()) {
30623156 // Flush and ensure docs on disk
30633157 flush_vbucket_to_disk (vbid, 1 /* expectedNumFlushed*/ );
@@ -3074,13 +3168,6 @@ void SingleThreadedActiveStreamTest::testProducerPrunesUserXattrsForDelete(
30743168 doc.item ->getNBytes ()));
30753169 }
30763170
3077- auto & readyQ = stream->public_readyQ ();
3078- ASSERT_EQ (0 , readyQ.size ());
3079-
3080- // Push items to the readyQ and check what we get
3081- stream->nextCheckpointItemTask ();
3082- ASSERT_EQ (2 , readyQ.size ());
3083-
30843171 auto resp = stream->public_nextQueuedItem ();
30853172 ASSERT_TRUE (resp);
30863173 ASSERT_EQ (DcpResponse::Event::SnapshotMarker, resp->getEvent ());
0 commit comments