Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 3.9k
branch-4.1: [fix](rpc) Fix AutoReleaseClosure data race with callback reuse (#61782)#67340
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:branch-4.1
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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( | ||
Contributor 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. [P2] Release the callback/controller after RPC completion Storing this callback on the filter also keeps its | ||
| state->query_options().ignore_runtime_filter_error ? std::weak_ptr<QueryContext> {} | ||
| : state->get_query_ctx_weak()); | ||
| auto merge_filter_closure = | ||
| AutoReleaseClosure<PMergeFilterRequest, DummyBrpcCallback<PMergeFilterResponse>>:: | ||
| create_unique(merge_filter_request, merge_filter_callback, | ||
| state->query_options().ignore_runtime_filter_error | ||
| ? std::weak_ptr<QueryContext> {} | ||
| : state->get_query_ctx_weak()); | ||
| AutoReleaseClosure<PMergeFilterRequest, HandleErrorBrpcCallback<PMergeFilterResponse>>:: | ||
| create_unique(merge_filter_request, _merge_filter_callback); | ||
| void* data = nullptr; | ||
| int len = 0; | ||
| @@ -54,19 +53,21 @@ Status RuntimeFilter::_push_to_remote(RuntimeState* state, const TNetworkAddress | ||
| pfragment_instance_id->set_hi(BackendOptions::get_local_backend().id); | ||
| pfragment_instance_id->set_lo((int64_t)this); | ||
| merge_filter_callback->cntl_->set_timeout_ms( | ||
| _merge_filter_callback->cntl_->set_timeout_ms( | ||
| get_execution_rpc_timeout_ms(state->get_query_ctx()->execution_timeout())); | ||
| if (config::execution_ignore_eovercrowded) { | ||
| merge_filter_callback->cntl_->ignore_eovercrowded(); | ||
| _merge_filter_callback->cntl_->ignore_eovercrowded(); | ||
| } | ||
| RETURN_IF_ERROR(serialize(merge_filter_request.get(), &data, &len)); | ||
| if (len > 0) { | ||
| DCHECK(data != nullptr); | ||
| merge_filter_callback->cntl_->request_attachment().append(data, len); | ||
| if (data == nullptr) { | ||
| return Status::InternalError( | ||
| "data is nullptr after serialization with len > 0, filter: {}", debug_string()); | ||
| } | ||
| _merge_filter_callback->cntl_->request_attachment().append(data, len); | ||
| } | ||
| stub->merge_filter(merge_filter_closure->cntl_.get(), merge_filter_closure->request_.get(), | ||
| merge_filter_closure->response_.get(), merge_filter_closure.get()); | ||
| // the closure will be released by brpc during closure->Run. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -43,6 +43,7 @@ | ||
| #include "runtime/exec_env.h" | ||
| #include "runtime/memory/mem_tracker.h" | ||
| #include "runtime/query_context.h" | ||
| #include "runtime/runtime_profile.h" | ||
| #include "runtime/runtime_state.h" | ||
| #include "runtime/thread_context.h" | ||
| #include "util/brpc_client_cache.h" | ||
| @@ -76,6 +77,28 @@ std::vector<RuntimeFilterPublishTarget> build_runtime_filter_publish_targets( | ||
| return publish_targets; | ||
| } | ||
| class RuntimeFilterRelayRpcClosure final : public google::protobuf::Closure { | ||
| public: | ||
| RuntimeFilterRelayRpcClosure(std::shared_ptr<PPublishFilterRequestV2> request, | ||
| std::weak_ptr<QueryContext> query_ctx) | ||
| : _request(std::move(request)), | ||
| _callback(HandleErrorBrpcCallback<PPublishFilterResponse>::create_shared( | ||
| std::move(query_ctx))) {} | ||
| void Run() override { | ||
| std::unique_ptr<RuntimeFilterRelayRpcClosure> self(this); | ||
| _callback->call(); | ||
| } | ||
| brpc::Controller* cntl() { return _callback->cntl_.get(); } | ||
| PPublishFilterRequestV2* request() { return _request.get(); } | ||
| PPublishFilterResponse* response() { return _callback->response_.get(); } | ||
| private: | ||
| std::shared_ptr<PPublishFilterRequestV2> _request; | ||
| std::shared_ptr<HandleErrorBrpcCallback<PPublishFilterResponse>> _callback; | ||
| }; | ||
| Status send_runtime_filter_relay_rpc(const RuntimeFilterPublishTask& task, | ||
| const butil::IOBuf& request_attachment, int timeout_ms, | ||
| std::weak_ptr<QueryContext> query_ctx) { | ||
| @@ -88,21 +111,17 @@ Status send_runtime_filter_relay_rpc(const RuntimeFilterPublishTask& task, | ||
| task.receiver.addr.hostname(), task.receiver.addr.port()); | ||
| } | ||
| auto closure = | ||
| AutoReleaseClosure<PPublishFilterRequestV2, DummyBrpcCallback<PPublishFilterResponse>>:: | ||
| create_unique(std::make_shared<PPublishFilterRequestV2>(task.request), | ||
| DummyBrpcCallback<PPublishFilterResponse>::create_shared(), | ||
| query_ctx); | ||
| // brpc calls Run() exactly once; RuntimeFilterRelayRpcClosure deletes itself there. | ||
| auto* closure = new RuntimeFilterRelayRpcClosure( | ||
| std::make_shared<PPublishFilterRequestV2>(task.request), std::move(query_ctx)); | ||
| if (!request_attachment.empty()) { | ||
| closure->cntl_->request_attachment().append(request_attachment); | ||
| closure->cntl()->request_attachment().append(request_attachment); | ||
| } | ||
| closure->cntl_->set_timeout_ms(timeout_ms); | ||
| closure->cntl()->set_timeout_ms(timeout_ms); | ||
| if (config::execution_ignore_eovercrowded) { | ||
| closure->cntl_->ignore_eovercrowded(); | ||
| closure->cntl()->ignore_eovercrowded(); | ||
| } | ||
| stub->apply_filterv2(closure->cntl_.get(), closure->request_.get(), closure->response_.get(), | ||
| closure.get()); | ||
| closure.release(); | ||
| stub->apply_filterv2(closure->cntl(), closure->request(), closure->response(), closure); | ||
| return Status::OK(); | ||
| } | ||
| @@ -456,9 +475,9 @@ Status RuntimeFilterMergeControllerEntity::send_filter_size(std::shared_ptr<Quer | ||
| Status st = Status::OK(); | ||
| // After all runtime filters' size are collected, we should send response to all producers. | ||
| if (cnt_val.merger->add_rf_size(request->filter_size())) { | ||
| auto ctx = query_ctx->ignore_runtime_filter_error() ? std::weak_ptr<QueryContext> {} | ||
| : query_ctx; | ||
| for (auto addr : cnt_val.source_addrs) { | ||
| cnt_val.sync_size_callbacks.resize(cnt_val.source_addrs.size()); | ||
| for (size_t i = 0; i < cnt_val.source_addrs.size(); ++i) { | ||
| auto& addr = cnt_val.source_addrs[i]; | ||
| std::shared_ptr<PBackendService_Stub> stub( | ||
| ExecEnv::GetInstance()->brpc_internal_client_cache()->get_client(addr)); | ||
| if (stub == nullptr) { | ||
| @@ -471,10 +490,14 @@ Status RuntimeFilterMergeControllerEntity::send_filter_size(std::shared_ptr<Quer | ||
| auto sync_request = std::make_shared<PSyncFilterSizeRequest>(); | ||
| sync_request->set_stage(cnt_val.stage); | ||
| auto closure = AutoReleaseClosure<PSyncFilterSizeRequest, | ||
| DummyBrpcCallback<PSyncFilterSizeResponse>>:: | ||
| create_unique(sync_request, | ||
| DummyBrpcCallback<PSyncFilterSizeResponse>::create_shared(), ctx); | ||
| auto callback = HandleErrorBrpcCallback<PSyncFilterSizeResponse>::create_shared( | ||
| query_ctx->ignore_runtime_filter_error() ? std::weak_ptr<QueryContext> {} | ||
| : query_ctx->weak_from_this()); | ||
| cnt_val.sync_size_callbacks[i] = callback; | ||
| auto closure = AutoReleaseClosure< | ||
| PSyncFilterSizeRequest, | ||
| HandleErrorBrpcCallback<PSyncFilterSizeResponse>>::create_unique(sync_request, | ||
| callback); | ||
| auto* pquery_id = closure->request_->mutable_query_id(); | ||
| pquery_id->set_hi(query_ctx->query_id().hi); | ||
| @@ -487,7 +510,6 @@ Status RuntimeFilterMergeControllerEntity::send_filter_size(std::shared_ptr<Quer | ||
| closure->request_->set_filter_id(filter_id); | ||
| closure->request_->set_filter_size(cnt_val.merger->get_received_sum_size()); | ||
| stub->sync_filter_size(closure->cntl_.get(), closure->request_.get(), | ||
| closure->response_.get(), closure.get()); | ||
| closure.release(); | ||
| @@ -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()); | ||
Contributor 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. [P1] Synchronize callback storage with recursive reset
| ||
| for (size_t i = 0; i < targets.size(); ++i) { | ||
| auto& target = targets[i]; | ||
| auto callback = HandleErrorBrpcCallback<PPublishFilterResponse>::create_shared(ctx); | ||
| cnt_val.publish_callbacks[i] = callback; | ||
| auto closure = AutoReleaseClosure<PPublishFilterRequestV2, | ||
| DummyBrpcCallback<PPublishFilterResponse>>:: | ||
| create_unique(std::make_shared<PPublishFilterRequestV2>(apply_request), | ||
| DummyBrpcCallback<PPublishFilterResponse>::create_shared(), ctx); | ||
| HandleErrorBrpcCallback<PPublishFilterResponse>>:: | ||
| create_unique(std::make_shared<PPublishFilterRequestV2>(apply_request), callback); | ||
| if (has_attachment) { | ||
| closure->cntl_->request_attachment().append(request_attachment); | ||
| @@ -717,6 +742,8 @@ Status GlobalMergeContext::reset(QueryContext* query_ctx) { | ||
| merger->increase_expected_producer_num(producer_size); | ||
| arrive_id.clear(); | ||
| source_addrs.clear(); | ||
| sync_size_callbacks.clear(); | ||
| publish_callbacks.clear(); | ||
| done = false; | ||
| stage++; | ||
| // Keep the Merger's own stage in sync for consistent debug output. | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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.
[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 sameExchangeSendCallbackandaddSuccessHandler()assigns over_suc_fnwhile 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_failedthrough 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.