-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: Add ReplyScope to SquashPipeline #7630
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 3 commits
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 |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
| #include "absl/types/span.h" | ||
| #include "base/cycle_clock.h" | ||
| #include "base/logging.h" | ||
| #include "common/borrowed_string.h" | ||
| #include "facade/error.h" | ||
| #include "util/fibers/proactor_base.h" | ||
|
|
||
|
|
@@ -344,7 +345,7 @@ void RedisReplyBuilderBase::SendBulkString(std::string_view str) { | |
| WritePieces(kCRLF); | ||
| } | ||
|
|
||
| void RedisReplyBuilderBase::SendBulkStringBorrowed(cmn::BorrowedString bs) { | ||
| void RedisReplyBuilderBase::SendBulkStringBorrowed(const cmn::BorrowedString& bs) { | ||
| ReplyScope scope(this); | ||
| tl_facade_stats->reply_stats.borrowed_string_sent_cnt++; | ||
|
|
||
|
|
@@ -359,11 +360,10 @@ void RedisReplyBuilderBase::SendBulkStringBorrowed(cmn::BorrowedString bs) { | |
| WriteRef(bs.view()); | ||
| } | ||
| WritePieces(kCRLF); | ||
| } | ||
|
|
||
| // Drain all iovecs (including any WriteRef into bs.view()) before this | ||
| // function returns. ~BorrowedString releases the pin afterward — by then | ||
| // the source bytes are already in the kernel. | ||
| Flush(); | ||
|
Comment on lines
-363
to
-366
Contributor
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. This was totally misleading and tried to make a normal use-case look like the exception. First, all Send functions assume that the lifetime of the passed reference (or reference object like string_view) does not extend beyond the function. This is why there is a
Contributor
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 haven't had a deep though when reviewing the PRs, but now we temporarily end up with two functions (const lvalue, rvalue). I think there would have been more elegant solutions
Collaborator
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 do not understand. Suppose when ReplyScope finishes, it will reference possible invalid string. FYI, I assumed that flushing a BorrowedString does not significantly impact performance compared to sending strings themselves.
Contributor
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. Because you are in full control of the lifetime. I've added a comment at the top that the reply builder accepts only reference types and assumes each of them goes out if scope immediately - it never says "references can be kept" if you never tell it
Contributor
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. If you want to break that assumption, use ReplyScopes
Contributor
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.
Because there is a ReplyScope inside the function and when ut goes out of scope it will either flush or copy the data
Collaborator
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. But if there is another replyscope above this function? The scope will persist, right?
Collaborator
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 think I can only exend the scoping, I can not disable it.
Collaborator
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. Maybe replacing |
||
| void RedisReplyBuilderBase::SendBulkStringBorrowed(cmn::BorrowedString&& bs) { | ||
|
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.
Severity: high 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage. |
||
| SendBulkStringBorrowed(static_cast<const cmn::BorrowedString&>(bs)); | ||
| } | ||
|
|
||
| void RedisReplyBuilderBase::SendLong(long val) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,12 +14,19 @@ | |
| #include "facade/facade_types.h" | ||
| #include "io/io.h" | ||
|
|
||
| namespace cmn { | ||
| class BorrowedString; | ||
| } // namespace cmn | ||
|
|
||
| namespace facade { | ||
|
|
||
| enum class RespVersion { kResp2, kResp3 }; | ||
|
|
||
| // Base class for all reply builders. Offer a simple high level interface for controlling output | ||
| // modes and sending basic response types. | ||
| // By default, pointer validity for reference types (string_view, const BorrowedString&) is only | ||
| // assumed for the Send...() function call. Use ReplyScope if the lifetime is longer to avoid copies | ||
| // and enable zero-copy vectorized IO. | ||
| class SinkReplyBuilder { | ||
| struct GuardBase { | ||
| bool prev; | ||
|
|
@@ -59,6 +66,17 @@ class SinkReplyBuilder { | |
| ~ReplyScope(); | ||
| }; | ||
|
|
||
| // Temporarily pauses an active ReplyScope. While paused, Send calls copy data as usual | ||
| // (no zero-copy refs). Restores the previous scoped_ state on destruction. | ||
| struct ScopePause : GuardBase { | ||
| explicit ScopePause(SinkReplyBuilder* rb) : GuardBase{std::exchange(rb->scoped_, false), rb} { | ||
| } | ||
|
|
||
| ~ScopePause() { | ||
| rb->scoped_ = prev; | ||
| } | ||
| }; | ||
|
|
||
| // Aggregator reduces the number of raw send calls by copying data in an intermediate buffer. | ||
| // Prefer ReplyScope if possible to additionally reduce the number of copies. | ||
| struct ReplyAggregator : GuardBase { | ||
|
|
@@ -197,15 +215,11 @@ class RedisReplyBuilderBase : public SinkReplyBuilder { | |
| void SendSimpleString(std::string_view str) override; | ||
| virtual void SendBulkString(std::string_view str); // RESP: Blob String | ||
|
|
||
| // Like SendBulkString, but takes ownership of a move-only cmn::BorrowedString | ||
| // whose bytes are borrowed from a stable in-memory source (e.g. a CompactObj | ||
| // LargeString under the read-only invariant). The default impl handles both | ||
| // raw (iovec ref) and packed (chunked decode into scratch) encodings and | ||
| // Flushes before returning, so ~BorrowedString releases the pin only after | ||
| // the source bytes are in the kernel. CapturingReplyBuilder overrides this | ||
| // to move `bs` into the captured payload, extending the pin's lifetime | ||
| // through replay. | ||
| virtual void SendBulkStringBorrowed(cmn::BorrowedString bs); | ||
| void SendBulkStringBorrowed(const cmn::BorrowedString& bs); | ||
|
|
||
| // The interface exposes only the rvalue function to allow squashing to "steal" the value, | ||
| // the real builder implmenetation forwards it to the constref version | ||
|
romange marked this conversation as resolved.
Outdated
|
||
| virtual void SendBulkStringBorrowed(cmn::BorrowedString&& bs); | ||
|
Collaborator
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. @dranikpg why do we need in this case do you agree that this function can not run under any scope? |
||
|
|
||
| void SendLong(long val) override; | ||
| virtual void SendDouble(double val); // RESP: Number | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,7 +56,7 @@ void CapturingReplyBuilder::SendBulkString(std::string_view str) { | |
|
|
||
| // Capture the borrow into the payload, extending the pin's lifetime until | ||
| // replay moves it into the real sink where it is parked across the writev. | ||
| void CapturingReplyBuilder::SendBulkStringBorrowed(cmn::BorrowedString bs) { | ||
| void CapturingReplyBuilder::SendBulkStringBorrowed(cmn::BorrowedString&& bs) { | ||
|
Collaborator
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. string_family.cc is the only user of this function. I think this signature should be removed |
||
| SKIP_LESS(ReplyMode::FULL); | ||
| Capture(std::move(bs)); | ||
| } | ||
|
|
@@ -130,8 +130,8 @@ struct CaptureVisitor { | |
| static_cast<RedisReplyBuilder*>(rb)->SendBulkString(bs); | ||
| } | ||
|
|
||
| void operator()(cmn::BorrowedString bs) { | ||
| static_cast<RedisReplyBuilder*>(rb)->SendBulkStringBorrowed(std::move(bs)); | ||
| void operator()(const cmn::BorrowedString& bs) { | ||
| static_cast<RedisReplyBuilder*>(rb)->SendBulkStringBorrowed(bs); | ||
| } | ||
|
|
||
| void operator()(payload::Null) { | ||
|
|
@@ -166,8 +166,12 @@ void CapturingReplyBuilder::Apply(Payload&& pl, SinkReplyBuilder* rb) { | |
| return; | ||
| } | ||
|
|
||
| Apply(static_cast<const Payload&>(pl), rb); | ||
| } | ||
|
|
||
| void CapturingReplyBuilder::Apply(const Payload& pl, SinkReplyBuilder* rb) { | ||
|
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.
Severity: medium 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage. |
||
| CaptureVisitor cv{rb}; | ||
| visit(cv, std::move(pl)); | ||
| visit(cv, pl); | ||
| } | ||
|
Comment on lines
+169
to
175
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. 1. Const replay still consumes Reply replay under an outer ReplyScope can queue iovecs that outlive the underlying bytes because collection replay in CapturingReplyBuilder::Apply(const Payload&) still moves elements out of CollectionPayload::arr, and RedisReplyBuilderBase::SendBulkStringBorrowed(BorrowedString&&) no longer takes ownership or extends the pin lifetime (it forwards as const&). As a result, moved-from/temporary std::string buffers or BorrowedString pins may be destroyed/unpinned before Connection::SquashPipeline’s outer scope finally flushes, leaving iovecs pointing at freed/unpinned memory. Agent Prompt
|
||
|
|
||
| void CapturingReplyBuilder::SetReplyMode(ReplyMode mode) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,7 +31,8 @@ class CapturingReplyBuilder : public RedisReplyBuilder { | |
| void SendDouble(double val) override; | ||
| void SendSimpleString(std::string_view str) override; | ||
| void SendBulkString(std::string_view str) override; | ||
| void SendBulkStringBorrowed(cmn::BorrowedString bs) override; | ||
|
|
||
| void SendBulkStringBorrowed(cmn::BorrowedString&& bs) override; | ||
|
|
||
| void StartCollection(unsigned len, CollectionType type) override; | ||
| void SendNullArray() override; | ||
|
|
@@ -56,6 +57,10 @@ class CapturingReplyBuilder : public RedisReplyBuilder { | |
| // Send payload to builder. | ||
| static void Apply(Payload&& pl, SinkReplyBuilder* builder); | ||
|
Collaborator
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. There are too many semantics now. Do we still need "r-value" interface? |
||
|
|
||
| // Send payload to builder without consuming it. String refs into the payload remain valid, | ||
| // making this safe to use under a ReplyScope when the payload outlives the scope. | ||
| static void Apply(const Payload& pl, SinkReplyBuilder* builder); | ||
|
|
||
| // If an error is stored inside payload, get a reference to it. | ||
| static std::optional<ErrorRef> TryExtractError(const Payload& pl); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of
ScopePauseyou can add ReplyScope::Pause() / UnPause().