Add ability to join two ScopedReservation's.

Bug: b:233089187
Change-Id: I7989de7982d31b45be1806bf14c22737a5ef4c82
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3703493
Reviewed-by: Hong Xu <[email protected]>
Commit-Queue: Leonid Baraz <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1014255}
diff --git a/components/reporting/resources/resource_interface.cc b/components/reporting/resources/resource_interface.cc
index 727eb00..b2cd998 100644
--- a/components/reporting/resources/resource_interface.cc
+++ b/components/reporting/resources/resource_interface.cc
@@ -8,6 +8,7 @@
 
 #include <cstdint>
 
+#include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -16,7 +17,7 @@
 
 ScopedReservation::ScopedReservation(
     uint64_t size,
-    scoped_refptr<ResourceInterface> resource_interface)
+    scoped_refptr<ResourceInterface> resource_interface) noexcept
     : resource_interface_(resource_interface) {
   if (!resource_interface->Reserve(size)) {
     return;
@@ -24,7 +25,7 @@
   size_ = size;
 }
 
-ScopedReservation::ScopedReservation(ScopedReservation&& other)
+ScopedReservation::ScopedReservation(ScopedReservation&& other) noexcept
     : resource_interface_(other.resource_interface_),
       size_(std::exchange(other.size_, absl::nullopt)) {}
 
@@ -36,14 +37,28 @@
   if (!reserved()) {
     return false;
   }
-  if (new_size <= 0 || size_.value() < new_size) {
+  if (new_size < 0 || size_.value() < new_size) {
     return false;
   }
   resource_interface_->Discard(size_.value() - new_size);
-  size_ = new_size;
+  if (new_size > 0) {
+    size_ = new_size;
+  } else {
+    size_ = absl::nullopt;
+  }
   return true;
 }
 
+void ScopedReservation::HandOver(ScopedReservation& other) {
+  DCHECK_EQ(resource_interface_.get(), other.resource_interface_.get())
+      << "Reservations are not related";
+  if (!other.reserved()) {
+    return;  // Nothing changes.
+  }
+  const uint64_t old_size = (reserved() ? size_.value() : 0uL);
+  size_ = old_size + std::exchange(other.size_, absl::nullopt).value();
+}
+
 ScopedReservation::~ScopedReservation() {
   if (reserved()) {
     resource_interface_->Discard(size_.value());
diff --git a/components/reporting/resources/resource_interface.h b/components/reporting/resources/resource_interface.h
index 235f42f..56fd3bce 100644
--- a/components/reporting/resources/resource_interface.h
+++ b/components/reporting/resources/resource_interface.h
@@ -60,16 +60,26 @@
 // Can be handed over to another owner.
 class ScopedReservation {
  public:
-  ScopedReservation(uint64_t size,
-                    scoped_refptr<ResourceInterface> resource_interface);
-  ScopedReservation(ScopedReservation&& other);
+  ScopedReservation(
+      uint64_t size,
+      scoped_refptr<ResourceInterface> resource_interface) noexcept;
+  ScopedReservation(ScopedReservation&& other) noexcept;
   ScopedReservation(const ScopedReservation& other) = delete;
+  ScopedReservation& operator=(ScopedReservation&& other) = delete;
   ScopedReservation& operator=(const ScopedReservation& other) = delete;
   ~ScopedReservation();
 
   bool reserved() const;
+
+  // Reduces reservation to |new_size|.
   bool Reduce(uint64_t new_size);
 
+  // Adds |other| to |this| without assigning or releasing any reservation.
+  // Used for seamless transition from one reservation to another (more generic
+  // than std::move). Resets |other| to non-reserved state upon return from this
+  // method.
+  void HandOver(ScopedReservation& other);
+
  private:
   const scoped_refptr<ResourceInterface> resource_interface_;
   absl::optional<uint64_t> size_;
diff --git a/components/reporting/resources/resource_interface_unittest.cc b/components/reporting/resources/resource_interface_unittest.cc
index 63bcbae..304800af 100644
--- a/components/reporting/resources/resource_interface_unittest.cc
+++ b/components/reporting/resources/resource_interface_unittest.cc
@@ -25,6 +25,11 @@
 class ResourceInterfaceTest
     : public ::testing::TestWithParam<scoped_refptr<ResourceInterface>> {
  protected:
+  void SetUp() override {
+    // Make sure parameters define reasonably large total resource size.
+    ASSERT_GE(resource_interface()->GetTotal(), 1u * 1024LLu * 1024LLu);
+  }
+
   scoped_refptr<ResourceInterface> resource_interface() const {
     return GetParam();
   }
@@ -146,7 +151,65 @@
   for (; size >= 2; size /= 2) {
     EXPECT_TRUE(scoped_reservation.Reduce(size / 2));
   }
-  EXPECT_FALSE(scoped_reservation.Reduce(size / 2));
+  EXPECT_TRUE(scoped_reservation.Reduce(size / 2));
+  EXPECT_FALSE(scoped_reservation.reserved());
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationBasicHandOver) {
+  uint64_t size = resource_interface()->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_interface());
+  ASSERT_TRUE(scoped_reservation.reserved());
+  {
+    ScopedReservation another_reservation(size - 1, resource_interface());
+    ASSERT_TRUE(another_reservation.reserved());
+    EXPECT_THAT(resource_interface()->GetUsed(),
+                Eq(resource_interface()->GetTotal() - 1));
+    EXPECT_TRUE(scoped_reservation.reserved());
+    EXPECT_TRUE(another_reservation.reserved());
+    scoped_reservation.HandOver(another_reservation);
+    EXPECT_THAT(resource_interface()->GetUsed(),
+                Eq(resource_interface()->GetTotal() - 1));
+  }
+  // Destruction of |anoter_reservation| does not change the amount used.
+  EXPECT_THAT(resource_interface()->GetUsed(),
+              Eq(resource_interface()->GetTotal() - 1));
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationRepeatingHandOvers) {
+  uint64_t size = resource_interface()->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_interface());
+  EXPECT_TRUE(scoped_reservation.reserved());
+
+  for (; size >= 2; size /= 2) {
+    ScopedReservation another_reservation(size / 2, resource_interface());
+    scoped_reservation.HandOver(another_reservation);
+  }
+  EXPECT_THAT(resource_interface()->GetUsed(),
+              Eq(resource_interface()->GetTotal() - 1));
+}
+
+TEST_P(ResourceInterfaceTest, ScopedReservationEmptyHandOver) {
+  uint64_t size = resource_interface()->GetTotal() / 2;
+  ScopedReservation scoped_reservation(size, resource_interface());
+
+  ASSERT_TRUE(scoped_reservation.reserved());
+  {
+    ScopedReservation another_reservation(size - 1, resource_interface());
+    ASSERT_TRUE(another_reservation.reserved());
+
+    EXPECT_THAT(resource_interface()->GetUsed(),
+                Eq(resource_interface()->GetTotal() - 1));
+    EXPECT_TRUE(scoped_reservation.reserved());
+    EXPECT_TRUE(another_reservation.reserved());
+
+    another_reservation.Reduce(0);
+    ASSERT_FALSE(another_reservation.reserved());
+
+    scoped_reservation.HandOver(another_reservation);
+    EXPECT_THAT(resource_interface()->GetUsed(), Eq(size));
+  }
+  // Destruction of |anoter_reservation| does not change the amount used.
+  EXPECT_THAT(resource_interface()->GetUsed(), Eq(size));
 }
 
 TEST_P(ResourceInterfaceTest, ReservationOverMaxTest) {