diff --git a/modules/congestion_controller/bbr/BUILD.gn b/modules/congestion_controller/bbr/BUILD.gn index c8ff076a7a..fa0bdbf23a 100644 --- a/modules/congestion_controller/bbr/BUILD.gn +++ b/modules/congestion_controller/bbr/BUILD.gn @@ -52,6 +52,17 @@ rtc_source_set("data_transfer_tracker") { "../../../rtc_base:rtc_base_approved", ] } + +rtc_source_set("packet_number_indexed_queue") { + visibility = [ ":*" ] + sources = [ + "packet_number_indexed_queue.h", + ] + deps = [ + "../../../rtc_base:checks", + ] +} + rtc_source_set("rtt_stats") { visibility = [ ":*" ] sources = [ @@ -76,6 +87,7 @@ if (rtc_include_tests) { sources = [ "bbr_network_controller_unittest.cc", "data_transfer_tracker_unittest.cc", + "packet_number_indexed_queue_unittest.cc", "rtt_stats_unittest.cc", "windowed_filter_unittest.cc", ] @@ -83,6 +95,7 @@ if (rtc_include_tests) { ":bbr", ":bbr_controller", ":data_transfer_tracker", + ":packet_number_indexed_queue", ":rtt_stats", ":windowed_filter", "../../../api/transport:network_control_test", diff --git a/modules/congestion_controller/bbr/packet_number_indexed_queue.h b/modules/congestion_controller/bbr/packet_number_indexed_queue.h new file mode 100644 index 0000000000..208920947e --- /dev/null +++ b/modules/congestion_controller/bbr/packet_number_indexed_queue.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// Based on the Quic implementation in Chromium. + +#ifndef MODULES_CONGESTION_CONTROLLER_BBR_PACKET_NUMBER_INDEXED_QUEUE_H_ +#define MODULES_CONGESTION_CONTROLLER_BBR_PACKET_NUMBER_INDEXED_QUEUE_H_ + +#include +#include +#include + +#include "rtc_base/checks.h" + +namespace webrtc { +namespace bbr { + +// PacketNumberIndexedQueue is a queue of mostly continuous numbered entries +// which supports the following operations: +// - adding elements to the end of the queue, or at some point past the end +// - removing elements in any order +// - retrieving elements +// If all elements are inserted in order, all of the operations above are +// amortized O(1) time. +// +// Internally, the data structure is a deque where each element is marked as +// present or not. The deque starts at the lowest present index. Whenever an +// element is removed, it's marked as not present, and the front of the deque is +// cleared of elements that are not present. +// +// The tail of the queue is not cleared due to the assumption of entries being +// inserted in order, though removing all elements of the queue will return it +// to its initial state. +// +// Note that this data structure is inherently hazardous, since an addition of +// just two entries will cause it to consume all of the memory available. +// Because of that, it is not a general-purpose container and should not be used +// as one. +template +class PacketNumberIndexedQueue { + public: + PacketNumberIndexedQueue() + : number_of_present_entries_(0), first_packet_(0) {} + + // Retrieve the entry associated with the packet number. Returns the pointer + // to the entry in case of success, or nullptr if the entry does not exist. + T* GetEntry(int64_t packet_number); + const T* GetEntry(int64_t packet_number) const; + + // Inserts data associated |packet_number| into (or past) the end of the + // queue, filling up the missing intermediate entries as necessary. Returns + // true if the element has been inserted successfully, false if it was already + // in the queue or inserted out of order. + template + bool Emplace(int64_t packet_number, Args&&... args); + + // Removes data associated with |packet_number| and frees the slots in the + // queue as necessary. + bool Remove(int64_t packet_number); + + bool IsEmpty() const { return number_of_present_entries_ == 0; } + + // Returns the number of entries in the queue. + size_t number_of_present_entries() const { + return number_of_present_entries_; + } + + // Returns the number of entries allocated in the underlying deque. This is + // proportional to the memory usage of the queue. + size_t entry_slots_used() const { return entries_.size(); } + + // Packet number of the first entry in the queue. Zero if the queue is empty. + int64_t first_packet() const { return first_packet_; } + + // Packet number of the last entry ever inserted in the queue. Note that the + // entry in question may have already been removed. Zero if the queue is + // empty. + int64_t last_packet() const { + if (IsEmpty()) { + return 0; + } + return first_packet_ + entries_.size() - 1; + } + + private: + // Wrapper around T used to mark whether the entry is actually in the map. + struct EntryWrapper { + T data; + bool present; + + EntryWrapper() : data(), present(false) {} + + template + explicit EntryWrapper(Args&&... args) + : data(std::forward(args)...), present(true) {} + }; + + // Cleans up unused slots in the front after removing an element. + void Cleanup(); + + const EntryWrapper* GetEntryWrapper(int64_t offset) const; + EntryWrapper* GetEntryWrapper(int64_t offset) { + const auto* const_this = this; + return const_cast(const_this->GetEntryWrapper(offset)); + } + + std::deque entries_; + size_t number_of_present_entries_; + int64_t first_packet_; +}; + +template +T* PacketNumberIndexedQueue::GetEntry(int64_t packet_number) { + EntryWrapper* entry = GetEntryWrapper(packet_number); + if (entry == nullptr) { + return nullptr; + } + return &entry->data; +} + +template +const T* PacketNumberIndexedQueue::GetEntry(int64_t packet_number) const { + const EntryWrapper* entry = GetEntryWrapper(packet_number); + if (entry == nullptr) { + return nullptr; + } + return &entry->data; +} + +template +template +bool PacketNumberIndexedQueue::Emplace(int64_t packet_number, + Args&&... args) { + if (IsEmpty()) { + RTC_DCHECK(entries_.empty()); + RTC_DCHECK_EQ(0u, first_packet_); + + entries_.emplace_back(std::forward(args)...); + number_of_present_entries_ = 1; + first_packet_ = packet_number; + return true; + } + + // Do not allow insertion out-of-order. + if (packet_number <= last_packet()) { + return false; + } + + // Handle potentially missing elements. + int64_t offset = packet_number - first_packet_; + if (offset > static_cast(entries_.size())) { + entries_.resize(offset); + } + + number_of_present_entries_++; + entries_.emplace_back(std::forward(args)...); + RTC_DCHECK_EQ(packet_number, last_packet()); + return true; +} + +template +bool PacketNumberIndexedQueue::Remove(int64_t packet_number) { + EntryWrapper* entry = GetEntryWrapper(packet_number); + if (entry == nullptr) { + return false; + } + entry->present = false; + number_of_present_entries_--; + + if (packet_number == first_packet()) { + Cleanup(); + } + return true; +} + +template +void PacketNumberIndexedQueue::Cleanup() { + while (!entries_.empty() && !entries_.front().present) { + entries_.pop_front(); + first_packet_++; + } + if (entries_.empty()) { + first_packet_ = 0; + } +} + +template +auto PacketNumberIndexedQueue::GetEntryWrapper(int64_t offset) const + -> const EntryWrapper* { + if (offset < first_packet_) { + return nullptr; + } + + offset -= first_packet_; + if (offset >= static_cast(entries_.size())) { + return nullptr; + } + + const EntryWrapper* entry = &entries_[offset]; + if (!entry->present) { + return nullptr; + } + + return entry; +} + +} // namespace bbr +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_BBR_PACKET_NUMBER_INDEXED_QUEUE_H_ diff --git a/modules/congestion_controller/bbr/packet_number_indexed_queue_unittest.cc b/modules/congestion_controller/bbr/packet_number_indexed_queue_unittest.cc new file mode 100644 index 0000000000..acfa871710 --- /dev/null +++ b/modules/congestion_controller/bbr/packet_number_indexed_queue_unittest.cc @@ -0,0 +1,187 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/congestion_controller/bbr/packet_number_indexed_queue.h" + +#include +#include +#include + +#include "test/gtest.h" + +namespace webrtc { +namespace bbr { +namespace { + +class PacketNumberIndexedQueueTest : public ::testing::Test { + public: + PacketNumberIndexedQueueTest() {} + + protected: + PacketNumberIndexedQueue queue_; +}; + +TEST_F(PacketNumberIndexedQueueTest, InitialState) { + EXPECT_TRUE(queue_.IsEmpty()); + EXPECT_EQ(0u, queue_.first_packet()); + EXPECT_EQ(0u, queue_.last_packet()); + EXPECT_EQ(0u, queue_.number_of_present_entries()); + EXPECT_EQ(0u, queue_.entry_slots_used()); +} + +TEST_F(PacketNumberIndexedQueueTest, InsertingContinuousElements) { + ASSERT_TRUE(queue_.Emplace(1001, "one")); + EXPECT_EQ("one", *queue_.GetEntry(1001)); + + ASSERT_TRUE(queue_.Emplace(1002, "two")); + EXPECT_EQ("two", *queue_.GetEntry(1002)); + + EXPECT_FALSE(queue_.IsEmpty()); + EXPECT_EQ(1001u, queue_.first_packet()); + EXPECT_EQ(1002u, queue_.last_packet()); + EXPECT_EQ(2u, queue_.number_of_present_entries()); + EXPECT_EQ(2u, queue_.entry_slots_used()); +} + +TEST_F(PacketNumberIndexedQueueTest, InsertingOutOfOrder) { + queue_.Emplace(1001, "one"); + + ASSERT_TRUE(queue_.Emplace(1003, "three")); + EXPECT_EQ(nullptr, queue_.GetEntry(1002)); + EXPECT_EQ("three", *queue_.GetEntry(1003)); + + EXPECT_EQ(1001u, queue_.first_packet()); + EXPECT_EQ(1003u, queue_.last_packet()); + EXPECT_EQ(2u, queue_.number_of_present_entries()); + EXPECT_EQ(3u, queue_.entry_slots_used()); + + ASSERT_FALSE(queue_.Emplace(1002, "two")); +} + +TEST_F(PacketNumberIndexedQueueTest, InsertingIntoPast) { + queue_.Emplace(1001, "one"); + EXPECT_FALSE(queue_.Emplace(1000, "zero")); +} + +TEST_F(PacketNumberIndexedQueueTest, InsertingDuplicate) { + queue_.Emplace(1001, "one"); + EXPECT_FALSE(queue_.Emplace(1001, "one")); +} + +TEST_F(PacketNumberIndexedQueueTest, RemoveInTheMiddle) { + queue_.Emplace(1001, "one"); + queue_.Emplace(1002, "two"); + queue_.Emplace(1003, "three"); + + ASSERT_TRUE(queue_.Remove(1002)); + EXPECT_EQ(nullptr, queue_.GetEntry(1002)); + + EXPECT_EQ(1001u, queue_.first_packet()); + EXPECT_EQ(1003u, queue_.last_packet()); + EXPECT_EQ(2u, queue_.number_of_present_entries()); + EXPECT_EQ(3u, queue_.entry_slots_used()); + + EXPECT_FALSE(queue_.Emplace(1002, "two")); + EXPECT_TRUE(queue_.Emplace(1004, "four")); +} + +TEST_F(PacketNumberIndexedQueueTest, RemoveAtImmediateEdges) { + queue_.Emplace(1001, "one"); + queue_.Emplace(1002, "two"); + queue_.Emplace(1003, "three"); + ASSERT_TRUE(queue_.Remove(1001)); + EXPECT_EQ(nullptr, queue_.GetEntry(1001)); + ASSERT_TRUE(queue_.Remove(1003)); + EXPECT_EQ(nullptr, queue_.GetEntry(1003)); + + EXPECT_EQ(1002u, queue_.first_packet()); + EXPECT_EQ(1003u, queue_.last_packet()); + EXPECT_EQ(1u, queue_.number_of_present_entries()); + EXPECT_EQ(2u, queue_.entry_slots_used()); + + EXPECT_TRUE(queue_.Emplace(1004, "four")); +} + +TEST_F(PacketNumberIndexedQueueTest, RemoveAtDistantFront) { + queue_.Emplace(1001, "one"); + queue_.Emplace(1002, "one (kinda)"); + queue_.Emplace(2001, "two"); + + EXPECT_EQ(1001u, queue_.first_packet()); + EXPECT_EQ(2001u, queue_.last_packet()); + EXPECT_EQ(3u, queue_.number_of_present_entries()); + EXPECT_EQ(1001u, queue_.entry_slots_used()); + + ASSERT_TRUE(queue_.Remove(1002)); + EXPECT_EQ(1001u, queue_.first_packet()); + EXPECT_EQ(2001u, queue_.last_packet()); + EXPECT_EQ(2u, queue_.number_of_present_entries()); + EXPECT_EQ(1001u, queue_.entry_slots_used()); + + ASSERT_TRUE(queue_.Remove(1001)); + EXPECT_EQ(2001u, queue_.first_packet()); + EXPECT_EQ(2001u, queue_.last_packet()); + EXPECT_EQ(1u, queue_.number_of_present_entries()); + EXPECT_EQ(1u, queue_.entry_slots_used()); +} + +TEST_F(PacketNumberIndexedQueueTest, RemoveAtDistantBack) { + queue_.Emplace(1001, "one"); + queue_.Emplace(2001, "two"); + + EXPECT_EQ(1001u, queue_.first_packet()); + EXPECT_EQ(2001u, queue_.last_packet()); + + ASSERT_TRUE(queue_.Remove(2001)); + EXPECT_EQ(1001u, queue_.first_packet()); + EXPECT_EQ(2001u, queue_.last_packet()); +} + +TEST_F(PacketNumberIndexedQueueTest, ClearAndRepopulate) { + queue_.Emplace(1001, "one"); + queue_.Emplace(2001, "two"); + + ASSERT_TRUE(queue_.Remove(1001)); + ASSERT_TRUE(queue_.Remove(2001)); + EXPECT_TRUE(queue_.IsEmpty()); + EXPECT_EQ(0u, queue_.first_packet()); + EXPECT_EQ(0u, queue_.last_packet()); + + EXPECT_TRUE(queue_.Emplace(101, "one")); + EXPECT_TRUE(queue_.Emplace(201, "two")); + EXPECT_EQ(101u, queue_.first_packet()); + EXPECT_EQ(201u, queue_.last_packet()); +} + +TEST_F(PacketNumberIndexedQueueTest, FailToRemoveElementsThatNeverExisted) { + ASSERT_FALSE(queue_.Remove(1000)); + queue_.Emplace(1001, "one"); + ASSERT_FALSE(queue_.Remove(1000)); + ASSERT_FALSE(queue_.Remove(1002)); +} + +TEST_F(PacketNumberIndexedQueueTest, FailToRemoveElementsTwice) { + queue_.Emplace(1001, "one"); + ASSERT_TRUE(queue_.Remove(1001)); + ASSERT_FALSE(queue_.Remove(1001)); + ASSERT_FALSE(queue_.Remove(1001)); +} + +TEST_F(PacketNumberIndexedQueueTest, ConstGetter) { + queue_.Emplace(1001, "one"); + const auto& const_queue = queue_; + + EXPECT_EQ("one", *const_queue.GetEntry(1001)); + EXPECT_EQ(nullptr, const_queue.GetEntry(1002)); +} + +} // namespace +} // namespace bbr +} // namespace webrtc