-
Notifications
You must be signed in to change notification settings - Fork 5.3k
buffer: improve read reservations to efficiently handle multiple slices #14054
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9c5199f
246f167
ae815f1
6adeeab
6af8023
c8c9cce
a0b68b7
ff13426
c0661eb
0416701
6e9f1aa
eb3fe0e
aff7e53
508d4dc
abcbe9e
be6a9a3
11a4362
532edbb
53fbc33
4bef236
efd8057
75278b6
e56585c
fc7b592
45e7353
5885132
675cd59
a1e6c3e
0e80aca
a5deffa
dc4d618
7bd544a
4e0331d
b4c8e46
112e3bd
6195334
3475f1d
f8d2a72
01e6ebd
1e20eeb
d3559f8
54d0162
c837704
530e045
873f6be
0c92529
2cfd939
0c26ceb
07a9314
6bc1fc3
9f3a717
ef66dc4
47d7cfd
ea5e975
4a92b7d
97bd65b
7b94c6f
180ab4b
875e8d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| #include "envoy/common/platform.h" | ||
| #include "envoy/common/pure.h" | ||
|
|
||
| #include "common/common/assert.h" | ||
| #include "common/common/byte_order.h" | ||
| #include "common/common/utility.h" | ||
|
|
||
|
|
@@ -72,6 +73,18 @@ class SliceData { | |
|
|
||
| using SliceDataPtr = std::unique_ptr<SliceData>; | ||
|
|
||
| class Reservation; | ||
| class ReservationSingleSlice; | ||
|
|
||
| // Base class for an object to manage the ownership for slices in a `Reservation` or | ||
| // `ReservationSingleSlice`. | ||
| class ReservationSlicesOwner { | ||
| public: | ||
| virtual ~ReservationSlicesOwner() = default; | ||
| }; | ||
|
|
||
| using ReservationSlicesOwnerPtr = std::unique_ptr<ReservationSlicesOwner>; | ||
|
|
||
| /** | ||
| * A basic buffer abstraction. | ||
| */ | ||
|
|
@@ -129,16 +142,6 @@ class Instance { | |
| */ | ||
| virtual void prepend(Instance& data) PURE; | ||
|
|
||
| /** | ||
| * Commit a set of slices originally obtained from reserve(). The number of slices should match | ||
| * the number obtained from reserve(). The size of each slice can also be altered. Commit must | ||
| * occur once following a reserve() without any mutating operations in between other than to the | ||
| * iovecs len_ fields. | ||
| * @param iovecs supplies the array of slices to commit. | ||
| * @param num_iovecs supplies the size of the slices array. | ||
| */ | ||
| virtual void commit(RawSlice* iovecs, uint64_t num_iovecs) PURE; | ||
|
|
||
| /** | ||
| * Copy out a section of the buffer. | ||
| * @param start supplies the buffer index to start copying from. | ||
|
|
@@ -202,13 +205,22 @@ class Instance { | |
| virtual void move(Instance& rhs, uint64_t length) PURE; | ||
|
|
||
| /** | ||
| * Reserve space in the buffer. | ||
| * @param length supplies the amount of space to reserve. | ||
| * @param iovecs supplies the slices to fill with reserved memory. | ||
| * @param num_iovecs supplies the size of the slices array. | ||
| * @return the number of iovecs used to reserve the space. | ||
| * Reserve space in the buffer for reading into. The amount of space reserved is determined | ||
| * based on buffer settings and performance considerations. | ||
| * @return a `Reservation`, on which `commit()` can be called, or which can | ||
| * be destructed to discard any resources in the `Reservation`. | ||
| */ | ||
| virtual uint64_t reserve(uint64_t length, RawSlice* iovecs, uint64_t num_iovecs) PURE; | ||
| virtual Reservation reserveForRead() PURE; | ||
|
|
||
| /** | ||
| * Reserve space in the buffer in a single slice. | ||
| * @param length the exact length of the reservation. | ||
| * @param separate_slice specifies whether the reserved space must be in a separate slice | ||
| * from any other data in this buffer. | ||
| * @return a `ReservationSingleSlice` which has exactly one slice in it. | ||
| */ | ||
| virtual ReservationSingleSlice reserveSingleSlice(uint64_t length, | ||
| bool separate_slice = false) PURE; | ||
|
|
||
| /** | ||
| * Search for an occurrence of data within the buffer. | ||
|
|
@@ -414,6 +426,17 @@ class Instance { | |
| * the low watermark. | ||
| */ | ||
| virtual bool highWatermarkTriggered() const PURE; | ||
|
|
||
| private: | ||
| friend Reservation; | ||
| friend ReservationSingleSlice; | ||
|
|
||
| /** | ||
| * Called by a `Reservation` to commit `length` bytes of the | ||
| * reservation. | ||
| */ | ||
| virtual void commit(uint64_t length, absl::Span<RawSlice> slices, | ||
| ReservationSlicesOwnerPtr slices_owner) PURE; | ||
| }; | ||
|
|
||
| using InstancePtr = std::unique_ptr<Instance>; | ||
|
|
@@ -441,5 +464,140 @@ class WatermarkFactory { | |
| using WatermarkFactoryPtr = std::unique_ptr<WatermarkFactory>; | ||
| using WatermarkFactorySharedPtr = std::shared_ptr<WatermarkFactory>; | ||
|
|
||
| /** | ||
| * Holds an in-progress addition to a buffer. | ||
| * | ||
| * @note For performance reasons, this class is passed by value to | ||
| * avoid an extra allocation, so it cannot have any virtual methods. | ||
| */ | ||
| class Reservation final { | ||
| public: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the default move constructor work for this class? I looked at the implementation of absl::InlinedVector's move constructor and I see it sets the size of the original to 0 in cases where inlined storage is used (good). |
||
| Reservation(Reservation&&) = default; | ||
ggreenway marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ~Reservation() = default; | ||
|
|
||
| /** | ||
| * @return an array of `RawSlice` of length `numSlices()`. | ||
| */ | ||
| RawSlice* slices() { return slices_.data(); } | ||
| const RawSlice* slices() const { return slices_.data(); } | ||
|
|
||
| /** | ||
| * @return the number of slices present. | ||
| */ | ||
| uint64_t numSlices() const { return slices_.size(); } | ||
|
|
||
| /** | ||
| * @return the total length of the Reservation. | ||
| */ | ||
| uint64_t length() const { return length_; } | ||
|
|
||
| /** | ||
| * Commits some or all of the data in the reservation. | ||
| * @param length supplies the number of bytes to commit. This must be | ||
| * less than or equal to the size of the `Reservation`. | ||
| * | ||
| * @note No other methods should be called on the object after `commit()` is called. | ||
| */ | ||
| void commit(uint64_t length) { | ||
antoniovicente marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ENVOY_BUG(length <= length_, "commit() length must be <= size of the Reservation"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @asraa One thing about ENVOY_BUG is that it should be tested. The behavior of OwnedImpl::commit when length is larger than the reservation seems well defined (good).
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the situation here that it's safe to have
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both conditions are the same,
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made it ENVOY_BUG instead of ASSERT because this condition MAY mean that the caller thought they were committing more bytes than they actually commit, which MAY mean the data in the buffer is now not what the caller expected (a pretty serious bug). However,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The bytes committed is min(length, length_). Tests verify that this case is handled gracefully.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, thanks for the followup. Just trying to be clear on the use of |
||
| ASSERT(length == 0 || !slices_.empty(), | ||
| "Reservation.commit() called on empty Reservation; possible double-commit()."); | ||
| buffer_.commit(length, absl::MakeSpan(slices_), std::move(slices_owner_)); | ||
| length_ = 0; | ||
| slices_.clear(); | ||
| ASSERT(slices_owner_ == nullptr); | ||
| } | ||
|
|
||
| // Tuned to allow reads of 128k, using 16k slices. | ||
| static constexpr uint32_t MAX_SLICES_ = 8; | ||
|
|
||
| private: | ||
| Reservation(Instance& buffer) : buffer_(buffer) {} | ||
|
|
||
| // The buffer that created this `Reservation`. | ||
| Instance& buffer_; | ||
|
|
||
| // The combined length of all slices in the Reservation. | ||
| uint64_t length_; | ||
|
|
||
| // The RawSlices in the reservation, usable by operations such as `::readv()`. | ||
| absl::InlinedVector<RawSlice, MAX_SLICES_> slices_; | ||
|
|
||
| // An owner that can be set by the creator of the `Reservation` to free slices upon | ||
| // destruction. | ||
| ReservationSlicesOwnerPtr slices_owner_; | ||
|
|
||
| public: | ||
| // The following are for use only by implementations of Buffer. Because c++ | ||
| // doesn't allow inheritance of friendship, these are just trying to make | ||
| // misuse easy to spot in a code review. | ||
| static Reservation bufferImplUseOnlyConstruct(Instance& buffer) { return Reservation(buffer); } | ||
| decltype(slices_)& bufferImplUseOnlySlices() { return slices_; } | ||
| ReservationSlicesOwnerPtr& bufferImplUseOnlySlicesOwner() { return slices_owner_; } | ||
| void bufferImplUseOnlySetLength(uint64_t length) { length_ = length; } | ||
| }; | ||
|
|
||
| /** | ||
| * Holds an in-progress addition to a buffer, holding only a single slice. | ||
| * | ||
| * @note For performance reasons, this class is passed by value to | ||
| * avoid an extra allocation, so it cannot have any virtual methods. | ||
| */ | ||
| class ReservationSingleSlice final { | ||
| public: | ||
| ReservationSingleSlice(ReservationSingleSlice&&) = default; | ||
| ~ReservationSingleSlice() = default; | ||
|
|
||
| /** | ||
| * @return the slice in the Reservation. | ||
| */ | ||
| RawSlice slice() const { return slice_; } | ||
|
|
||
| /** | ||
| * @return the total length of the Reservation. | ||
| */ | ||
| uint64_t length() const { return slice_.len_; } | ||
|
|
||
| /** | ||
| * Commits some or all of the data in the reservation. | ||
| * @param length supplies the number of bytes to commit. This must be | ||
| * less than or equal to the size of the `Reservation`. | ||
| * | ||
| * @note No other methods should be called on the object after `commit()` is called. | ||
| */ | ||
| void commit(uint64_t length) { | ||
| ENVOY_BUG(length <= slice_.len_, "commit() length must be <= size of the Reservation"); | ||
| ASSERT(length == 0 || slice_.mem_ != nullptr, | ||
| "Reservation.commit() called on empty Reservation; possible double-commit()."); | ||
| buffer_.commit(length, absl::MakeSpan(&slice_, 1), std::move(slice_owner_)); | ||
| slice_ = {nullptr, 0}; | ||
| ASSERT(slice_owner_ == nullptr); | ||
| } | ||
|
|
||
| private: | ||
| ReservationSingleSlice(Instance& buffer) : buffer_(buffer) {} | ||
|
|
||
| // The buffer that created this `Reservation`. | ||
| Instance& buffer_; | ||
|
|
||
| // The RawSlice in the reservation, usable by anything needing the raw pointer | ||
| // and length to read into. | ||
| RawSlice slice_{}; | ||
|
|
||
| // An owner that can be set by the creator of the `ReservationSingleSlice` to free the slice upon | ||
| // destruction. | ||
| ReservationSlicesOwnerPtr slice_owner_; | ||
|
|
||
| public: | ||
| // The following are for use only by implementations of Buffer. Because c++ | ||
| // doesn't allow inheritance of friendship, these are just trying to make | ||
| // misuse easy to spot in a code review. | ||
| static ReservationSingleSlice bufferImplUseOnlyConstruct(Instance& buffer) { | ||
| return ReservationSingleSlice(buffer); | ||
| } | ||
| RawSlice& bufferImplUseOnlySlice() { return slice_; } | ||
| ReservationSlicesOwnerPtr& bufferImplUseOnlySliceOwner() { return slice_owner_; } | ||
| }; | ||
|
|
||
| } // namespace Buffer | ||
| } // namespace Envoy | ||
Uh oh!
There was an error while loading. Please reload this page.