]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd/object_map: potentially use in-memory object map in DiffRequest
authorIlya Dryomov <idryomov@gmail.com>
Sat, 6 Jan 2024 16:05:39 +0000 (17:05 +0100)
committerIlya Dryomov <idryomov@gmail.com>
Sat, 20 Jan 2024 18:00:24 +0000 (19:00 +0100)
If the object map for the end version is around (already loaded in
memory, either due to the end version being a snapshot or due to
exclusive lock being held), use it to run diff-iterate against the
beginning of time.  Since it's the only object map needed in that
case, such calls would be satisfied locally.

Fixes: https://tracker.ceph.com/issues/63341
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
(cherry picked from commit 0c4bb58c900efa2356ea8526d3432b2922787afa)

src/librbd/ObjectMap.h
src/librbd/object_map/DiffRequest.cc
src/test/librbd/mock/MockObjectMap.h
src/test/librbd/object_map/test_mock_DiffRequest.cc

index 8b5b352ef8aa4c46f2fcf5ca1daba65d8a4b3770..35ea4cb88f93a341f188cf66fc8ffc354c863d98 100644 (file)
@@ -45,6 +45,12 @@ public:
     return m_object_map.size();
   }
 
+  template <typename F, typename... Args>
+  auto with_object_map(F&& f, Args&&... args) const {
+    std::shared_lock locker(m_lock);
+    return std::forward<F>(f)(m_object_map, std::forward<Args>(args)...);
+  }
+
   inline void set_state(uint64_t object_no, uint8_t new_state,
                         const boost::optional<uint8_t> &current_state) {
     std::unique_lock locker{m_lock};
index 7da6152058b0ec1f521fbdf2af80246612c89885..acaf31a39bfbf6bbcc6c1cdef29480c76e89f863 100644 (file)
@@ -240,10 +240,30 @@ void DiffRequest<I>::send() {
     return;
   }
 
+  std::shared_lock image_locker{m_image_ctx->image_lock};
+  if (is_diff_iterate() &&
+      m_snap_id_start == 0 &&
+      m_snap_id_end == m_image_ctx->snap_id &&
+      m_image_ctx->object_map != nullptr) {
+    ldout(cct, 10) << "using in-memory object map" << dendl;
+    m_current_snap_id = m_snap_id_end;
+
+    int r = prepare_for_object_map();
+    if (r == 0) {
+      r = m_image_ctx->object_map->with_object_map(
+        [this](const BitVector<2>& object_map) {
+          return process_object_map(object_map);
+        });
+    }
+    image_locker.unlock();
+
+    finish(r);
+    return;
+  }
+
   // collect all the snap ids in the provided range (inclusive) unless
   // this is diff-iterate against the beginning of time, in which case
   // only the end version matters
-  std::shared_lock image_locker{m_image_ctx->image_lock};
   if (!is_diff_iterate() || m_snap_id_start != 0) {
     if (m_snap_id_start != 0) {
       m_snap_ids.insert(m_snap_id_start);
index 2a1adbcaebab3169be17e0898fdcebc5360889d8..5e3235cf023d106af7a0d14c4a29df0025b84473 100644 (file)
@@ -4,19 +4,27 @@
 #ifndef CEPH_TEST_LIBRBD_MOCK_OBJECT_MAP_H
 #define CEPH_TEST_LIBRBD_MOCK_OBJECT_MAP_H
 
+#include "common/bit_vector.hpp"
 #include "librbd/Utils.h"
 #include "gmock/gmock.h"
 
 namespace librbd {
 
 struct MockObjectMap {
-  MOCK_METHOD1(at, uint8_t(uint64_t));
-  uint8_t operator[](uint64_t object_no) {
+  MOCK_CONST_METHOD1(at, uint8_t(uint64_t));
+  uint8_t operator[](uint64_t object_no) const {
     return at(object_no);
   }
 
   MOCK_CONST_METHOD0(size, uint64_t());
 
+  MOCK_CONST_METHOD0(with, ceph::BitVector<2>());
+  template <typename F, typename... Args>
+  auto with_object_map(F&& f, Args&&... args) const {
+    const ceph::BitVector<2> object_map = with();
+    return std::forward<F>(f)(object_map, std::forward<Args>(args)...);
+  }
+
   MOCK_METHOD1(open, void(Context *on_finish));
   MOCK_METHOD1(close, void(Context *on_finish));
 
index 8d6ee2d27b1cdab4e6c7258e6a378d8246de62a2..9b88f0d75ca9e87121168fbf7124c2a7890b0c97 100644 (file)
@@ -28,6 +28,7 @@ void noop(MockTestImageCtx&) {}
 using ::testing::_;
 using ::testing::Invoke;
 using ::testing::InSequence;
+using ::testing::Return;
 using ::testing::StrEq;
 using ::testing::WithArg;
 
@@ -207,12 +208,23 @@ public:
     expect_load_map(mock_image_ctx, snap_id, object_map, r, [](){});
   }
 
+  void expect_with_map(MockTestImageCtx& mock_image_ctx,
+                       const BitVector<2>& object_map) {
+    EXPECT_CALL(*mock_image_ctx.object_map, with()).WillOnce(Return(object_map));
+  }
+
   template <typename F>
-  int do_diff(F&& f, uint64_t start_snap_id, uint64_t end_snap_id,
+  int do_diff(bool want_object_map, F&& f,
+              uint64_t start_snap_id, uint64_t end_snap_id,
               uint64_t start_object_no, uint64_t end_object_no) {
     InSequence seq;
 
     MockTestImageCtx mock_image_ctx(*m_image_ctx);
+    MockObjectMap mock_object_map;
+    if (want_object_map) {
+      mock_image_ctx.object_map = &mock_object_map;
+    }
+    mock_image_ctx.snap_id = end_snap_id;
     std::forward<F>(f)(mock_image_ctx);
 
     C_SaferCond ctx;
@@ -224,13 +236,14 @@ public:
   }
 
   template <typename F>
-  void test_diff_iterate(F&& f, uint64_t start_snap_id, uint64_t end_snap_id,
+  void test_diff_iterate(bool want_object_map, F&& f,
+                         uint64_t start_snap_id, uint64_t end_snap_id,
                          const BitVector<2>& expected_diff_state) {
     // ranged -- run through all ranges (substrings) in expected_diff_state
     for (uint64_t i = 0; i < expected_diff_state.size(); i++) {
       for (uint64_t j = i + 1; j <= expected_diff_state.size(); j++) {
-        ASSERT_EQ(0, do_diff(std::forward<F>(f), start_snap_id, end_snap_id,
-                             i, j));
+        ASSERT_EQ(0, do_diff(want_object_map, std::forward<F>(f),
+                             start_snap_id, end_snap_id, i, j));
         ASSERT_EQ(j - i, m_diff_state.size());
         for (uint64_t k = 0; k < m_diff_state.size(); k++) {
           ASSERT_EQ(expected_diff_state[i + k], m_diff_state[k]);
@@ -239,16 +252,17 @@ public:
     }
 
     // unranged -- equivalent to i=0, j=expected_diff_state.size() range
-    ASSERT_EQ(0, do_diff(std::forward<F>(f), start_snap_id, end_snap_id,
-                         0, UINT64_MAX - 1));
+    ASSERT_EQ(0, do_diff(want_object_map, std::forward<F>(f),
+                         start_snap_id, end_snap_id, 0, UINT64_MAX - 1));
     ASSERT_EQ(expected_diff_state, m_diff_state);
   }
 
   template <typename F>
-  void test_deep_copy(F&& f, uint64_t start_snap_id, uint64_t end_snap_id,
+  void test_deep_copy(bool want_object_map, F&& f,
+                      uint64_t start_snap_id, uint64_t end_snap_id,
                       const BitVector<2>& expected_diff_state) {
-    ASSERT_EQ(0, do_diff(std::forward<F>(f), start_snap_id, end_snap_id,
-                         0, UINT64_MAX));
+    ASSERT_EQ(0, do_diff(want_object_map, std::forward<F>(f),
+                         start_snap_id, end_snap_id, 0, UINT64_MAX));
     ASSERT_EQ(expected_diff_state, m_diff_state);
   }
 
@@ -258,17 +272,21 @@ public:
 
 TEST_P(TestMockObjectMapDiffRequest, InvalidStartSnap) {
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(noop, CEPH_NOSNAP, CEPH_NOSNAP, 123, 456));
+    ASSERT_EQ(-EINVAL, do_diff(false, noop, CEPH_NOSNAP, CEPH_NOSNAP, 123, 456));
+    ASSERT_EQ(-EINVAL, do_diff(true, noop, CEPH_NOSNAP, CEPH_NOSNAP, 123, 456));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(noop, CEPH_NOSNAP, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, noop, CEPH_NOSNAP, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, noop, CEPH_NOSNAP, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
 TEST_P(TestMockObjectMapDiffRequest, InvalidEndSnap) {
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(noop, 2, 1, 123, 456));
+    ASSERT_EQ(-EINVAL, do_diff(false, noop, 2, 1, 123, 456));
+    ASSERT_EQ(-EINVAL, do_diff(true, noop, 2, 1, 123, 456));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(noop, 2, 1, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, noop, 2, 1, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, noop, 2, 1, 0, UINT64_MAX));
   }
 }
 
@@ -276,24 +294,32 @@ TEST_P(TestMockObjectMapDiffRequest, StartEndSnapEqual) {
   BitVector<2> expected_diff_state;
 
   if (is_diff_iterate()) {
-    ASSERT_EQ(0, do_diff(noop, 1, 1, 123, 456));
+    ASSERT_EQ(0, do_diff(false, noop, 1, 1, 123, 456));
+    ASSERT_EQ(expected_diff_state, m_diff_state);
+    ASSERT_EQ(0, do_diff(true, noop, 1, 1, 123, 456));
+    ASSERT_EQ(expected_diff_state, m_diff_state);
   } else {
-    ASSERT_EQ(0, do_diff(noop, 1, 1, 0, UINT64_MAX));
+    ASSERT_EQ(0, do_diff(false, noop, 1, 1, 0, UINT64_MAX));
+    ASSERT_EQ(expected_diff_state, m_diff_state);
+    ASSERT_EQ(0, do_diff(true, noop, 1, 1, 0, UINT64_MAX));
+    ASSERT_EQ(expected_diff_state, m_diff_state);
   }
-  ASSERT_EQ(expected_diff_state, m_diff_state);
 }
 
 TEST_P(TestMockObjectMapDiffRequest, InvalidStartObject) {
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(noop, 0, 1, UINT64_MAX, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, noop, 0, 1, UINT64_MAX, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, noop, 0, 1, UINT64_MAX, UINT64_MAX));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(noop, 0, 1, 123, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, noop, 0, 1, 123, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, noop, 0, 1, 123, UINT64_MAX));
   }
 }
 
 TEST_P(TestMockObjectMapDiffRequest, InvalidEndObject) {
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(noop, 0, 1, 456, 123));
+    ASSERT_EQ(-EINVAL, do_diff(false, noop, 0, 1, 456, 123));
+    ASSERT_EQ(-EINVAL, do_diff(true, noop, 0, 1, 456, 123));
   } else {
     SUCCEED();
   }
@@ -303,7 +329,9 @@ TEST_P(TestMockObjectMapDiffRequest, StartEndObjectEqual) {
   BitVector<2> expected_diff_state;
 
   if (is_diff_iterate()) {
-    ASSERT_EQ(0, do_diff(noop, 0, 1, 123, 123));
+    ASSERT_EQ(0, do_diff(false, noop, 0, 1, 123, 123));
+    ASSERT_EQ(expected_diff_state, m_diff_state);
+    ASSERT_EQ(0, do_diff(true, noop, 0, 1, 123, 123));
     ASSERT_EQ(expected_diff_state, m_diff_state);
   } else {
     SUCCEED();
@@ -315,9 +343,11 @@ TEST_P(TestMockObjectMapDiffRequest, FastDiffDisabled) {
   REQUIRE(!is_feature_enabled(RBD_FEATURE_OBJECT_MAP));
 
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(noop, 0, CEPH_NOSNAP, 123, 456));
+    ASSERT_EQ(-EINVAL, do_diff(false, noop, 0, CEPH_NOSNAP, 123, 456));
+    ASSERT_EQ(-EINVAL, do_diff(true, noop, 0, CEPH_NOSNAP, 123, 456));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(noop, 0, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, noop, 0, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, noop, 0, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -345,9 +375,15 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnap) {
     expect_load_map(mock_image_ctx, 1, object_map_1, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 0, 1, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, 1, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_1);
+    };
+    test_diff_iterate(false, load, 0, 1, expected_diff_state);
+    test_diff_iterate(true, with, 0, 1, expected_diff_state);
   } else {
-    test_deep_copy(load, 0, 1, expected_diff_state);
+    test_deep_copy(false, load, 0, 1, expected_diff_state);
+    test_deep_copy(true, load, 0, 1, expected_diff_state);
   }
 }
 
@@ -367,9 +403,15 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapEmpty) {
     expect_load_map(mock_image_ctx, 1, object_map_1, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 0, 1, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, 1, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_1);
+    };
+    test_diff_iterate(false, load, 0, 1, expected_diff_state);
+    test_diff_iterate(true, with, 0, 1, expected_diff_state);
   } else {
-    test_deep_copy(load, 0, 1, expected_diff_state);
+    test_deep_copy(false, load, 0, 1, expected_diff_state);
+    test_deep_copy(true, load, 0, 1, expected_diff_state);
   }
 }
 
@@ -406,7 +448,12 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnap) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    test_diff_iterate(load, 0, 2, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, 2, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_2);
+    };
+    test_diff_iterate(false, load, 0, 2, expected_diff_state);
+    test_diff_iterate(true, with, 0, 2, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -414,7 +461,8 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnap) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    test_deep_copy(load, 0, 2, expected_diff_state);
+    test_deep_copy(false, load, 0, 2, expected_diff_state);
+    test_deep_copy(true, load, 0, 2, expected_diff_state);
   }
 }
 
@@ -456,7 +504,12 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapGrow) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    test_diff_iterate(load, 0, 2, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, 2, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_2);
+    };
+    test_diff_iterate(false, load, 0, 2, expected_diff_state);
+    test_diff_iterate(true, with, 0, 2, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -464,7 +517,8 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapGrow) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    test_deep_copy(load, 0, 2, expected_diff_state);
+    test_deep_copy(false, load, 0, 2, expected_diff_state);
+    test_deep_copy(true, load, 0, 2, expected_diff_state);
   }
 }
 
@@ -494,7 +548,12 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapGrowFrom
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    test_diff_iterate(load, 0, 2, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, 2, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_2);
+    };
+    test_diff_iterate(false, load, 0, 2, expected_diff_state);
+    test_diff_iterate(true, with, 0, 2, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -502,7 +561,8 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapGrowFrom
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    test_deep_copy(load, 0, 2, expected_diff_state);
+    test_deep_copy(false, load, 0, 2, expected_diff_state);
+    test_deep_copy(true, load, 0, 2, expected_diff_state);
   }
 }
 
@@ -550,7 +610,12 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapShrink)
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    test_diff_iterate(load, 0, 2, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, 2, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_2);
+    };
+    test_diff_iterate(false, load, 0, 2, expected_diff_state);
+    test_diff_iterate(true, with, 0, 2, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -558,7 +623,8 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapShrink)
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    test_deep_copy(load, 0, 2, expected_diff_state);
+    test_deep_copy(false, load, 0, 2, expected_diff_state);
+    test_deep_copy(true, load, 0, 2, expected_diff_state);
   }
 }
 
@@ -592,7 +658,12 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapShrinkTo
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    test_diff_iterate(load, 0, 2, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, 2, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_2);
+    };
+    test_diff_iterate(false, load, 0, 2, expected_diff_state);
+    test_diff_iterate(true, with, 0, 2, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -600,7 +671,41 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapShrinkTo
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    test_deep_copy(load, 0, 2, expected_diff_state);
+    test_deep_copy(false, load, 0, 2, expected_diff_state);
+    test_deep_copy(true, load, 0, 2, expected_diff_state);
+  }
+}
+
+TEST_P(TestMockObjectMapDiffRequest, FromBeginningToUnsetSnap) {
+  REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+  uint32_t object_count = std::size(from_beginning_table);
+  m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+  m_image_ctx->snap_info = {
+    {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {},
+          {}, {}, {}}}
+  };
+
+  BitVector<2> object_map_1;
+  object_map_1.resize(object_count);
+  BitVector<2> expected_diff_state;
+  expected_diff_state.resize(object_count);
+  for (uint32_t i = 0; i < object_count; i++) {
+    object_map_1[i] = from_beginning_table[i][0];
+    expected_diff_state[i] = from_beginning_table[i][1];
+  }
+
+  auto load = [&](MockTestImageCtx& mock_image_ctx) {
+    mock_image_ctx.snap_id = 123;
+    expect_get_flags(mock_image_ctx, 1, 0, 0);
+    expect_load_map(mock_image_ctx, 1, object_map_1, 0);
+  };
+  if (is_diff_iterate()) {
+    test_diff_iterate(false, load, 0, 1, expected_diff_state);
+    test_diff_iterate(true, load, 0, 1, expected_diff_state);
+  } else {
+    test_deep_copy(false, load, 0, 1, expected_diff_state);
+    test_deep_copy(true, load, 0, 1, expected_diff_state);
   }
 }
 
@@ -624,9 +729,15 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHead) {
     expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
-    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -643,9 +754,15 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadEmpty) {
     expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
-    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -680,7 +797,12 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnap) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -688,7 +810,8 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnap) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -728,7 +851,12 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapGrow) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -736,7 +864,8 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapGrow) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -764,7 +893,12 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapGrowFrom
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -772,7 +906,8 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapGrowFrom
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -818,7 +953,12 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapShrink)
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -826,7 +966,8 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapShrink)
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -859,7 +1000,12 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapShrinkTo
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -867,7 +1013,37 @@ TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapShrinkTo
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
+  }
+}
+
+TEST_P(TestMockObjectMapDiffRequest, FromBeginningToUnsetHead) {
+  REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF);
+
+  uint32_t object_count = std::size(from_beginning_table);
+  m_image_ctx->size = object_count * (1 << m_image_ctx->order);
+
+  BitVector<2> object_map_head;
+  object_map_head.resize(object_count);
+  BitVector<2> expected_diff_state;
+  expected_diff_state.resize(object_count);
+  for (uint32_t i = 0; i < object_count; i++) {
+    object_map_head[i] = from_beginning_table[i][0];
+    expected_diff_state[i] = from_beginning_table[i][1];
+  }
+
+  auto load = [&](MockTestImageCtx& mock_image_ctx) {
+    mock_image_ctx.snap_id = 123;
+    expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+    expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
+  };
+  if (is_diff_iterate()) {
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, load, 0, CEPH_NOSNAP, expected_diff_state);
+  } else {
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -902,9 +1078,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnap) {
     expect_load_map(mock_image_ctx, 2, object_map_2, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, 2, expected_diff_state);
+    test_diff_iterate(false, load, 1, 2, expected_diff_state);
+    test_diff_iterate(true, load, 1, 2, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, 2, expected_diff_state);
+    test_deep_copy(false, load, 1, 2, expected_diff_state);
+    test_deep_copy(true, load, 1, 2, expected_diff_state);
   }
 }
 
@@ -944,9 +1122,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapGrow) {
     expect_load_map(mock_image_ctx, 2, object_map_2, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, 2, expected_diff_state);
+    test_diff_iterate(false, load, 1, 2, expected_diff_state);
+    test_diff_iterate(true, load, 1, 2, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, 2, expected_diff_state);
+    test_deep_copy(false, load, 1, 2, expected_diff_state);
+    test_deep_copy(true, load, 1, 2, expected_diff_state);
   }
 }
 
@@ -978,9 +1158,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapGrowFromZero) {
     expect_load_map(mock_image_ctx, 2, object_map_2, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, 2, expected_diff_state);
+    test_diff_iterate(false, load, 1, 2, expected_diff_state);
+    test_diff_iterate(true, load, 1, 2, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, 2, expected_diff_state);
+    test_deep_copy(false, load, 1, 2, expected_diff_state);
+    test_deep_copy(true, load, 1, 2, expected_diff_state);
   }
 }
 
@@ -1026,9 +1208,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapShrink) {
     expect_load_map(mock_image_ctx, 2, object_map_2, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, 2, expected_diff_state);
+    test_diff_iterate(false, load, 1, 2, expected_diff_state);
+    test_diff_iterate(true, load, 1, 2, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, 2, expected_diff_state);
+    test_deep_copy(false, load, 1, 2, expected_diff_state);
+    test_deep_copy(true, load, 1, 2, expected_diff_state);
   }
 }
 
@@ -1064,9 +1248,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapShrinkToZero) {
     expect_load_map(mock_image_ctx, 2, object_map_2, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, 2, expected_diff_state);
+    test_diff_iterate(false, load, 1, 2, expected_diff_state);
+    test_diff_iterate(true, load, 1, 2, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, 2, expected_diff_state);
+    test_deep_copy(false, load, 1, 2, expected_diff_state);
+    test_deep_copy(true, load, 1, 2, expected_diff_state);
   }
 }
 
@@ -1112,9 +1298,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapIntermediateSnap) {
     expect_load_map(mock_image_ctx, 3, object_map_3, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, 3, expected_diff_state);
+    test_diff_iterate(false, load, 1, 3, expected_diff_state);
+    test_diff_iterate(true, load, 1, 3, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, 3, expected_diff_state);
+    test_deep_copy(false, load, 1, 3, expected_diff_state);
+    test_deep_copy(true, load, 1, 3, expected_diff_state);
   }
 }
 
@@ -1147,9 +1335,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToHead) {
     expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -1187,9 +1377,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadGrow) {
     expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -1219,9 +1411,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadGrowFromZero) {
     expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -1265,9 +1459,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadShrink) {
     expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -1302,9 +1498,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadShrinkToZero) {
     expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -1348,9 +1546,11 @@ TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadIntermediateSnap) {
     expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -1365,9 +1565,11 @@ TEST_P(TestMockObjectMapDiffRequest, StartSnapDNE) {
   };
 
   if (is_diff_iterate()) {
-    ASSERT_EQ(-ENOENT, do_diff(noop, 1, 2, 0, object_count));
+    ASSERT_EQ(-ENOENT, do_diff(false, noop, 1, 2, 0, object_count));
+    ASSERT_EQ(-ENOENT, do_diff(true, noop, 1, 2, 0, object_count));
   } else {
-    ASSERT_EQ(-ENOENT, do_diff(noop, 1, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-ENOENT, do_diff(false, noop, 1, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-ENOENT, do_diff(true, noop, 1, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1385,13 +1587,15 @@ TEST_P(TestMockObjectMapDiffRequest, EndSnapDNE) {
   object_map_1.resize(object_count);
 
   if (is_diff_iterate()) {
-    ASSERT_EQ(-ENOENT, do_diff(noop, 0, 2, 0, object_count));
+    ASSERT_EQ(-ENOENT, do_diff(false, noop, 0, 2, 0, object_count));
+    ASSERT_EQ(-ENOENT, do_diff(true, noop, 0, 2, 0, object_count));
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
       expect_load_map(mock_image_ctx, 1, object_map_1, 0);
     };
-    ASSERT_EQ(-ENOENT, do_diff(load, 0, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-ENOENT, do_diff(false, load, 0, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-ENOENT, do_diff(true, load, 0, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1421,7 +1625,12 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateSnapDNEFromBeginning) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -1430,7 +1639,8 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateSnapDNEFromBeginning) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -1463,9 +1673,11 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateSnapDNEFromSnap) {
     expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -1488,9 +1700,11 @@ TEST_P(TestMockObjectMapDiffRequest, StartObjectMapDNE) {
     expect_load_map(mock_image_ctx, 1, object_map_1, -ENOENT);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-ENOENT, do_diff(load, 1, 2, 0, object_count));
+    ASSERT_EQ(-ENOENT, do_diff(false, load, 1, 2, 0, object_count));
+    ASSERT_EQ(-ENOENT, do_diff(true, load, 1, 2, 0, object_count));
   } else {
-    ASSERT_EQ(-ENOENT, do_diff(load, 1, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-ENOENT, do_diff(false, load, 1, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-ENOENT, do_diff(true, load, 1, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1520,7 +1734,12 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapDNE) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, -ENOENT);
     };
-    ASSERT_EQ(-ENOENT, do_diff(load, 0, 2, 0, object_count));
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, 2, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_2);
+    };
+    ASSERT_EQ(-ENOENT, do_diff(false, load, 0, 2, 0, object_count));
+    test_diff_iterate(true, with, 0, 2, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -1528,7 +1747,8 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapDNE) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, -ENOENT);
     };
-    ASSERT_EQ(-ENOENT, do_diff(load, 0, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-ENOENT, do_diff(false, load, 0, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-ENOENT, do_diff(true, load, 0, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1555,7 +1775,12 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapDNEFromBeginning) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -1563,7 +1788,8 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapDNEFromBeginning) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -1598,9 +1824,11 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapDNEFromSnap) {
     expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
   };
   if (is_diff_iterate()) {
-    test_diff_iterate(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   } else {
-    test_deep_copy(load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state);
   }
 }
 
@@ -1620,9 +1848,11 @@ TEST_P(TestMockObjectMapDiffRequest, StartFastDiffInvalid) {
     expect_get_flags(mock_image_ctx, 1, RBD_FLAG_FAST_DIFF_INVALID, 0);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, 2, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 1, 2, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 1, 2, 0, object_count));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 1, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 1, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1645,14 +1875,16 @@ TEST_P(TestMockObjectMapDiffRequest, EndFastDiffInvalid) {
     auto get_flags = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 2, RBD_FLAG_FAST_DIFF_INVALID, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 0, 2, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 0, 2, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 0, 2, 0, object_count));
   } else {
     auto get_flags = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
       expect_load_map(mock_image_ctx, 1, object_map_1, 0);
       expect_get_flags(mock_image_ctx, 2, RBD_FLAG_FAST_DIFF_INVALID, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 0, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 0, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 0, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1678,12 +1910,18 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateFastDiffInvalidFromBeginning) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto get_flags = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, RBD_FLAG_FAST_DIFF_INVALID, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 0, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 0, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 0, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1708,9 +1946,11 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateFastDiffInvalidFromSnap) {
     expect_get_flags(mock_image_ctx, 2, RBD_FLAG_FAST_DIFF_INVALID, 0);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, CEPH_NOSNAP, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 1, CEPH_NOSNAP, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 1, CEPH_NOSNAP, 0, object_count));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(get_flags, 1, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 1, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 1, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1733,9 +1973,11 @@ TEST_P(TestMockObjectMapDiffRequest, StartObjectMapLoadError) {
     expect_load_map(mock_image_ctx, 1, object_map_1, -EPERM);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EPERM, do_diff(load, 1, 2, 0, object_count));
+    ASSERT_EQ(-EPERM, do_diff(false, load, 1, 2, 0, object_count));
+    ASSERT_EQ(-EPERM, do_diff(true, load, 1, 2, 0, object_count));
   } else {
-    ASSERT_EQ(-EPERM, do_diff(load, 1, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EPERM, do_diff(false, load, 1, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EPERM, do_diff(true, load, 1, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1765,7 +2007,12 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapLoadError) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, -EPERM);
     };
-    ASSERT_EQ(-EPERM, do_diff(load, 0, 2, 0, object_count));
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, 2, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_2);
+    };
+    ASSERT_EQ(-EPERM, do_diff(false, load, 0, 2, 0, object_count));
+    test_diff_iterate(true, with, 0, 2, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -1773,7 +2020,8 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapLoadError) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, -EPERM);
     };
-    ASSERT_EQ(-EPERM, do_diff(load, 0, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EPERM, do_diff(false, load, 0, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EPERM, do_diff(true, load, 0, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1800,13 +2048,19 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapLoadErrorFromBeginning
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
       expect_load_map(mock_image_ctx, 1, object_map_1, -EPERM);
     };
-    ASSERT_EQ(-EPERM, do_diff(load, 0, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EPERM, do_diff(false, load, 0, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EPERM, do_diff(true, load, 0, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1833,9 +2087,11 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapLoadErrorFromSnap) {
     expect_load_map(mock_image_ctx, 2, object_map_2, -EPERM);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EPERM, do_diff(load, 1, CEPH_NOSNAP, 0, object_count));
+    ASSERT_EQ(-EPERM, do_diff(false, load, 1, CEPH_NOSNAP, 0, object_count));
+    ASSERT_EQ(-EPERM, do_diff(true, load, 1, CEPH_NOSNAP, 0, object_count));
   } else {
-    ASSERT_EQ(-EPERM, do_diff(load, 1, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EPERM, do_diff(false, load, 1, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EPERM, do_diff(true, load, 1, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1859,9 +2115,11 @@ TEST_P(TestMockObjectMapDiffRequest, StartObjectMapTooSmall) {
     expect_load_map(mock_image_ctx, 1, object_map_1, 0);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(load, 1, 2, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(false, load, 1, 2, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(true, load, 1, 2, 0, object_count));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(load, 1, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, load, 1, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, load, 1, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1887,7 +2145,12 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapTooSmall) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(load, 0, 2, 0, object_count));
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, 2, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_2);
+    };
+    ASSERT_EQ(-EINVAL, do_diff(false, load, 0, 2, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(true, with, 0, 2, 0, object_count));
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -1895,7 +2158,8 @@ TEST_P(TestMockObjectMapDiffRequest, EndObjectMapTooSmall) {
       expect_get_flags(mock_image_ctx, 2, 0, 0);
       expect_load_map(mock_image_ctx, 2, object_map_2, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(load, 0, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, load, 0, 2, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, load, 0, 2, 0, UINT64_MAX));
   }
 }
 
@@ -1923,13 +2187,19 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapTooSmallFromBeginning)
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
       expect_load_map(mock_image_ctx, 1, object_map_1, 0);
     };
-    ASSERT_EQ(-EINVAL, do_diff(load, 0, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, load, 0, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, load, 0, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1957,9 +2227,11 @@ TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapTooSmallFromSnap) {
     expect_load_map(mock_image_ctx, 2, object_map_2, 0);
   };
   if (is_diff_iterate()) {
-    ASSERT_EQ(-EINVAL, do_diff(load, 1, CEPH_NOSNAP, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(false, load, 1, CEPH_NOSNAP, 0, object_count));
+    ASSERT_EQ(-EINVAL, do_diff(true, load, 1, CEPH_NOSNAP, 0, object_count));
   } else {
-    ASSERT_EQ(-EINVAL, do_diff(load, 1, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(false, load, 1, CEPH_NOSNAP, 0, UINT64_MAX));
+    ASSERT_EQ(-EINVAL, do_diff(true, load, 1, CEPH_NOSNAP, 0, UINT64_MAX));
   }
 }
 
@@ -1987,7 +2259,12 @@ TEST_P(TestMockObjectMapDiffRequest, ObjectMapTooLarge) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_diff_iterate(load, 0, CEPH_NOSNAP, expected_diff_state);
+    auto with = [&](MockTestImageCtx& mock_image_ctx) {
+      expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
+      expect_with_map(mock_image_ctx, object_map_head);
+    };
+    test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state);
   } else {
     auto load = [&](MockTestImageCtx& mock_image_ctx) {
       expect_get_flags(mock_image_ctx, 1, 0, 0);
@@ -1995,7 +2272,8 @@ TEST_P(TestMockObjectMapDiffRequest, ObjectMapTooLarge) {
       expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0);
       expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0);
     };
-    test_deep_copy(load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state);
+    test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state);
   }
 }