-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Async POC #6317
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?
Async POC #6317
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| +0 −33 | .agent/README.md | |
| +0 −460 | .agent/cpp_guidelines.md | |
| +0 −140 | .github/copilot-instructions.md | |
| +49 −0 | AGENTS.md | |
| +30 −4 | CMakeLists.txt | |
| +47 −13 | blaze.sh | |
| +199 −53 | cmake/internal.cmake | |
| +3 −0 | cmake/third_party.cmake | |
| +18 −23 | util/fibers/detail/wait_queue.cc | |
| +27 −15 | util/fibers/detail/wait_queue.h | |
| +46 −4 | util/fibers/fibers_test.cc | |
| +19 −0 | util/fibers/synchronization.cc | |
| +60 −44 | util/fibers/synchronization.h | |
| +1 −1 | util/tls/CMakeLists.txt | |
| +52 −0 | util/tls/iovec_utils.cc | |
| +28 −0 | util/tls/iovec_utils.h | |
| +169 −4 | util/tls/tls_socket.cc | |
| +623 −35 | util/tls/tls_socket_test.cc |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -500,7 +500,8 @@ void Connection::AsyncOperations::operator()(ParsedCommand& cmd) { | |||||||||||
| DVLOG(2) << "Dispatching pipeline: " << cmd.Front(); | ||||||||||||
|
|
||||||||||||
| ++self->local_stats_.cmds; | ||||||||||||
| self->service_->DispatchCommand(ParsedArgs{cmd}, &cmd); | ||||||||||||
| self->service_->DispatchCommand(ParsedArgs{cmd}, &cmd, | ||||||||||||
| ServiceInterface::AsyncPreference::ONLY_SYNC); | ||||||||||||
|
|
||||||||||||
| self->last_interaction_ = time(nullptr); | ||||||||||||
| self->skip_next_squashing_ = false; | ||||||||||||
|
|
@@ -577,7 +578,7 @@ Connection::Connection(Protocol protocol, util::HttpListenerBase* http_listener, | |||||||||||
| static atomic_uint32_t next_id{1}; | ||||||||||||
|
|
||||||||||||
| constexpr size_t kReqSz = sizeof(ParsedCommand); | ||||||||||||
| static_assert(kReqSz <= 256); | ||||||||||||
| // static_assert(kReqSz <= 256); | ||||||||||||
|
|
||||||||||||
| // TODO: to move parser initialization to where we initialize the reply builder. | ||||||||||||
| switch (protocol) { | ||||||||||||
|
|
@@ -1181,7 +1182,8 @@ Connection::ParserStatus Connection::ParseRedis(unsigned max_busy_cycles) { | |||||||||||
|
|
||||||||||||
| auto dispatch_sync = [this] { | ||||||||||||
| FillBackedArgs(tmp_parse_args_, parsed_cmd_); | ||||||||||||
| service_->DispatchCommand(ParsedArgs{*parsed_cmd_}, parsed_cmd_); | ||||||||||||
| service_->DispatchCommand(ParsedArgs{*parsed_cmd_}, parsed_cmd_, | ||||||||||||
| ServiceInterface::AsyncPreference::ONLY_SYNC); | ||||||||||||
| }; | ||||||||||||
|
|
||||||||||||
| auto dispatch_async = [this]() -> MessageHandle { return {FromArgs(tmp_parse_args_)}; }; | ||||||||||||
|
|
@@ -2032,7 +2034,7 @@ bool Connection::ParseMCBatch() { | |||||||||||
| memcache_parser_->Reset(); | ||||||||||||
| auto client_error = [](string_view msg) { return absl::StrCat("CLIENT_ERROR ", msg); }; | ||||||||||||
|
|
||||||||||||
| parsed_tail_->SetDeferredReply(); | ||||||||||||
| parsed_tail_->SetDeferredReply(true); | ||||||||||||
| switch (result) { | ||||||||||||
| case MemcacheParser::UNKNOWN_CMD: | ||||||||||||
| parsed_tail_->SendSimpleString("ERROR"); | ||||||||||||
|
|
@@ -2054,49 +2056,29 @@ bool Connection::ParseMCBatch() { | |||||||||||
|
|
||||||||||||
| bool Connection::ExecuteMCBatch() { | ||||||||||||
| // Execute sequentially all parsed commands. | ||||||||||||
| while (parsed_to_execute_) { | ||||||||||||
| auto* cmd = parsed_to_execute_; | ||||||||||||
| bool is_head = (cmd == parsed_head_); | ||||||||||||
|
|
||||||||||||
| bool has_replied = false; | ||||||||||||
|
|
||||||||||||
| if (is_head) { | ||||||||||||
| // Protocol parse errors create commands with already cached replies. | ||||||||||||
| // Try sending payload now in case it's already set. | ||||||||||||
| has_replied = cmd->SendPayload(); | ||||||||||||
| DVLOG(2) << "Maybe replying head: " << has_replied; | ||||||||||||
| } else { | ||||||||||||
| // We are not the head command, so we can not reply directly. | ||||||||||||
| if (cmd->IsDeferredReply()) { | ||||||||||||
| has_replied = true; // The error reply is filled by the parser. | ||||||||||||
| } else { | ||||||||||||
| cmd->SetDeferredReply(); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if (!has_replied) { | ||||||||||||
| service_->DispatchMC(cmd); | ||||||||||||
|
|
||||||||||||
| // If the reply was not deferred, then DispatchMC has surely replied. | ||||||||||||
| has_replied = !cmd->IsDeferredReply(); | ||||||||||||
| DVLOG(2) << "Executed command, has_replied: " << has_replied; | ||||||||||||
| } | ||||||||||||
| parsed_to_execute_ = cmd->next; | ||||||||||||
|
|
||||||||||||
| // Only if commands have deferred replies we need to keep them in the parsed queue | ||||||||||||
| // until they complete. | ||||||||||||
| if (is_head && has_replied) { | ||||||||||||
| // This is head and it replied to the client socket, so we can remove it from the parsed | ||||||||||||
| // queue right away. | ||||||||||||
| // This optimization makes the ReplyMCBatch call a no-op unless we actually run asynchronous | ||||||||||||
| // commands with deferred replies. | ||||||||||||
| parsed_head_ = parsed_to_execute_; | ||||||||||||
| for (auto*& cmd = parsed_to_execute_; cmd; cmd = cmd->next) { | ||||||||||||
| if (cmd->IsReady()) // in case parser error was set immediately | ||||||||||||
| continue; | ||||||||||||
|
|
||||||||||||
| // If there are pending async commands, we require async | ||||||||||||
| bool is_head = parsed_head_ == parsed_to_execute_; | ||||||||||||
| auto async_pref = is_head ? ServiceInterface::AsyncPreference::PREFER_ASYNC | ||||||||||||
| : ServiceInterface::AsyncPreference::REQUIRE_ASYNC; | ||||||||||||
|
|
||||||||||||
| DispatchResult result = service_->DispatchMC(cmd, async_pref); | ||||||||||||
| VLOG(0) << int(result) << " -> " << int(async_pref); | ||||||||||||
|
||||||||||||
| VLOG(0) << int(result) << " -> " << int(async_pref); | |
| DVLOG(1) << int(result) << " -> " << int(async_pref); |
Copilot
AI
Jan 8, 2026
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.
Memory accounting for dispatch queue bytes is disabled. This will cause incorrect memory statistics and potential memory leak tracking issues.
| // stats_->dispatch_queue_bytes -= cmd->UsedMemory(); | |
| parsed_head_ = cmd->next; | |
| stats_->dispatch_queue_bytes -= cmd->UsedMemory(); | |
| parsed_head_ = cmd->next; | |
| delete cmd; |
Copilot
AI
Jan 8, 2026
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.
Debug VLOG statement at level 0 should be removed or changed to a higher level before production use.
| VLOG(0) << "Waiter triggered"; | |
| DVLOG(2) << "Waiter triggered"; |
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.
Please write explanation here what exactly we do here. It's gentle, so I prefer we have comments than going through low-level code to understand
Copilot
AI
Jan 8, 2026
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.
Debug VLOG statement at level 0 should be removed or changed to a higher level before production use.
| VLOG(0) << "Rolling in with head_ready: " << head_ready; | |
| VLOG(1) << "Rolling in with head_ready: " << head_ready; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| #include "facade/parsed_command.h" | ||
|
|
||
| #include "base/logging.h" | ||
| #include "core/overloaded.h" | ||
| #include "facade/conn_context.h" | ||
| #include "facade/dragonfly_connection.h" | ||
| #include "facade/reply_builder.h" | ||
|
|
@@ -47,11 +48,9 @@ string MCRender::RenderDeleted() const { | |
| } | ||
|
|
||
| void ParsedCommand::ResetForReuse() { | ||
| allow_async_execution_ = false; | ||
| is_deferred_reply_ = false; | ||
| reply_payload_ = std::monostate{}; | ||
| reply_ = std::monostate{}; | ||
|
|
||
| state_.store(0, std::memory_order_relaxed); | ||
| offsets_.clear(); | ||
| if (HeapMemory() > 1024) { | ||
| storage_.clear(); // also deallocates the heap. | ||
|
|
@@ -63,8 +62,7 @@ void ParsedCommand::SendError(std::string_view str, std::string_view type) { | |
| if (!is_deferred_reply_) { | ||
| rb_->SendError(str, type); | ||
| } else { | ||
| reply_payload_ = payload::make_error(str, type); | ||
| NotifyReplied(); | ||
| reply_ = payload::make_error(str, type); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -76,10 +74,9 @@ void ParsedCommand::SendError(facade::OpStatus status) { | |
| rb_->SendError(StatusToMsg(status)); | ||
| } else { | ||
| if (status == OpStatus::OK) | ||
| reply_payload_ = payload::SimpleString{"OK"}; | ||
| reply_ = payload::SimpleString{"OK"}; | ||
| else | ||
| reply_payload_ = payload::make_error(StatusToMsg(status)); | ||
| NotifyReplied(); | ||
| reply_ = payload::make_error(StatusToMsg(status)); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -93,75 +90,51 @@ void ParsedCommand::SendSimpleString(std::string_view str) { | |
| if (!is_deferred_reply_) { | ||
| rb_->SendSimpleString(str); | ||
| } else { | ||
| reply_payload_ = payload::make_simple_or_noreply(str); | ||
| NotifyReplied(); | ||
| } | ||
| } | ||
|
|
||
| void ParsedCommand::SendLong(long val) { | ||
| if (is_deferred_reply_) { | ||
| reply_payload_ = long(val); | ||
| NotifyReplied(); | ||
| } else { | ||
| rb_->SendLong(val); | ||
| reply_ = payload::make_simple_or_noreply(str); | ||
| } | ||
| } | ||
|
|
||
| void ParsedCommand::SendNull() { | ||
| if (is_deferred_reply_) { | ||
| reply_payload_ = payload::Null{}; | ||
| NotifyReplied(); | ||
| reply_ = payload::Null{}; | ||
| } else { | ||
| DCHECK(mc_cmd_ == nullptr); // RESP only | ||
| static_cast<RedisReplyBuilder*>(rb_)->SendNull(); | ||
| } | ||
| } | ||
|
|
||
| bool ParsedCommand::SendPayload() { | ||
| if (is_deferred_reply_) { | ||
| CapturingReplyBuilder::Apply(std::move(reply_payload_), rb_); | ||
| reply_payload_ = {}; | ||
| return true; | ||
| } | ||
| return false; | ||
| void ParsedCommand::Resolve(ErrorReply&& error) { | ||
| DCHECK(is_deferred_reply_); | ||
| SendError(error); | ||
| } | ||
|
|
||
| bool ParsedCommand::CheckDoneAndMarkHead() { | ||
| uint8_t state = state_.load(std::memory_order_acquire); | ||
|
|
||
| while ((state & ASYNC_REPLY_DONE) == 0) { | ||
| // If we marked it as head already, return false. | ||
| if (state & HEAD_REPLY) { | ||
| return false; | ||
| } | ||
|
|
||
| // Mark it as head. If succeeded (i.e ASYNC_REPLY_DONE is still not set), return false | ||
| if (state_.compare_exchange_weak(state, state | HEAD_REPLY, std::memory_order_acq_rel)) { | ||
| return false; | ||
| } | ||
| // Otherwise, retry with updated state. | ||
| } | ||
|
|
||
| // ASYNC_REPLY_DONE is set, return true. | ||
| return true; | ||
| bool ParsedCommand::IsReady() const { | ||
| dfly::Overloaded ov{[](const payload::Payload& pl) { return pl.index() > 0; }, | ||
| [](const AsyncTask& task) { return task.blocker->IsCompleted(); }}; | ||
| return visit(ov, reply_); | ||
| } | ||
|
|
||
| void ParsedCommand::NotifyReplied() { | ||
| // A synchronization point. We set ASYNC_REPLY_DONE to mark it's safe now to read the payload. | ||
| uint8_t prev_state = state_.fetch_or(ASYNC_REPLY_DONE, std::memory_order_acq_rel); | ||
|
|
||
| DVLOG(1) << "ParsedCommand::NotifyReplied with state " << unsigned(prev_state); | ||
| bool ParsedCommand::OnCompletion(util::fb2::detail::Waiter* waiter) { | ||
| dfly::Overloaded ov{[](const payload::Payload& pl) { | ||
| DCHECK(pl.index() > 0); | ||
| return true; | ||
| }, | ||
| [waiter](const AsyncTask& task) { | ||
| task.blocker->OnCompletion(waiter); | ||
| return false; | ||
| }}; | ||
| return visit(ov, reply_); | ||
|
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. how the access to
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 never write into reply from the shard thread, its either assigned to the return value of the handler or set to the callback that will reply
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. Ok, I will need to see SET command I guess - how it propagates the Stored reply from the shard thread.
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. it won't propagate from the shard thread, it will work via a replier |
||
| } | ||
|
|
||
| if (prev_state & DELETE_INTENT) { | ||
| delete this; | ||
| return; | ||
| } | ||
| // If it was marked as head already, notify the connection that the head is done. | ||
| if (prev_state & HEAD_REPLY) { | ||
| // TODO: this might crash as we currently do not wait for async commands on connection close. | ||
| DCHECK(conn_cntx_); | ||
| conn_cntx_->conn()->Notify(); | ||
| } | ||
| void ParsedCommand::Reply() { | ||
| dfly::Overloaded ov{ | ||
| [this](payload::Payload&& pl) { CapturingReplyBuilder::Apply(std::move(pl), rb_); }, | ||
| [](AsyncTask&& task) { | ||
| DCHECK(task.blocker->IsCompleted()); | ||
| while (!task.replier.done()) | ||
| task.replier.resume(); | ||
| }}; | ||
| visit(ov, exchange(reply_, monostate{})); | ||
| } | ||
|
|
||
| } // namespace facade | ||
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.
The static assertion checking ParsedCommand size is commented out. This should either be updated to reflect the new size (232) or removed with explanation, as size constraints may be important for memory layout.