github-actions[bot] commented on code in PR #67340:
URL: https://github.com/apache/doris/pull/67340#discussion_r3942493565


##########
be/src/exec/operator/exchange_sink_buffer.cpp:
##########
@@ -346,6 +346,7 @@ Status ExchangeSinkBuffer::_send_rpc(RpcInstance& 
instance_data) {
             }
             // The eos here only indicates that the current exchange sink has 
reached eos.
             // However, the queue still contains data from other exchange 
sinks, so RPCs need to continue being sent.
+            // `_send_rpc` must be the LAST operation in this function, 
because it may reuse the callback!

Review Comment:
   [P1] Keep the active handler alive across reentrant reuse
   
   This call is not actually the last operation in the handler: on a 
synchronous failure from the nested send, lines 351-353 still use this lambda's 
captures. For another queued block on the same channel, `_send_rpc()` reuses 
the same `ExchangeSendCallback` and `addSuccessHandler()` assigns over 
`_suc_fn` while that very target is executing. If HTTP attachment/DNS/client 
setup then returns an error before launching the RPC, execution comes back here 
and calls `_failed` through a lambda whose stored target has already been 
destroyed. Please keep a local copy of the selected handler before invoking it 
(or defer handler replacement until it returns); the broadcast branch has the 
same pattern.



##########
be/src/exec/runtime_filter/runtime_filter_producer.h:
##########
@@ -17,15 +17,78 @@
 
 #pragma once
 
+#include <glog/logging.h>
+
+#include <memory>
 #include <mutex>
 
 #include "exec/pipeline/dependency.h"
 #include "exec/runtime_filter/runtime_filter.h"
 #include "runtime/query_context.h"
-#include "runtime/runtime_profile.h"
+#include "util/brpc_closure.h"
 
 namespace doris {
 #include "common/compile_check_begin.h"
+
+// Callback for sync-size RPCs. Handles errors (disable wrapper + sub 
dependency) in call().
+class SyncSizeCallback : public DummyBrpcCallback<PSendFilterSizeResponse> {
+    ENABLE_FACTORY_CREATOR(SyncSizeCallback);
+
+public:
+    SyncSizeCallback(std::shared_ptr<Dependency> dependency,
+                     std::shared_ptr<RuntimeFilterWrapper> wrapper,
+                     std::weak_ptr<QueryContext> context)
+            : _dependency(std::move(dependency)), _wrapper(wrapper), 
_context(std::move(context)) {}
+
+    void call() override {
+        // On error: disable the wrapper and sub the dependency here, because 
set_synced_size()
+        // will never be called (the merge node won't respond with a sync).
+        // On success: do NOT sub here. The merge node will respond with 
sync_filter_size,
+        // which calls set_synced_size() -> _dependency->sub().
+        if (this->cntl_->Failed()) {
+            LOG(WARNING) << fmt::format("RPC meet failed: {}", 
this->cntl_->ErrorText());
+            if (auto w = _wrapper.lock()) {
+                w->set_state(RuntimeFilterWrapper::State::DISABLED, 
this->cntl_->ErrorText());
+            }
+            if (auto ctx = _context.lock()) {
+                if (!ctx->ignore_runtime_filter_error()) {
+                    ctx->cancel(
+                            Status::NetworkError("RPC meet failed: {}", 
this->cntl_->ErrorText()));
+                }
+            }
+            auto p = 
std::dynamic_pointer_cast<CountedFinishDependency>(_dependency);
+            DORIS_CHECK(p);
+            p->sub();
+            return;
+        }
+
+        Status status = Status::create(this->response_->status());
+        if (!status.ok()) {
+            LOG(WARNING) << "RPC meet error status: " << status;
+            if (auto w = _wrapper.lock()) {
+                w->set_state(RuntimeFilterWrapper::State::DISABLED, 
status.to_string());
+            }
+            if (auto ctx = _context.lock()) {
+                if (!ctx->ignore_runtime_filter_error()) {
+                    ctx->cancel(status);

Review Comment:
   [P1] Preserve EOF as a non-cancelling sync result
   
   This branch now calls `QueryContext::cancel()` for `END_OF_FILE` whenever 
`ignore_runtime_filter_error=false`. That status is a normal late-response 
sentinel here: `FragmentMgr::send_filter_size()` returns EOF when the 
merge-side query context has already finished. The old `SyncSizeClosure` still 
disabled the wrapper and released the dependency, but its base handler 
explicitly returned before cancelling on EOF. With this change, an otherwise 
healthy local fragment can have all of its pipelines cancelled just because the 
remote merge context disappeared first. Please retain the 
wrapper-disable/`sub()` cleanup while exempting EOF from logging/cancellation, 
and cover `SyncSizeCallback` with the non-ignoring option (the added EOF test 
exercises a different callback).



##########
be/src/exec/runtime_filter/runtime_filter_mgr.cpp:
##########
@@ -669,11 +691,14 @@ Status 
RuntimeFilterMergeControllerEntity::_send_rf_to_target(
     }
 
     auto st = Status::OK();
-    for (auto& target : targets) {
+    cnt_val.publish_callbacks.resize(targets.size());

Review Comment:
   [P1] Synchronize callback storage with recursive reset
   
   `merge()` releases `cnt_val.mtx` before calling `_send_rf_to_target()`, so 
this resize/write is not synchronized with `GlobalMergeContext::reset()`, which 
takes that mutex and clears `publish_callbacks`. Waiting for the old PFC to be 
destroyed does not drain its fire-and-forget `merge_filter` RPC; an old merge 
handler can pass the stage check, drop the mutex, and still be publishing when 
the next recursive round resets the context. That permits concurrent 
`clear()`/`resize()` on the same vector (or lets an old stage repopulate the 
new stage's owner set), which is undefined behavior. Please serialize the whole 
publish setup with reset and revalidate the stage, or make callback ownership 
per-RPC/self-owned so reset never mutates shared in-flight storage.



##########
be/src/exec/runtime_filter/runtime_filter.cpp:
##########
@@ -36,13 +36,12 @@ Status RuntimeFilter::_push_to_remote(RuntimeState* state, 
const TNetworkAddress
 
     auto merge_filter_request = std::make_shared<PMergeFilterRequest>();
     merge_filter_request->set_stage(_stage);
-    auto merge_filter_callback = 
DummyBrpcCallback<PMergeFilterResponse>::create_shared();
+    _merge_filter_callback = 
HandleErrorBrpcCallback<PMergeFilterResponse>::create_shared(

Review Comment:
   [P2] Release the callback/controller after RPC completion
   
   Storing this callback on the filter also keeps its `brpc::Controller` alive 
after `Run()`; the serialized filter is appended to that controller's 
`request_attachment()` below. In the pinned brpc 1.4.0 implementation the 
request attachment is cleared by controller reset/destruction, not by normal 
RPC completion, and there is no completion-path reset here. The same pattern 
appears in `GlobalMergeContext::publish_callbacks`, so successful 
merge/direct-publish RPCs retain an extra serialized bloom-filter buffer until 
a CTE reset or query teardown (potentially tens of MiB per filter during a long 
probe). Please use completion-scoped ownership like 
`RuntimeFilterRelayRpcClosure`, or otherwise release/reset each owner safely at 
the end of its callback.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to