Skip to content
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

buffer: improve read reservations to efficiently handle multiple slices #14054

Merged
merged 59 commits into from
Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
9c5199f
buffer: improve read reservations to efficiently handle multiple slices
ggreenway Oct 28, 2020
246f167
Merge remote-tracking branch 'upstream/master' into multi-slice-read
ggreenway Nov 30, 2020
ae815f1
honor high watermark
ggreenway Dec 2, 2020
6adeeab
Merge remote-tracking branch 'upstream/master' into multi-slice-read
ggreenway Dec 4, 2020
6af8023
fix test expectation
ggreenway Dec 4, 2020
c8c9cce
fix incorrect merge conflict resolution
ggreenway Dec 4, 2020
a0b68b7
remove commented out code
ggreenway Dec 4, 2020
ff13426
Remove old reserve/commit API; migrate all uses to new API
ggreenway Dec 7, 2020
c0661eb
don't increase size of preferred length in watermark code trying to
ggreenway Dec 8, 2020
0416701
Merge remote-tracking branch 'upstream/master' into multi-slice-read
ggreenway Dec 10, 2020
6e9f1aa
api comments; delete old code
ggreenway Dec 10, 2020
eb3fe0e
readability cleanup
ggreenway Dec 11, 2020
aff7e53
change interface to have a different Reservation type for single-slic…
ggreenway Dec 14, 2020
508d4dc
Merge remote-tracking branch 'upstream/master' into multi-slice-read
ggreenway Dec 15, 2020
abcbe9e
build fixes
ggreenway Dec 16, 2020
be6a9a3
fix watermark buffer reservation logic
ggreenway Dec 16, 2020
11a4362
Merge remote-tracking branch 'upstream/master' into multi-slice-read
ggreenway Dec 21, 2020
532edbb
remove accidental addition to setup_clang.sh
ggreenway Dec 21, 2020
53fbc33
fixes
ggreenway Dec 21, 2020
4bef236
Merge remote-tracking branch 'upstream/master' into multi-slice-read
ggreenway Jan 14, 2021
efd8057
release note
ggreenway Jan 14, 2021
75278b6
fix merge conflict
ggreenway Jan 14, 2021
e56585c
clang_tidy
ggreenway Jan 14, 2021
fc7b592
fix mac build
ggreenway Jan 14, 2021
45e7353
Merge remote-tracking branch 'upstream/master' into multi-slice-read
ggreenway Jan 14, 2021
5885132
one more ASSERT
ggreenway Jan 19, 2021
675cd59
Merge remote-tracking branch 'upstream/main' into multi-slice-read
ggreenway Jan 19, 2021
a1e6c3e
remove cast to fix compilation; the types matched exactly without the…
ggreenway Jan 21, 2021
0e80aca
fix tls throughput benchmark correctness
ggreenway Jan 21, 2021
a5deffa
undo change to connection impl test buffer size
ggreenway Jan 21, 2021
dc4d618
ensure reservation is the desired size
ggreenway Jan 21, 2021
7bd544a
comment in test regarding when slices are added, and additional
ggreenway Jan 21, 2021
4e0331d
add expectation on reservation length
ggreenway Jan 21, 2021
b4c8e46
rework API to not take a param for reservation size for reads
ggreenway Jan 21, 2021
112e3bd
reset length_ on commit()
ggreenway Jan 21, 2021
6195334
check for double-commit()
ggreenway Jan 21, 2021
3475f1d
remove assert that isn't true in fuzz corpus
ggreenway Jan 21, 2021
f8d2a72
ensure we don't overflow the inline-vector in Reservation
ggreenway Jan 21, 2021
01e6ebd
Merge remote-tracking branch 'upstream/main' into multi-slice-read
ggreenway Jan 21, 2021
1e20eeb
fix buffer_fuzz
ggreenway Jan 22, 2021
d3559f8
reserveWithMaxLength
ggreenway Jan 22, 2021
54d0162
add test for ENVOY_BUG
ggreenway Jan 22, 2021
c837704
make IoHandle::read() max_length optional
ggreenway Jan 22, 2021
530e045
Add test for freelist; fixes to make it work like a stack
ggreenway Jan 22, 2021
873f6be
Merge remote-tracking branch 'upstream/main' into multi-slice-read
ggreenway Jan 25, 2021
0c92529
fix ordering of release notes (auto-merge got wrong order)
ggreenway Jan 25, 2021
2cfd939
fix spelling
ggreenway Jan 25, 2021
0c26ceb
slightly improve speed_test
ggreenway Jan 26, 2021
07a9314
only allocate one block for impl to track ownership in reservation,
ggreenway Jan 26, 2021
6bc1fc3
fix build
ggreenway Jan 26, 2021
9f3a717
optimize: only lookup thread_local once per reservation
ggreenway Jan 26, 2021
ef66dc4
fix test
ggreenway Jan 26, 2021
47d7cfd
clang-tidy
ggreenway Jan 26, 2021
ea5e975
minor cleanup of `len_` handling
ggreenway Jan 26, 2021
4a92b7d
more expectations in ReserveZeroCommit
ggreenway Jan 27, 2021
97bd65b
Merge remote-tracking branch 'upstream/main' into multi-slice-read
ggreenway Jan 27, 2021
7b94c6f
Remove a test that no longer makes sense.
ggreenway Jan 27, 2021
180ab4b
Merge remote-tracking branch 'upstream/main' into multi-slice-read
ggreenway Feb 1, 2021
875e8d3
Merge remote-tracking branch 'upstream/main' into multi-slice-read
ggreenway Feb 1, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Minor Behavior Changes
*Changes that may cause incompatibilities for some users, but should not for most*

* oauth filter: added the optional parameter :ref:`auth_scopes <envoy_v3_api_field_extensions.filters.http.oauth2.v3alpha.OAuth2Config.auth_scopes>` with default value of 'user' if not provided. Enables this value to be overridden in the Authorization request to the OAuth provider.
* perf: allow reading more bytes per operation from raw sockets to improve performance.
* tcp: setting NODELAY in the base connection class. This should have no effect for TCP or HTTP proxying, but may improve throughput in other areas. This behavior can be temporarily reverted by setting `envoy.reloadable_features.always_nodelay` to false.
* upstream: host weight changes now cause a full load balancer rebuild as opposed to happening
atomically inline. This change has been made to support load balancer pre-computation of data
Expand Down
1 change: 1 addition & 0 deletions include/envoy/buffer/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ envoy_cc_library(
],
deps = [
"//include/envoy/api:os_sys_calls_interface",
"//source/common/common:assert_lib",
"//source/common/common:byte_order_lib",
"//source/common/common:utility_lib",
],
Expand Down
190 changes: 174 additions & 16 deletions include/envoy/buffer/buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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`.
ggreenway marked this conversation as resolved.
Show resolved Hide resolved
*/
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.
Expand Down Expand Up @@ -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>;
Expand Down Expand Up @@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

The 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).
The buffer_ and length_ are still set to their original values after the move. I guess that's probably fine.

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");
Copy link
Contributor

Choose a reason for hiding this comment

The 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).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the situation here that it's safe to have length > length_? Trying to understand why one condition is ENVOY_BUG and the other is ASSERT.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both conditions are the same, length must be less than or equal to length_, the change just moved it from ASSERT to ENVOY_BUG.
(Maybe the semantics of ENVOY_BUG are confusing? ENVOY_BUG(condition, msg) is an error if condition was false, just like ASSERT(condition)).

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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, commit() will keep the data structure in a sane/safe state, so RELEASE_ASSERT seemed over-zealous.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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 and ENVOY_BUG in this situation.

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
6 changes: 4 additions & 2 deletions include/envoy/network/io_handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,13 @@ class IoHandle {
/**
* Read from a io handle directly into buffer.
* @param buffer supplies the buffer to read into.
* @param max_length supplies the maximum length to read.
* @param max_length supplies the maximum length to read. A value of absl::nullopt means to read
* as much data as possible, within the constraints of available buffer size.
* @return a IoCallUint64Result with err_ = nullptr and rc_ = the number of bytes
* read if successful, or err_ = some IoError for failure. If call failed, rc_ shouldn't be used.
*/
virtual Api::IoCallUint64Result read(Buffer::Instance& buffer, uint64_t max_length) PURE;
virtual Api::IoCallUint64Result read(Buffer::Instance& buffer,
absl::optional<uint64_t> max_length) PURE;

/**
* Write the data in slices out.
Expand Down
Loading