This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new 114b4463429 branch-4.2: [Enhancement] use stream load async return to 
optimize the performance under high concurrency (#70171)
114b4463429 is described below

commit 114b44634294e354f13062d9e98d4df8f562760d
Author: Xin Liao <[email protected]>
AuthorDate: Thu Nov 5 09:36:10 2026 +0800

    branch-4.2: [Enhancement] use stream load async return to optimize the 
performance under high concurrency (#70171)
    
    Pick #53144 (merged commit 3ec56d7a796b9361d67203fcd9b7e2a32bbbeab4).
    
    Co-authored-by: Userwhite <[email protected]>
---
 be/src/common/config.cpp                           |   8 ++
 be/src/common/config.h                             |   7 +
 be/src/io/fs/multi_table_pipe.cpp                  |   2 +-
 be/src/load/group_commit/wal/wal_table.cpp         |   2 +-
 .../routine_load/routine_load_task_executor.cpp    |   8 +-
 be/src/load/stream_load/stream_load_context.h      |  14 +-
 be/src/load/stream_load/stream_load_executor.cpp   |  27 +++-
 be/src/load/stream_load/stream_load_executor.h     |   5 +
 be/src/runtime/fragment_mgr.cpp                    |   6 +-
 be/src/runtime/fragment_mgr.h                      |   4 +-
 be/src/service/http/action/http_stream.cpp         |   2 +-
 be/src/service/http/action/stream_load.cpp         | 151 ++++++++++++---------
 be/src/service/http/action/stream_load.h           |   5 +-
 be/src/service/http/ev_http_server.cpp             |   1 +
 be/src/service/http/http_channel.cpp               |   3 +
 be/src/service/http/http_request.cpp               |  45 ++++++
 be/src/service/http/http_request.h                 |  13 ++
 be/src/service/internal_service.cpp                |   2 +-
 18 files changed, 224 insertions(+), 81 deletions(-)

diff --git a/be/src/common/config.cpp b/be/src/common/config.cpp
index 106e578ebc2..6f6a4bedcd1 100644
--- a/be/src/common/config.cpp
+++ b/be/src/common/config.cpp
@@ -633,6 +633,14 @@ DEFINE_Bool(enable_all_http_auth, "false");
 // Number of webserver workers
 DEFINE_Int32(webserver_num_workers, "128");
 
+// Async replies: stream load only now
+// reply wait timeout only happens if:
+// 1. Stream load fragment execution times out
+//    HTTP request freed → stream load canceled
+// 2. Client disconnects
+DEFINE_mInt32(async_reply_timeout_s, "60");
+DEFINE_Validator(async_reply_timeout_s, [](const int config) -> bool { return 
config >= 3; });
+
 DEFINE_Bool(enable_single_replica_load, "true");
 // Number of download workers for single replica load
 DEFINE_Int32(single_replica_load_download_num_workers, "64");
diff --git a/be/src/common/config.h b/be/src/common/config.h
index 7910e627cd8..f597ee7cc2c 100644
--- a/be/src/common/config.h
+++ b/be/src/common/config.h
@@ -705,6 +705,13 @@ DECLARE_Bool(enable_all_http_auth);
 // Number of webserver workers
 DECLARE_Int32(webserver_num_workers);
 
+// Async replies: stream load only now
+// reply wait timeout only happens if:
+// 1. Stream load fragment execution times out
+//    HTTP request freed → stream load canceled
+// 2. Client disconnects
+DECLARE_mInt32(async_reply_timeout_s);
+
 DECLARE_Bool(enable_single_replica_load);
 // Number of download workers for single replica load
 DECLARE_Int32(single_replica_load_download_num_workers);
diff --git a/be/src/io/fs/multi_table_pipe.cpp 
b/be/src/io/fs/multi_table_pipe.cpp
index 0ce0d9b869f..92a333e1431 100644
--- a/be/src/io/fs/multi_table_pipe.cpp
+++ b/be/src/io/fs/multi_table_pipe.cpp
@@ -341,7 +341,7 @@ void MultiTablePipe::_handle_consumer_finished() {
               << " number_filtered_rows=" << _ctx->number_filtered_rows
               << " number_unselected_rows=" << _ctx->number_unselected_rows
               << ", ctx: " << _ctx->brief();
-    _ctx->promise.set_value(_status); // when all done, finish the routine 
load task
+    _ctx->load_status_promise.set_value(_status); // when all done, finish the 
routine load task
 }
 
 } // namespace io
diff --git a/be/src/load/group_commit/wal/wal_table.cpp 
b/be/src/load/group_commit/wal/wal_table.cpp
index f797def5c27..24a6c9b28fe 100644
--- a/be/src/load/group_commit/wal/wal_table.cpp
+++ b/be/src/load/group_commit/wal/wal_table.cpp
@@ -274,7 +274,7 @@ Status WalTable::_handle_stream_load(int64_t wal_id, const 
std::string& wal,
                     { st = 
Status::InternalError("WalTable::_handle_stream_load.fail"); });
     if (st.ok()) {
         // wait stream load finish
-        RETURN_IF_ERROR(ctx->future.get());
+        RETURN_IF_ERROR(ctx->load_status_future.get());
         if (ctx->status.ok()) {
             // deprecated and should be removed in 3.1, use token instead.
             ctx->auth.auth_code = wal_id;
diff --git a/be/src/load/routine_load/routine_load_task_executor.cpp 
b/be/src/load/routine_load/routine_load_task_executor.cpp
index ad8f15c070b..8364de65e03 100644
--- a/be/src/load/routine_load/routine_load_task_executor.cpp
+++ b/be/src/load/routine_load/routine_load_task_executor.cpp
@@ -381,7 +381,7 @@ void 
RoutineLoadTaskExecutor::exec_task(std::shared_ptr<StreamLoadContext> ctx,
         if (UNLIKELY(!_status_.ok() && !_status_.is<PUBLISH_TIMEOUT>())) { \
             err_handler(ctx, _status_, err_msg);                           \
             cb(ctx);                                                       \
-            _status_ = ctx->future.get();                                  \
+            _status_ = ctx->load_status_future.get();                      \
             if (!_status_.ok()) {                                          \
                 LOG(ERROR) << "failed to get future, " << ctx->brief();    \
             }                                                              \
@@ -468,7 +468,7 @@ void 
RoutineLoadTaskExecutor::exec_task(std::shared_ptr<StreamLoadContext> ctx,
     }
 
     // wait for all consumers finished
-    HANDLE_ERROR(ctx->future.get(), "consume failed");
+    HANDLE_ERROR(ctx->load_status_future.get(), "consume failed");
 
     ctx->load_cost_millis = UnixMillis() - ctx->start_millis;
 
@@ -549,12 +549,12 @@ Status 
RoutineLoadTaskExecutor::_execute_plan_for_test(std::shared_ptr<StreamLoa
             Status st = pipe->read_at(0, result, &read_bytes);
             if (!st.ok()) {
                 LOG(WARNING) << "read failed";
-                ctx->promise.set_value(st);
+                ctx->load_status_promise.set_value(st);
                 break;
             }
 
             if (read_bytes == 0) {
-                ctx->promise.set_value(Status::OK());
+                ctx->load_status_promise.set_value(Status::OK());
                 break;
             }
 
diff --git a/be/src/load/stream_load/stream_load_context.h 
b/be/src/load/stream_load/stream_load_context.h
index 195ec4942c8..4e257c86e7d 100644
--- a/be/src/load/stream_load/stream_load_context.h
+++ b/be/src/load/stream_load/stream_load_context.h
@@ -24,6 +24,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <condition_variable>
 #include <future>
 #include <map>
 #include <memory>
@@ -205,8 +206,8 @@ public:
 
     std::vector<TTabletCommitInfo> commit_infos;
 
-    std::promise<Status> promise;
-    std::future<Status> future = promise.get_future();
+    std::promise<Status> load_status_promise;
+    std::future<Status> load_status_future = load_status_promise.get_future();
 
     Status status;
 
@@ -257,6 +258,15 @@ public:
     std::string qualified_user;
     std::string cloud_cluster;
 
+    // 1. _can_send_reply: Ensure `send_reply` is invoked only after 
on_header/handle complete,
+    //    avoid client errors (e.g., broken pipe).
+    // 2. _finish_send_reply: Prevent duplicate reply sending; skip reply if 
HTTP request is canceled
+    //    due to long import execution time.
+    std::mutex _send_reply_lock;
+    std::condition_variable _can_send_reply_cv;
+    bool _can_send_reply = false;
+    bool _finish_send_reply = false;
+
 public:
     ExecEnv* exec_env() { return _exec_env; }
 
diff --git a/be/src/load/stream_load/stream_load_executor.cpp 
b/be/src/load/stream_load/stream_load_executor.cpp
index c7594917847..c600f16a300 100644
--- a/be/src/load/stream_load/stream_load_executor.cpp
+++ b/be/src/load/stream_load/stream_load_executor.cpp
@@ -27,8 +27,11 @@
 #include <glog/logging.h>
 #include <stdint.h>
 
+#include <functional>
 #include <future>
 #include <map>
+#include <memory>
+#include <mutex>
 #include <ostream>
 #include <string>
 #include <utility>
@@ -86,6 +89,12 @@ bvar::LatencyRecorder 
g_stream_load_commit_txn_latency("stream_load", "commit_tx
 
 Status 
StreamLoadExecutor::execute_plan_fragment(std::shared_ptr<StreamLoadContext> 
ctx,
                                                  const 
TPipelineFragmentParamsList& parent) {
+    return execute_plan_fragment(ctx, parent, 
[](std::shared_ptr<StreamLoadContext> ctx) {});
+}
+
+Status StreamLoadExecutor::execute_plan_fragment(
+        std::shared_ptr<StreamLoadContext> ctx, const 
TPipelineFragmentParamsList& parent,
+        const std::function<void(std::shared_ptr<StreamLoadContext> ctx)>& cb) 
{
 // submit this params
 #ifndef BE_TEST
     
ctx->put_result.pipeline_params.query_options.__set_enable_strict_cast(false);
@@ -93,7 +102,9 @@ Status 
StreamLoadExecutor::execute_plan_fragment(std::shared_ptr<StreamLoadConte
     LOG(INFO) << "begin to execute stream load. label=" << ctx->label << ", 
txn_id=" << ctx->txn_id
               << ", query_id=" << ctx->id;
     Status st;
-    auto exec_fragment = [ctx, this](RuntimeState* state, Status* status) {
+    std::shared_ptr<bool> is_prepare_success = std::make_shared<bool>(false);
+
+    auto exec_fragment = [ctx, cb, this, is_prepare_success](RuntimeState* 
state, Status* status) {
         if (ctx->group_commit) {
             ctx->label = state->import_label();
             ctx->txn_id = state->wal_id();
@@ -144,7 +155,7 @@ Status 
StreamLoadExecutor::execute_plan_fragment(std::shared_ptr<StreamLoadConte
             }
         }
         ctx->write_data_cost_nanos = MonotonicNanos() - 
ctx->start_write_data_nanos;
-        ctx->promise.set_value(*status);
+        ctx->load_status_promise.set_value(*status);
 
         if (!status->ok() && ctx->body_sink != nullptr) {
             // In some cases, the load execution is exited early.
@@ -164,16 +175,22 @@ Status 
StreamLoadExecutor::execute_plan_fragment(std::shared_ptr<StreamLoadConte
                 static_cast<void>(this->commit_txn(ctx.get()));
             }
         }
+
+        if (*is_prepare_success) {
+            // if prepare failed, on_header will send reply
+            cb(ctx);
+        }
     };
-    st = _exec_env->fragment_mgr()->exec_plan_fragment(
-            ctx->put_result.pipeline_params, QuerySource::STREAM_LOAD, 
exec_fragment, parent);
+    st = 
_exec_env->fragment_mgr()->exec_plan_fragment(ctx->put_result.pipeline_params,
+                                                       
QuerySource::STREAM_LOAD, exec_fragment,
+                                                       parent, 
is_prepare_success);
 
     if (!st.ok()) {
         // no need to check unref's return value
         return st;
     }
 #else
-    ctx->promise.set_value(k_stream_load_plan_status);
+    ctx->load_status_promise.set_value(k_stream_load_plan_status);
 #endif
     return Status::OK();
 }
diff --git a/be/src/load/stream_load/stream_load_executor.h 
b/be/src/load/stream_load/stream_load_executor.h
index 3472ae5a200..0455e31e088 100644
--- a/be/src/load/stream_load/stream_load_executor.h
+++ b/be/src/load/stream_load/stream_load_executor.h
@@ -19,6 +19,7 @@
 
 #include <gen_cpp/PaloInternalService_types.h>
 
+#include <functional>
 #include <memory>
 
 #include "common/factory_creator.h"
@@ -54,6 +55,10 @@ public:
     Status execute_plan_fragment(std::shared_ptr<StreamLoadContext> ctx,
                                  const TPipelineFragmentParamsList& parent);
 
+    Status execute_plan_fragment(
+            std::shared_ptr<StreamLoadContext> ctx, const 
TPipelineFragmentParamsList& parent,
+            const std::function<void(std::shared_ptr<StreamLoadContext> ctx)>& 
cb);
+
 protected:
     // collect the load statistics from context and set them to stat
     // return true if stat is set, otherwise, return false
diff --git a/be/src/runtime/fragment_mgr.cpp b/be/src/runtime/fragment_mgr.cpp
index 5437711b894..f6fee3af383 100644
--- a/be/src/runtime/fragment_mgr.cpp
+++ b/be/src/runtime/fragment_mgr.cpp
@@ -627,7 +627,8 @@ std::string FragmentMgr::dump_pipeline_tasks(TUniqueId& 
query_id) {
 
 Status FragmentMgr::exec_plan_fragment(const TPipelineFragmentParams& params,
                                        QuerySource query_source, const 
FinishCallback& cb,
-                                       const TPipelineFragmentParamsList& 
parent) {
+                                       const TPipelineFragmentParamsList& 
parent,
+                                       std::shared_ptr<bool> 
is_prepare_success) {
     VLOG_ROW << "exec_plan_fragment " << print_fragment_params_summary(params);
 
     std::shared_ptr<QueryContext> query_ctx;
@@ -695,6 +696,9 @@ Status FragmentMgr::exec_plan_fragment(const 
TPipelineFragmentParams& params,
     query_ctx->set_pipeline_context(params.fragment_id, context);
 
     RETURN_IF_ERROR(context->submit());
+    if (is_prepare_success != nullptr) {
+        *is_prepare_success = true;
+    }
     return Status::OK();
 }
 
diff --git a/be/src/runtime/fragment_mgr.h b/be/src/runtime/fragment_mgr.h
index c010637849a..8bf64f45a6a 100644
--- a/be/src/runtime/fragment_mgr.h
+++ b/be/src/runtime/fragment_mgr.h
@@ -127,8 +127,10 @@ public:
     void remove_pipeline_context(std::pair<TUniqueId, int> key);
     void remove_query_context(const TUniqueId& key);
 
+    // `is_prepare_success` is used by invoker to ensure callback can be 
handle correctly (eg. stream_load_executor)
     Status exec_plan_fragment(const TPipelineFragmentParams& params, const 
QuerySource query_type,
-                              const FinishCallback& cb, const 
TPipelineFragmentParamsList& parent);
+                              const FinishCallback& cb, const 
TPipelineFragmentParamsList& parent,
+                              std::shared_ptr<bool> is_prepare_success = 
nullptr);
 
     Status start_query_execution(const PExecPlanFragmentStartRequest* request);
 
diff --git a/be/src/service/http/action/http_stream.cpp 
b/be/src/service/http/action/http_stream.cpp
index 9a17dfa218b..ae005e37cdd 100644
--- a/be/src/service/http/action/http_stream.cpp
+++ b/be/src/service/http/action/http_stream.cpp
@@ -160,7 +160,7 @@ Status HttpStreamAction::_handle(HttpRequest* http_req, 
std::shared_ptr<StreamLo
     RETURN_IF_ERROR(ctx->body_sink->finish());
 
     // wait stream load finish
-    RETURN_IF_ERROR(ctx->future.get());
+    RETURN_IF_ERROR(ctx->load_status_future.get());
 
     if (ctx->group_commit) {
         LOG(INFO) << "skip commit because this is group commit, pipe_id=" << 
ctx->id.to_string();
diff --git a/be/src/service/http/action/stream_load.cpp 
b/be/src/service/http/action/stream_load.cpp
index a4ef0bca8f8..dd930f850f1 100644
--- a/be/src/service/http/action/stream_load.cpp
+++ b/be/src/service/http/action/stream_load.cpp
@@ -29,9 +29,11 @@
 #include <sys/time.h>
 #include <thrift/protocol/TDebugProtocol.h>
 
+#include <algorithm>
 #include <cstdint>
 #include <cstdlib>
 #include <ctime>
+#include <functional>
 #include <future>
 #include <sstream>
 #include <stdexcept>
@@ -114,17 +116,85 @@ void StreamLoadAction::handle(HttpRequest* req) {
         return;
     }
 
+    {
+        std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
+        ctx->_can_send_reply = true;
+        ctx->_can_send_reply_cv.notify_all();
+    }
+
     // status already set to fail
     if (ctx->status.ok()) {
-        ctx->status = _handle(ctx);
+        ctx->status = _handle(ctx, req);
         if (!ctx->status.ok() && !ctx->status.is<PUBLISH_TIMEOUT>()) {
-            LOG(WARNING) << "handle streaming load failed, id=" << ctx->id
-                         << ", errmsg=" << ctx->status;
+            _send_reply(ctx, req);
+        }
+    }
+}
+
+Status StreamLoadAction::_handle(std::shared_ptr<StreamLoadContext> ctx, 
HttpRequest* req) {
+    if (ctx->body_bytes > 0 && ctx->receive_bytes != ctx->body_bytes) {
+        LOG(WARNING) << "recevie body don't equal with body bytes, 
body_bytes=" << ctx->body_bytes
+                     << ", receive_bytes=" << ctx->receive_bytes << ", id=" << 
ctx->id;
+        return Status::Error<ErrorCode::NETWORK_ERROR>("receive body don't 
equal with body bytes");
+    }
+
+    // if we use non-streaming, MessageBodyFileSink.finish will close the file
+    RETURN_IF_ERROR(ctx->body_sink->finish());
+    if (!ctx->use_streaming) {
+        // we need to close file first, then execute_plan_fragment here
+        ctx->body_sink.reset();
+        TPipelineFragmentParamsList mocked;
+        
RETURN_IF_ERROR(_exec_env->stream_load_executor()->execute_plan_fragment(
+                ctx, mocked,
+                [req, this](std::shared_ptr<StreamLoadContext> ctx) { 
_on_finish(ctx, req); }));
+    }
+
+    return Status::OK();
+}
+
+void StreamLoadAction::_on_finish(std::shared_ptr<StreamLoadContext> ctx, 
HttpRequest* req) {
+    ctx->status = ctx->load_status_future.get();
+    if (ctx->status.ok()) {
+        if (ctx->group_commit) {
+            LOG(INFO) << "skip commit because this is group commit, pipe_id="
+                      << ctx->id.to_string();
+        } else if (ctx->two_phase_commit) {
+            int64_t pre_commit_start_time = MonotonicNanos();
+            ctx->status = 
_exec_env->stream_load_executor()->pre_commit_txn(ctx.get());
+            ctx->pre_commit_txn_cost_nanos = MonotonicNanos() - 
pre_commit_start_time;
+        } else {
+            // If put file success we need commit this load
+            int64_t commit_and_publish_start_time = MonotonicNanos();
+            ctx->status = 
_exec_env->stream_load_executor()->commit_txn(ctx.get());
+            ctx->commit_and_publish_txn_cost_nanos =
+                    MonotonicNanos() - commit_and_publish_start_time;
+            g_stream_load_commit_and_publish_latency_ms
+                    << ctx->commit_and_publish_txn_cost_nanos / 1000000;
         }
     }
+    _send_reply(ctx, req);
+}
+
+void StreamLoadAction::_send_reply(std::shared_ptr<StreamLoadContext> ctx, 
HttpRequest* req) {
+    std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
+    // 1. _can_send_reply: ensure `send_reply` is invoked only after 
on_header/handle complete,
+    //    avoid client errors (e.g., broken pipe).
+    // 2. _finish_send_reply: Prevent duplicate reply sending; skip reply if 
HTTP request is canceled
+    //    due to long import execution time.
+    while (!ctx->_finish_send_reply && !ctx->_can_send_reply) {
+        ctx->_can_send_reply_cv.wait(lock1);
+    }
+    if (ctx->_finish_send_reply) {
+        return;
+    }
+    DCHECK(ctx->_can_send_reply);
+    ctx->_finish_send_reply = true;
+    ctx->_can_send_reply_cv.notify_all();
     ctx->load_cost_millis = UnixMillis() - ctx->start_millis;
 
     if (!ctx->status.ok() && !ctx->status.is<PUBLISH_TIMEOUT>()) {
+        LOG(WARNING) << "handle streaming load failed, id=" << ctx->id
+                     << ", errmsg=" << ctx->status;
         if (ctx->need_rollback) {
             _exec_env->stream_load_executor()->rollback_txn(ctx.get());
             ctx->need_rollback = false;
@@ -137,7 +207,7 @@ void StreamLoadAction::handle(HttpRequest* req) {
     auto str = ctx->to_json();
     // add new line at end
     str = str + '\n';
-    HttpChannel::send_reply(req, str);
+
 #ifndef BE_TEST
     if (config::enable_stream_load_record || 
config::enable_stream_load_record_to_audit_log_table) {
         if (req->header(HTTP_SKIP_RECORD_TO_AUDIT_LOG_TABLE).empty()) {
@@ -147,6 +217,8 @@ void StreamLoadAction::handle(HttpRequest* req) {
     }
 #endif
 
+    HttpChannel::send_reply(req, str);
+
     LOG(INFO) << "finished to execute stream load. label=" << ctx->label
               << ", txn_id=" << ctx->txn_id << ", query_id=" << ctx->id
               << ", load_cost_ms=" << ctx->load_cost_millis << ", 
receive_data_cost_ms="
@@ -168,46 +240,9 @@ void StreamLoadAction::handle(HttpRequest* req) {
     }
 }
 
-Status StreamLoadAction::_handle(std::shared_ptr<StreamLoadContext> ctx) {
-    if (ctx->body_bytes > 0 && ctx->receive_bytes != ctx->body_bytes) {
-        LOG(WARNING) << "recevie body don't equal with body bytes, 
body_bytes=" << ctx->body_bytes
-                     << ", receive_bytes=" << ctx->receive_bytes << ", id=" << 
ctx->id;
-        return Status::Error<ErrorCode::NETWORK_ERROR>("receive body don't 
equal with body bytes");
-    }
-
-    // if we use non-streaming, MessageBodyFileSink.finish will close the file
-    RETURN_IF_ERROR(ctx->body_sink->finish());
-    if (!ctx->use_streaming) {
-        // we need to close file first, then execute_plan_fragment here
-        ctx->body_sink.reset();
-        TPipelineFragmentParamsList mocked;
-        
RETURN_IF_ERROR(_exec_env->stream_load_executor()->execute_plan_fragment(ctx, 
mocked));
-    }
-
-    // wait stream load finish
-    RETURN_IF_ERROR(ctx->future.get());
-
-    if (ctx->group_commit) {
-        LOG(INFO) << "skip commit because this is group commit, pipe_id=" << 
ctx->id.to_string();
-        return Status::OK();
-    }
-
-    if (ctx->two_phase_commit) {
-        int64_t pre_commit_start_time = MonotonicNanos();
-        
RETURN_IF_ERROR(_exec_env->stream_load_executor()->pre_commit_txn(ctx.get()));
-        ctx->pre_commit_txn_cost_nanos = MonotonicNanos() - 
pre_commit_start_time;
-    } else {
-        // If put file success we need commit this load
-        int64_t commit_and_publish_start_time = MonotonicNanos();
-        
RETURN_IF_ERROR(_exec_env->stream_load_executor()->commit_txn(ctx.get()));
-        ctx->commit_and_publish_txn_cost_nanos = MonotonicNanos() - 
commit_and_publish_start_time;
-        g_stream_load_commit_and_publish_latency_ms
-                << ctx->commit_and_publish_txn_cost_nanos / 1000000;
-    }
-    return Status::OK();
-}
-
 int StreamLoadAction::on_header(HttpRequest* req) {
+    req->mark_send_reply();
+
     streaming_load_current_processing->increment(1);
 
     std::shared_ptr<StreamLoadContext> ctx = 
std::make_shared<StreamLoadContext>(_exec_env);
@@ -237,26 +272,12 @@ int StreamLoadAction::on_header(HttpRequest* req) {
     }
     if (!st.ok()) {
         ctx->status = std::move(st);
-        if (ctx->need_rollback) {
-            _exec_env->stream_load_executor()->rollback_txn(ctx.get());
-            ctx->need_rollback = false;
+        {
+            std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
+            ctx->_can_send_reply = true;
+            ctx->_can_send_reply_cv.notify_all();
         }
-        if (ctx->body_sink != nullptr) {
-            ctx->body_sink->cancel(ctx->status.to_string());
-        }
-        auto str = ctx->to_json();
-        // add new line at end
-        str = str + '\n';
-        HttpChannel::send_reply(req, str);
-#ifndef BE_TEST
-        if (config::enable_stream_load_record ||
-            config::enable_stream_load_record_to_audit_log_table) {
-            if (req->header(HTTP_SKIP_RECORD_TO_AUDIT_LOG_TABLE).empty()) {
-                str = ctx->prepare_stream_load_record(str);
-                _save_stream_load_record(ctx, str);
-            }
-        }
-#endif
+        _send_reply(ctx, req);
         return -1;
     }
     return 0;
@@ -836,8 +857,12 @@ Status StreamLoadAction::_process_put(HttpRequest* 
http_req,
     if (!ctx->use_streaming) {
         return Status::OK();
     }
+
     TPipelineFragmentParamsList mocked;
-    return _exec_env->stream_load_executor()->execute_plan_fragment(ctx, 
mocked);
+    return _exec_env->stream_load_executor()->execute_plan_fragment(
+            ctx, mocked, [http_req, this](std::shared_ptr<StreamLoadContext> 
ctx) {
+                _on_finish(ctx, http_req);
+            });
 }
 
 Status StreamLoadAction::_data_saved_path(HttpRequest* req, std::string* 
file_path,
diff --git a/be/src/service/http/action/stream_load.h 
b/be/src/service/http/action/stream_load.h
index 5803c1ea393..29fc92065ed 100644
--- a/be/src/service/http/action/stream_load.h
+++ b/be/src/service/http/action/stream_load.h
@@ -18,6 +18,7 @@
 #pragma once
 
 #include <memory>
+#include <mutex>
 #include <string>
 
 #include "common/metrics/metrics.h"
@@ -46,7 +47,7 @@ public:
 
 private:
     Status _on_header(HttpRequest* http_req, 
std::shared_ptr<StreamLoadContext> ctx);
-    Status _handle(std::shared_ptr<StreamLoadContext> ctx);
+    Status _handle(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req);
     Status _data_saved_path(HttpRequest* req, std::string* file_path, int64_t 
file_bytes);
     Status _process_put(HttpRequest* http_req, 
std::shared_ptr<StreamLoadContext> ctx);
     Status _can_group_commit(HttpRequest* http_req, 
std::shared_ptr<StreamLoadContext> ctx,
@@ -54,6 +55,8 @@ private:
     void _save_stream_load_record(std::shared_ptr<StreamLoadContext> ctx, 
const std::string& str);
     Status _check_wal_space(const std::string& group_commit_mode, int64_t 
content_length);
     Status _handle_group_commit(HttpRequest* http_req, 
std::shared_ptr<StreamLoadContext> ctx);
+    void _on_finish(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req);
+    void _send_reply(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req);
 
 private:
     ExecEnv* _exec_env;
diff --git a/be/src/service/http/ev_http_server.cpp 
b/be/src/service/http/ev_http_server.cpp
index 219548fd191..9aeea94c28d 100644
--- a/be/src/service/http/ev_http_server.cpp
+++ b/be/src/service/http/ev_http_server.cpp
@@ -56,6 +56,7 @@ static void on_chunked(struct evhttp_request* ev_req, void* 
param) {
 
 static void on_free(struct evhttp_request* ev_req, void* arg) {
     HttpRequest* request = (HttpRequest*)arg;
+    request->wait_finish_send_reply();
     delete request;
 }
 
diff --git a/be/src/service/http/http_channel.cpp 
b/be/src/service/http/http_channel.cpp
index 1e0db4345d0..bf101b83f4d 100644
--- a/be/src/service/http/http_channel.cpp
+++ b/be/src/service/http/http_channel.cpp
@@ -53,6 +53,7 @@ void HttpChannel::send_error(HttpRequest* request, HttpStatus 
status) {
 void HttpChannel::send_reply(HttpRequest* request, HttpStatus status) {
     evhttp_send_reply(request->get_evhttp_request(), status, 
default_reason(status).c_str(),
                       nullptr);
+    request->finish_send_reply();
 }
 
 void HttpChannel::send_reply(HttpRequest* request, HttpStatus status, const 
std::string& content) {
@@ -66,6 +67,7 @@ void HttpChannel::send_reply(HttpRequest* request, HttpStatus 
status, const std:
         evbuffer_add(evb, content.c_str(), content.size());
     }
     evhttp_send_reply(request->get_evhttp_request(), status, 
default_reason(status).c_str(), evb);
+    request->finish_send_reply();
     evbuffer_free(evb);
 }
 
@@ -80,6 +82,7 @@ void HttpChannel::send_file(HttpRequest* request, int fd, 
size_t off, size_t siz
         bufferevent_add_to_rate_limit_group(buffer_event, rate_limit_group);
     }
     evhttp_send_reply(evhttp_request, HttpStatus::OK, 
default_reason(HttpStatus::OK).c_str(), evb);
+    request->finish_send_reply();
     evbuffer_free(evb);
 }
 
diff --git a/be/src/service/http/http_request.cpp 
b/be/src/service/http/http_request.cpp
index 2a21ba42251..a9182c9e4d5 100644
--- a/be/src/service/http/http_request.cpp
+++ b/be/src/service/http/http_request.cpp
@@ -22,6 +22,7 @@
 #include <event2/http_struct.h>
 #include <event2/keyvalq_struct.h>
 
+#include <memory>
 #include <sstream>
 #include <string>
 #include <unordered_map>
@@ -187,4 +188,48 @@ const char* HttpRequest::remote_host() const {
     return _ev_req->remote_host;
 }
 
+void HttpRequest::finish_send_reply() {
+    if (_send_reply_type == REPLY_SYNC) {
+        return;
+    }
+
+    std::string infos;
+    if (_handler_ctx != nullptr) {
+        infos = 
reinterpret_cast<StreamLoadContext*>(_handler_ctx.get())->brief();
+    }
+    _http_reply_promise.set_value(true);
+}
+
+void HttpRequest::wait_finish_send_reply() {
+    if (_send_reply_type == REPLY_SYNC) {
+        return;
+    }
+
+    std::string infos;
+    StreamLoadContext* ctx = nullptr;
+    if (_handler_ctx != nullptr) {
+        ctx = reinterpret_cast<StreamLoadContext*>(_handler_ctx.get());
+        infos = ctx->brief();
+        _handler->free_handler_ctx(_handler_ctx);
+    }
+
+    VLOG_NOTICE << "start to wait send reply, infos=" << infos;
+    auto status = 
_http_reply_future.wait_for(std::chrono::seconds(config::async_reply_timeout_s));
+    // if request is timeout and can't cancel fragment in time, it will cause 
some new request block
+    // so we will free cancelled request in time.
+    if (status != std::future_status::ready) {
+        LOG(WARNING) << "wait for send reply timeout, " << 
this->debug_string();
+        std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
+        // do not send_reply after free current request
+        ctx->_can_send_reply = false;
+        ctx->_finish_send_reply = true;
+        ctx->_can_send_reply_cv.notify_all();
+    } else {
+        VLOG_NOTICE << "wait send reply finished";
+    }
+
+    // delete _handler_ctx at the end, in case that finish_send_reply can't 
get detailed info
+    _handler_ctx = nullptr;
+}
+
 } // namespace doris
diff --git a/be/src/service/http/http_request.h 
b/be/src/service/http/http_request.h
index ebf2c7d1687..cb2b0164653 100644
--- a/be/src/service/http/http_request.h
+++ b/be/src/service/http/http_request.h
@@ -19,6 +19,7 @@
 
 #include <glog/logging.h>
 
+#include <future>
 #include <map>
 #include <memory>
 #include <string>
@@ -33,6 +34,8 @@ namespace doris {
 
 class HttpHandler;
 
+enum SendReplyType { REPLY_SYNC = 0, REPLY_ASYNC = 1 };
+
 class HttpRequest {
 public:
     HttpRequest(evhttp_request* ev_req);
@@ -84,7 +87,13 @@ public:
 
     void set_header(const std::string& key, const std::string& value) { 
_headers[key] = value; }
 
+    void mark_send_reply(SendReplyType type = REPLY_ASYNC) { _send_reply_type 
= type; }
+
+    void finish_send_reply();
+    void wait_finish_send_reply();
+
 private:
+    SendReplyType _send_reply_type = REPLY_SYNC;
     HttpMethod _method;
     std::string _uri;
     std::string _raw_path;
@@ -98,6 +107,10 @@ private:
 
     std::shared_ptr<void> _handler_ctx;
     std::string _request_body;
+
+    // ensure send_reply finished
+    std::promise<bool> _http_reply_promise;
+    std::future<bool> _http_reply_future = _http_reply_promise.get_future();
 };
 
 } // namespace doris
diff --git a/be/src/service/internal_service.cpp 
b/be/src/service/internal_service.cpp
index 99c1a6e9ae1..ffc7ae0b3f5 100644
--- a/be/src/service/internal_service.cpp
+++ b/be/src/service/internal_service.cpp
@@ -1378,7 +1378,7 @@ void 
PInternalService::report_stream_load_status(google::protobuf::RpcController
     if (!stream_load_ctx) {
         st = Status::InternalError("unknown stream load id: {}", 
UniqueId(load_id).to_string());
     }
-    stream_load_ctx->promise.set_value(st);
+    stream_load_ctx->load_status_promise.set_value(st);
     st.to_protobuf(response->mutable_status());
 }
 


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

Reply via email to