Extracts SpdyHeaderBlock::Storage as a separate library. This is a continuation of the attempt to simplify SpdyHeaderBlock. gfe-relnote: Refactoring, no functional change. PiperOrigin-RevId: 284571559 Change-Id: I8396e585439ec58bcdba6aeb22f44f56352625ba
diff --git a/spdy/core/spdy_header_block.cc b/spdy/core/spdy_header_block.cc index 91c8494..9ccc627 100644 --- a/spdy/core/spdy_header_block.cc +++ b/spdy/core/spdy_header_block.cc
@@ -25,9 +25,6 @@ // large header blocks. const size_t kInitialMapBuckets = 11; -// SpdyHeaderBlock::Storage allocates blocks of this size by default. -const size_t kDefaultStorageBlockSize = 2048; - const char kCookieKey[] = "cookie"; const char kNullSeparator = 0; @@ -42,62 +39,7 @@ } // namespace -// This class provides a backing store for SpdyStringPieces. It previously used -// custom allocation logic, but now uses an UnsafeArena instead. It has the -// property that SpdyStringPieces that refer to data in Storage are never -// invalidated until the Storage is deleted or Clear() is called. -// -// Write operations always append to the last block. If there is not enough -// space to perform the write, a new block is allocated, and any unused space -// is wasted. -class SpdyHeaderBlock::Storage { - public: - Storage() : arena_(kDefaultStorageBlockSize) {} - Storage(const Storage&) = delete; - Storage& operator=(const Storage&) = delete; - - SpdyStringPiece Write(const SpdyStringPiece s) { - return SpdyStringPiece(arena_.Memdup(s.data(), s.size()), s.size()); - } - - // If |s| points to the most recent allocation from arena_, the arena will - // reclaim the memory. Otherwise, this method is a no-op. - void Rewind(const SpdyStringPiece s) { - arena_.Free(const_cast<char*>(s.data()), s.size()); - } - - void Clear() { arena_.Reset(); } - - // Given a list of fragments and a separator, writes the fragments joined by - // the separator to a contiguous region of memory. Returns a SpdyStringPiece - // pointing to the region of memory. - SpdyStringPiece WriteFragments(const std::vector<SpdyStringPiece>& fragments, - SpdyStringPiece separator) { - if (fragments.empty()) { - return SpdyStringPiece(); - } - size_t total_size = separator.size() * (fragments.size() - 1); - for (const auto fragment : fragments) { - total_size += fragment.size(); - } - char* dst = arena_.Alloc(total_size); - size_t written = Join(dst, fragments, separator); - DCHECK_EQ(written, total_size); - return SpdyStringPiece(dst, total_size); - } - - size_t bytes_allocated() const { return arena_.status().bytes_allocated(); } - - // TODO(xunjieli): https://crbug.com/669108. Merge this with bytes_allocated() - size_t EstimateMemoryUsage() const { - return arena_.status().bytes_allocated(); - } - - private: - SpdyUnsafeArena arena_; -}; - -SpdyHeaderBlock::HeaderValue::HeaderValue(Storage* storage, +SpdyHeaderBlock::HeaderValue::HeaderValue(SpdyHeaderStorage* storage, SpdyStringPiece key, SpdyStringPiece initial_value) : storage_(storage), @@ -186,8 +128,8 @@ SpdyHeaderBlock::ValueProxy::~ValueProxy() { // If the ValueProxy is destroyed while lookup_result_ == block_->end(), - // the assignment operator was never used, and the block's Storage can - // reclaim the memory used by the key. This makes lookup-only access to + // the assignment operator was never used, and the block's SpdyHeaderStorage + // can reclaim the memory used by the key. This makes lookup-only access to // SpdyHeaderBlock through operator[] memory-neutral. if (valid_ && lookup_result_ == block_->map_.end()) { block_->GetStorage()->Rewind(key_); @@ -197,7 +139,7 @@ SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=( const SpdyStringPiece value) { *spdy_header_block_value_size_ += value.size(); - Storage* storage = block_->GetStorage(); + SpdyHeaderStorage* storage = block_->GetStorage(); if (lookup_result_ == block_->map_.end()) { SPDY_DVLOG(1) << "Inserting: (" << key_ << ", " << value << ")"; lookup_result_ = @@ -300,7 +242,7 @@ SPDY_DVLOG(1) << "Updating key: " << iter->first << " with value: " << value.second; value_size_ -= iter->second.SizeEstimate(); - auto* storage = GetStorage(); + SpdyHeaderStorage* storage = GetStorage(); iter->second = HeaderValue(storage, iter->first, storage->Write(value.second)); } @@ -350,14 +292,14 @@ void SpdyHeaderBlock::AppendHeader(const SpdyStringPiece key, const SpdyStringPiece value) { auto backed_key = WriteKey(key); - auto* storage = GetStorage(); + SpdyHeaderStorage* storage = GetStorage(); map_.emplace(std::make_pair( backed_key, HeaderValue(storage, backed_key, storage->Write(value)))); } -SpdyHeaderBlock::Storage* SpdyHeaderBlock::GetStorage() { +SpdyHeaderStorage* SpdyHeaderBlock::GetStorage() { if (storage_ == nullptr) { - storage_ = std::make_unique<Storage>(); + storage_ = std::make_unique<SpdyHeaderStorage>(); } return storage_.get(); } @@ -375,23 +317,4 @@ } } -size_t Join(char* dst, - const std::vector<SpdyStringPiece>& fragments, - SpdyStringPiece separator) { - if (fragments.empty()) { - return 0; - } - auto* original_dst = dst; - auto it = fragments.begin(); - memcpy(dst, it->data(), it->size()); - dst += it->size(); - for (++it; it != fragments.end(); ++it) { - memcpy(dst, separator.data(), separator.size()); - dst += separator.size(); - memcpy(dst, it->data(), it->size()); - dst += it->size(); - } - return dst - original_dst; -} - } // namespace spdy
diff --git a/spdy/core/spdy_header_block.h b/spdy/core/spdy_header_block.h index b9c0967..ff5f4c9 100644 --- a/spdy/core/spdy_header_block.h +++ b/spdy/core/spdy_header_block.h
@@ -13,6 +13,7 @@ #include <utility> #include <vector> +#include "net/third_party/quiche/src/spdy/core/spdy_header_storage.h" #include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h" #include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" #include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h" @@ -38,13 +39,11 @@ // It's expected that keys are rarely deleted from a SpdyHeaderBlock. class SPDY_EXPORT_PRIVATE SpdyHeaderBlock { private: - class Storage; - // Stores a list of value fragments that can be joined later with a // key-dependent separator. class SPDY_EXPORT_PRIVATE HeaderValue { public: - HeaderValue(Storage* storage, + HeaderValue(SpdyHeaderStorage* storage, SpdyStringPiece key, SpdyStringPiece initial_value); @@ -73,7 +72,7 @@ // fragments and separators. SpdyStringPiece ConsolidatedValue() const; - mutable Storage* storage_; + mutable SpdyHeaderStorage* storage_; mutable std::vector<SpdyStringPiece> fragments_; // The first element is the key; the second is the consolidated value. mutable std::pair<SpdyStringPiece, SpdyStringPiece> pair_; @@ -180,7 +179,7 @@ // This object provides automatic conversions that allow SpdyHeaderBlock to be // nearly a drop-in replacement for // SpdyLinkedHashMap<std::string, std::string>. - // It reads data from or writes data to a SpdyHeaderBlock::Storage. + // It reads data from or writes data to a SpdyHeaderStorage. class SPDY_EXPORT_PRIVATE ValueProxy { public: ~ValueProxy(); @@ -226,25 +225,19 @@ friend class test::SpdyHeaderBlockPeer; void AppendHeader(const SpdyStringPiece key, const SpdyStringPiece value); - Storage* GetStorage(); + SpdyHeaderStorage* GetStorage(); SpdyStringPiece WriteKey(const SpdyStringPiece key); size_t bytes_allocated() const; // SpdyStringPieces held by |map_| point to memory owned by |*storage_|. // |storage_| might be nullptr as long as |map_| is empty. MapType map_; - std::unique_ptr<Storage> storage_; + std::unique_ptr<SpdyHeaderStorage> storage_; size_t key_size_ = 0; size_t value_size_ = 0; }; -// Writes |fragments| to |dst|, joined by |separator|. |dst| must be large -// enough to hold the result. Returns the number of bytes written. -SPDY_EXPORT_PRIVATE size_t Join(char* dst, - const std::vector<SpdyStringPiece>& fragments, - SpdyStringPiece separator); - } // namespace spdy #endif // QUICHE_SPDY_CORE_SPDY_HEADER_BLOCK_H_
diff --git a/spdy/core/spdy_header_block_test.cc b/spdy/core/spdy_header_block_test.cc index 56facb4..a744a3b 100644 --- a/spdy/core/spdy_header_block_test.cc +++ b/spdy/core/spdy_header_block_test.cc
@@ -192,32 +192,6 @@ Pair("foo", "baz"))); } -TEST(JoinTest, JoinEmpty) { - std::vector<SpdyStringPiece> empty; - SpdyStringPiece separator = ", "; - char buf[10] = ""; - size_t written = Join(buf, empty, separator); - EXPECT_EQ(0u, written); -} - -TEST(JoinTest, JoinOne) { - std::vector<SpdyStringPiece> v = {"one"}; - SpdyStringPiece separator = ", "; - char buf[15]; - size_t written = Join(buf, v, separator); - EXPECT_EQ(3u, written); - EXPECT_EQ("one", SpdyStringPiece(buf, written)); -} - -TEST(JoinTest, JoinMultiple) { - std::vector<SpdyStringPiece> v = {"one", "two", "three"}; - SpdyStringPiece separator = ", "; - char buf[15]; - size_t written = Join(buf, v, separator); - EXPECT_EQ(15u, written); - EXPECT_EQ("one, two, three", SpdyStringPiece(buf, written)); -} - namespace { size_t SpdyHeaderBlockSize(const SpdyHeaderBlock& block) { size_t size = 0;
diff --git a/spdy/core/spdy_header_storage.cc b/spdy/core/spdy_header_storage.cc new file mode 100644 index 0000000..7028e90 --- /dev/null +++ b/spdy/core/spdy_header_storage.cc
@@ -0,0 +1,56 @@ +#include "net/third_party/quiche/src/spdy/core/spdy_header_storage.h" + +namespace spdy { +namespace { + +// SpdyHeaderStorage allocates blocks of this size by default. +const size_t kDefaultStorageBlockSize = 2048; + +} // namespace + +SpdyHeaderStorage::SpdyHeaderStorage() : arena_(kDefaultStorageBlockSize) {} + +SpdyStringPiece SpdyHeaderStorage::Write(const SpdyStringPiece s) { + return SpdyStringPiece(arena_.Memdup(s.data(), s.size()), s.size()); +} + +void SpdyHeaderStorage::Rewind(const SpdyStringPiece s) { + arena_.Free(const_cast<char*>(s.data()), s.size()); +} + +SpdyStringPiece SpdyHeaderStorage::WriteFragments( + const std::vector<SpdyStringPiece>& fragments, + SpdyStringPiece separator) { + if (fragments.empty()) { + return SpdyStringPiece(); + } + size_t total_size = separator.size() * (fragments.size() - 1); + for (const auto fragment : fragments) { + total_size += fragment.size(); + } + char* dst = arena_.Alloc(total_size); + size_t written = Join(dst, fragments, separator); + DCHECK_EQ(written, total_size); + return SpdyStringPiece(dst, total_size); +} + +size_t Join(char* dst, + const std::vector<SpdyStringPiece>& fragments, + SpdyStringPiece separator) { + if (fragments.empty()) { + return 0; + } + auto* original_dst = dst; + auto it = fragments.begin(); + memcpy(dst, it->data(), it->size()); + dst += it->size(); + for (++it; it != fragments.end(); ++it) { + memcpy(dst, separator.data(), separator.size()); + dst += separator.size(); + memcpy(dst, it->data(), it->size()); + dst += it->size(); + } + return dst - original_dst; +} + +} // namespace spdy
diff --git a/spdy/core/spdy_header_storage.h b/spdy/core/spdy_header_storage.h new file mode 100644 index 0000000..26ae2ae --- /dev/null +++ b/spdy/core/spdy_header_storage.h
@@ -0,0 +1,55 @@ +#ifndef QUICHE_SPDY_CORE_SPDY_HEADER_STORAGE_H_ +#define QUICHE_SPDY_CORE_SPDY_HEADER_STORAGE_H_ + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h" + +namespace spdy { + +// This class provides a backing store for SpdyStringPieces. It previously used +// custom allocation logic, but now uses an UnsafeArena instead. It has the +// property that SpdyStringPieces that refer to data in SpdyHeaderStorage are +// never invalidated until the SpdyHeaderStorage is deleted or Clear() is +// called. +// +// Write operations always append to the last block. If there is not enough +// space to perform the write, a new block is allocated, and any unused space +// is wasted. +class SPDY_EXPORT_PRIVATE SpdyHeaderStorage { + public: + SpdyHeaderStorage(); + SpdyHeaderStorage(const SpdyHeaderStorage&) = delete; + SpdyHeaderStorage& operator=(const SpdyHeaderStorage&) = delete; + + SpdyStringPiece Write(SpdyStringPiece s); + + // If |s| points to the most recent allocation from arena_, the arena will + // reclaim the memory. Otherwise, this method is a no-op. + void Rewind(SpdyStringPiece s); + + void Clear() { arena_.Reset(); } + + // Given a list of fragments and a separator, writes the fragments joined by + // the separator to a contiguous region of memory. Returns a SpdyStringPiece + // pointing to the region of memory. + SpdyStringPiece WriteFragments(const std::vector<SpdyStringPiece>& fragments, + SpdyStringPiece separator); + + size_t bytes_allocated() const { return arena_.status().bytes_allocated(); } + + size_t EstimateMemoryUsage() const { return bytes_allocated(); } + + private: + SpdyUnsafeArena arena_; +}; + +// Writes |fragments| to |dst|, joined by |separator|. |dst| must be large +// enough to hold the result. Returns the number of bytes written. +SPDY_EXPORT_PRIVATE size_t Join(char* dst, + const std::vector<SpdyStringPiece>& fragments, + SpdyStringPiece separator); + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_HEADER_STORAGE_H_
diff --git a/spdy/core/spdy_header_storage_test.cc b/spdy/core/spdy_header_storage_test.cc new file mode 100644 index 0000000..6f82013 --- /dev/null +++ b/spdy/core/spdy_header_storage_test.cc
@@ -0,0 +1,35 @@ +#include "net/third_party/quiche/src/spdy/core/spdy_header_storage.h" + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_test.h" + +namespace spdy { +namespace test { + +TEST(JoinTest, JoinEmpty) { + std::vector<SpdyStringPiece> empty; + SpdyStringPiece separator = ", "; + char buf[10] = ""; + size_t written = Join(buf, empty, separator); + EXPECT_EQ(0u, written); +} + +TEST(JoinTest, JoinOne) { + std::vector<SpdyStringPiece> v = {"one"}; + SpdyStringPiece separator = ", "; + char buf[15]; + size_t written = Join(buf, v, separator); + EXPECT_EQ(3u, written); + EXPECT_EQ("one", SpdyStringPiece(buf, written)); +} + +TEST(JoinTest, JoinMultiple) { + std::vector<SpdyStringPiece> v = {"one", "two", "three"}; + SpdyStringPiece separator = ", "; + char buf[15]; + size_t written = Join(buf, v, separator); + EXPECT_EQ(15u, written); + EXPECT_EQ("one, two, three", SpdyStringPiece(buf, written)); +} + +} // namespace test +} // namespace spdy