-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: Inline buffer for captured replies #7648
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
base: main
Are you sure you want to change the base?
Changes from all 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 |
|---|---|---|
|
|
@@ -14,7 +14,7 @@ namespace cmn { | |
|
|
||
| class BackedArguments { | ||
| constexpr static size_t kLenCap = 5; | ||
| constexpr static size_t kStorageCap = 88; | ||
| constexpr static size_t kStorageCap = 128; | ||
|
|
||
| constexpr static size_t kShrinkFloor = 64 << 10; // 64 KiB | ||
|
|
||
|
|
@@ -130,15 +130,19 @@ class BackedArguments { | |
| storage_.resize(last_offs); | ||
| } | ||
|
|
||
| // Inlined buffer where the command arguments are stored. | ||
| // Can be used as a local buffer to store captured replies | ||
| std::span<char> GetInlineBuffer() { | ||
| return {storage_.begin(), std::max(kStorageCap, storage_.size())}; | ||
|
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. |
||
| } | ||
|
Comment on lines
+133
to
+137
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. 3. Inline buffer overwrites args BackedArguments::GetInlineBuffer exposes a span starting at storage_.begin(), but storage_ contains the serialized command argument bytes; CapturingReplyBuilder::SendBulkString writes reply bytes into this span and stores a string_view (BulkStringRef) into the payload. This can overwrite command arguments (and any string_views referencing them, e.g. transaction/journaling), and if the reply string_view aliases the same buffer region the memcpy can have overlap UB. Agent Prompt
|
||
|
|
||
| protected: | ||
| absl::InlinedVector<uint32_t, kLenCap> offsets_; | ||
|
|
||
| // The capacity is chosen so that we allocate a fully utilized (128 bytes) block. | ||
| // See CommandContext for storage cap size limits | ||
| absl::InlinedVector<char, kStorageCap> storage_; | ||
| }; | ||
|
|
||
| static_assert(sizeof(BackedArguments) == 128); | ||
|
|
||
| template <typename I> void BackedArguments::Assign(I begin, I end, size_t len) { | ||
| offsets_.resize(len); | ||
| size_t total_size = 0; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -750,9 +750,6 @@ Connection::Connection(Protocol protocol, util::HttpListenerBase* http_listener, | |
| ssl_ctx_(ctx), | ||
| service_(service), | ||
| flags_(0) { | ||
| constexpr size_t kReqSz = sizeof(ParsedCommand); | ||
| static_assert(kReqSz <= 256); | ||
|
|
||
| // TODO: to move parser initialization to where we initialize the reply builder. | ||
| switch (protocol) { | ||
| case Protocol::REDIS: | ||
|
|
@@ -2980,7 +2977,7 @@ void Connection::EnqueueParsedCommand(ParsedCommand* cmd) { | |
| } | ||
| parsed_tail_ = cmd; | ||
|
|
||
| size_t used_mem = cmd->EnqueuedBytes(); | ||
| size_t used_mem = cmd->UsedMemory(); | ||
| parsed_cmd_q_len_++; | ||
| dispatch_waiting_count_++; // the newly appended tail command is not yet dispatched | ||
| parsed_cmd_q_bytes_ += used_mem; | ||
|
|
@@ -3013,7 +3010,7 @@ void Connection::ReleasePipelinedCommand(ParsedCommand* cmd) { | |
| } | ||
|
|
||
| void Connection::ReleaseParsedCommand(ParsedCommand* cmd) { | ||
| size_t used_mem = cmd->EnqueuedBytes(); | ||
| size_t used_mem = cmd->UsedMemory(); | ||
| auto& conn_stats = tl_facade_stats->conn_stats; | ||
|
|
||
| DCHECK_GT(parsed_cmd_q_len_, 0u); | ||
|
|
@@ -3042,6 +3039,16 @@ void Connection::ReleaseParsedCommand(ParsedCommand* cmd) { | |
| } | ||
| } | ||
|
|
||
| void Connection::AdjustParsedCmdBytes(ssize_t delta) { | ||
| if (parsed_cmd_q_bytes_ == 0) | ||
| return; // command dispatched synchronously, not in pipeline queue | ||
| auto& conn_stats = tl_facade_stats->conn_stats; | ||
| DCHECK_GE(static_cast<ssize_t>(parsed_cmd_q_bytes_) + delta, 0); | ||
| DCHECK_GE(static_cast<ssize_t>(conn_stats.pipeline_queue_bytes) + delta, 0); | ||
| parsed_cmd_q_bytes_ += delta; | ||
|
dranikpg marked this conversation as resolved.
|
||
| conn_stats.pipeline_queue_bytes += delta; | ||
| } | ||
|
Comment on lines
+3042
to
+3050
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. Queue bytes delta wraps Connection::AdjustParsedCmdBytes applies a signed delta to size_t counters via "+=", so negative deltas will convert/wrap and corrupt pipeline queue byte accounting. The only caller also computes the delta as (after - before) using size_t, which underflows when memory shrinks and passes the wrong value downstream. Agent Prompt
|
||
|
|
||
| void Connection::DestroyParsedQueue() { | ||
| ConnectionMemoryTracker memory_tracker(this); | ||
| while (parsed_head_ != nullptr) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -88,7 +88,7 @@ ABSL_FLAG(bool, multi_exec_squash, true, | |
|
|
||
| ABSL_FLAG(bool, lua_resp2_legacy_float, false, | ||
| "Return rounded down integers instead of floats for lua scripts with RESP2"); | ||
| ABSL_FLAG(uint32_t, multi_eval_squash_buffer, 4096, "Max buffer for squashed commands per script"); | ||
| ABSL_FLAG(uint32_t, multi_eval_squash_buffer, 8096, "Max buffer for squashed commands per script"); | ||
|
|
||
|
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. |
||
| ABSL_DECLARE_FLAG(bool, primary_port_http_enabled); | ||
| ABSL_FLAG(size_t, listpack_max_field_len, 64, | ||
|
|
@@ -883,8 +883,15 @@ void StoreInMultiBlock(ConnectionContext* dfly_cntx, const CommandId* cid, | |
| auto& exec_info = dfly_cntx->conn_state.exec_info; | ||
| const size_t old_size = exec_info.GetStoredCmdBytes(); | ||
|
|
||
| // Moves arguments from parsed_cmd to body. | ||
| // SwapArgs inside StoredCmd moves heap storage out of parsed_cmd, | ||
| // so we must adjust the pipeline queue accounting for the delta. | ||
| size_t before = parsed_cmd->UsedMemory(); | ||
| exec_info.body.emplace_back(cid, parsed_cmd, tail_index); | ||
| size_t after = parsed_cmd->UsedMemory(); | ||
| if (before != after) { | ||
| dfly_cntx->conn()->AdjustParsedCmdBytes(ssize_t(after) - ssize_t(before)); | ||
| } | ||
|
|
||
| exec_info.stored_cmd_bytes += exec_info.body.back().UsedMemory(); | ||
| exec_info.is_write |= cid->IsJournaled(); | ||
| ServerState::tlocal()->stats.stored_cmd_bytes += exec_info.GetStoredCmdBytes() - old_size; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -219,6 +219,13 @@ OpStatus MultiCommandSquasher::SquashedHopCb(EngineShard* es, RespVersion resp_v | |
| auto* ctx = &local_cntx; | ||
| crb.SetReplyMode(dispatched.reply_mode); | ||
|
|
||
| // Allow captured replies to be stored in argument storage. | ||
| // Some commands might include arguments in replies, so we have a limited set | ||
| if (dispatched.cmd_cntx && dispatched.cid->SupportsAsync()) | ||
| crb.ProvideInlineBuffer(dispatched.cmd_cntx->GetInlineBuffer()); | ||
| else | ||
| crb.ProvideInlineBuffer({}); // reset buffer | ||
|
|
||
|
Comment on lines
+223
to
+228
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. or add some flag to CommandId instead? |
||
| // With tiered storage enabled, it makes sense to dispatch async commands concurrently | ||
| // to allow concurrent disk operations. Tiered futures are only blocked on during replies | ||
| bool do_async = es->tiered_storage() && !IsAtomic() && opts_.pipeline_mode && | ||
|
|
||
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.
we got this by removing two fields + growing to hit mi_good_size of 320