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


##########
be/src/runtime/result_block_buffer.cpp:
##########
@@ -103,19 +104,140 @@ Status ResultBlockBuffer<ResultCtxType>::close(const 
TUniqueId& id, Status exec_
 }
 
 template <typename ResultCtxType>
-void ResultBlockBuffer<ResultCtxType>::cancel(const Status& reason) {
-    std::unique_lock<std::mutex> l(_lock);
+void ResultBlockBuffer<ResultCtxType>::cancel(const Status& reason, bool 
release_outfile) {
     SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(_mem_tracker);
-    if (_status.ok()) {
-        _status = reason;
+    {
+        std::unique_lock<std::mutex> l(_lock);
+        if (_status.ok()) {
+            _status = reason;
+        }
+        _arrow_data_arrival.notify_all();
+        for (auto& ctx : _waiting_rpc) {
+            ctx->on_failure(reason);
+        }
+        _waiting_rpc.clear();
+        _update_dependency();
+        _result_batch_queue.clear();
     }
-    _arrow_data_arrival.notify_all();
-    for (auto& ctx : _waiting_rpc) {
-        ctx->on_failure(reason);
+    if (release_outfile) {
+        // Release query result memory before rollback performs potentially 
slow remote I/O.
+        release_outfile_cleanup();
     }
-    _waiting_rpc.clear();
-    _update_dependency();
-    _result_batch_queue.clear();
+}
+
+template <typename ResultCtxType>
+Status ResultBlockBuffer<ResultCtxType>::add_outfile_cleanup(OutfileCleanup 
cleanup) {
+    SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(_mem_tracker);
+    bool run_cleanup = false;
+    bool discard_cleanup = false;
+    {
+        std::lock_guard<std::mutex> l(_lock);
+        if (_outfile_state == OutfileState::ABORTED) {
+            run_cleanup = true;
+        } else if (_outfile_state == OutfileState::COMMITTED) {
+            discard_cleanup = true;
+        } else {
+            _outfile_cleanups.emplace_back(std::move(cleanup));
+        }
+    }
+    if (discard_cleanup) {
+        cleanup = nullptr;
+        return Status::OK();
+    }
+    if (run_cleanup) {
+        Status status;
+        for (int attempt = 0; attempt < 3; ++attempt) {
+            status = cleanup();
+            if (status.ok()) {
+                return status;
+            }
+        }
+        // Cancellation removed this buffer from the manager, so bounded 
inline retries are the
+        // last live-query owner for a cleanup registered after that point.
+        return status;
+    }
+    return Status::OK();
+}
+
+template <typename ResultCtxType>
+Status ResultBlockBuffer<ResultCtxType>::finish_outfile(OutfileOperation 
operation) {
+    SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(_mem_tracker);
+    std::vector<OutfileCleanup> cleanups;
+    {
+        std::lock_guard<std::mutex> l(_lock);
+        if (operation == OutfileOperation::PREPARE) {
+            if (_outfile_state == OutfileState::ABORTED) {
+                return Status::Cancelled("outfile result buffer was already 
aborted");
+            }
+            if (_outfile_state == OutfileState::PENDING) {
+                _outfile_state = OutfileState::PREPARED;
+            }
+            return Status::OK();
+        }
+        if (operation == OutfileOperation::COMMIT) {
+            if (_outfile_state == OutfileState::COMMITTED) {
+                return Status::OK();
+            }
+            if (_outfile_state != OutfileState::PREPARED) {
+                return Status::InternalError("outfile result buffer is not 
prepared");
+            }
+            // Keep rollback ownership after this provisional acknowledgement 
so an explicit ABORT
+            // can compensate a COMMIT that reached only a subset of receivers.
+            _outfile_state = OutfileState::COMMITTED;
+            return Status::OK();
+        }
+        if (operation == OutfileOperation::ABORT) {
+            if (_outfile_state == OutfileState::ABORTED && 
_outfile_cleanups.empty()) {

Review Comment:
   [P1] Do not treat an in-flight ABORT drain as empty/finished. FE 
`OUTFILE_ABORT` can set `ABORTED`, swap the registered callbacks out, and block 
in cleanup while timeout/shutdown erases the manager entry and 
`release_outfile_cleanup()` re-enters here. This branch then returns OK because 
the vector is only temporarily empty; if the first cleanup fails, it reinserts 
after the manager owner is gone and no retry can find the buffer. This differs 
from the prior late-registration thread: this callback was already registered 
and is temporarily owned by the first ABORT stack. Track/join an active drain 
(or transfer it to durable retry ownership), and add a latch test overlapping 
ABORT with cancellation and a cleanup that fails before succeeding.



##########
be/src/service/internal_service.cpp:
##########
@@ -770,41 +819,156 @@ void 
PInternalService::outfile_write_success(google::protobuf::RpcController* co
             return;
         }
 
-        auto&& res = FileFactory::create_file_writer(file_type_res.value(), 
ExecEnv::GetInstance(),
-                                                     
file_options.broker_addresses,
-                                                     
file_options.broker_properties, file_name,
-                                                     {
-                                                             .write_file_cache 
= false,
-                                                             .sync_file_data = 
false,
-                                                     });
-        using T = std::decay_t<decltype(res)>;
-        if (!res.has_value()) [[unlikely]] {
-            st = std::forward<T>(res).error();
+        io::FSPropertiesRef properties(file_type_res.value());
+        properties.broker_addresses = &file_options.broker_addresses;
+        properties.properties = &file_options.broker_properties;
+        io::FileDescription file_description;
+        file_description.path = file_name;
+        auto fs_res = FileFactory::create_fs(properties, file_description);
+        if (!fs_res.has_value()) [[unlikely]] {
+            st = std::move(fs_res).error();
+            st.to_protobuf(result->mutable_status());
+            return;
+        }
+        auto file_system = std::move(fs_res).value();
+
+        if (request->operation() == OUTFILE_MARKER_DELETE) {
+            // Delete only a path created by this token. A blind rollback 
could otherwise remove a
+            // pre-existing user file when CREATE failed before acquiring 
ownership.
+            if (owned_marker_path.empty()) {
+                Status::OK().to_protobuf(result->mutable_status());
+                return;
+            }
+            st = file_system->delete_file(owned_marker_path);
+            if (st.ok() || st.is<ErrorCode::NOT_FOUND>()) {
+                std::lock_guard marker_guard(outfile_marker_lock);
+                auto state_it = outfile_marker_states.find(marker_token);
+                if (state_it != outfile_marker_states.end() &&
+                    state_it->second.owned_path == owned_marker_path) {
+                    state_it->second.owned_path.clear();
+                    state_it->second.updated_at = 
std::chrono::steady_clock::now();
+                }
+                st = Status::OK();
+            }
+            st.to_protobuf(result->mutable_status());
+            return;
+        }
+        if (request->operation() != OUTFILE_MARKER_CREATE) {
+            Status::InvalidArgument("unknown OUTFILE success marker operation")
+                    .to_protobuf(result->mutable_status());
+            return;
+        }
+
+        // Never claim an existing marker path; rollback is restricted to 
token-owned paths below.
+        bool exists = true;
+        st = file_system->exists(file_name, &exists);

Review Comment:
   [P1] Preserve legacy remote success-marker behavior when atomic OUTFILE is 
disabled. This unconditional existence check also runs for an old FE (the new 
fields are absent and `marker_token` falls back to the path) and for a new FE 
below v14. Previously only LOCAL rejected an existing marker; S3/HDFS/Broker 
went directly through their existing writer semantics, so a BE-first rolling 
upgrade can make an otherwise legacy query fail here after its data files were 
written. Gate the new nonexistence/ownership policy on 
`file_options.enable_atomic_outfile` (or an explicit protocol bit), and add 
field-absent/version-13 remote-marker compatibility coverage.



##########
be/src/exec/sink/writer/vfile_result_writer.cpp:
##########
@@ -123,13 +170,21 @@ Status VFileResultWriter::_create_next_file_writer() {
 
 Status VFileResultWriter::_create_file_writer(const std::string& file_name) {
     auto file_type = 
DORIS_TRY(FileFactory::convert_storage_type(_storage_type));
-    _file_writer_impl = DORIS_TRY(FileFactory::create_file_writer(
-            file_type, _state->exec_env(), _file_opts->broker_addresses,
-            _file_opts->broker_properties, file_name,
-            {
-                    .write_file_cache = false,
-                    .sync_file_data = false,
-            }));
+    io::FSPropertiesRef properties(file_type);
+    properties.broker_addresses = &_file_opts->broker_addresses;
+    properties.properties = &_file_opts->broker_properties;
+    io::FileDescription file_description;
+    file_description.path = file_name;
+    _file_system = DORIS_TRY(FileFactory::create_fs(properties, 
file_description));
+    // Create/open can publish a path before returning an error, so claim 
deterministic ownership
+    // first. A separate filesystem preserves Broker's existing per-path 
endpoint selection.
+    _created_files.emplace_back(_file_system, file_name);

Review Comment:
   [P2] Avoid retaining one initialized filesystem wrapper per rotated file. 
This call runs for every part and `_created_files` keeps each shared pointer; 
atomic success then moves the whole vector into a cleanup callback retained 
until the result buffer expires (300 seconds by default). With the supported 5 
MB split size, a large export can therefore pin O(parts) 
filesystem/configuration/client-holder objects even though S3/HDFS clients are 
cache-shared and Broker cleanup needs at most one filesystem per selected 
endpoint. Keep the O(parts) path manifest, but deduplicate cleanup filesystems 
by storage identity/endpoint, and add a many-rotation test that verifies 
retained filesystem instances stay bounded.



##########
be/src/exec/sink/writer/vfile_result_writer.cpp:
##########
@@ -123,13 +170,21 @@ Status VFileResultWriter::_create_next_file_writer() {
 
 Status VFileResultWriter::_create_file_writer(const std::string& file_name) {
     auto file_type = 
DORIS_TRY(FileFactory::convert_storage_type(_storage_type));
-    _file_writer_impl = DORIS_TRY(FileFactory::create_file_writer(
-            file_type, _state->exec_env(), _file_opts->broker_addresses,
-            _file_opts->broker_properties, file_name,
-            {
-                    .write_file_cache = false,
-                    .sync_file_data = false,
-            }));
+    io::FSPropertiesRef properties(file_type);
+    properties.broker_addresses = &_file_opts->broker_addresses;

Review Comment:
   [P1] Make deferred Broker cleanup own this configuration. These pointers 
refer into `ResultFileSinkOperatorX::_file_opts`, and `BrokerFileSystem` stores 
the selected `TNetworkAddress` and property map as `const&`. Atomic close moves 
that filesystem into a result-buffer cleanup callback, but pipeline teardown 
destroys `_file_opts`; a later timeout, partial-COMMIT failure, or marker 
failure then calls `delete_file()` through dangling references, risking a BE 
crash and failed rollback. Copy the selected address/properties into the 
retained filesystem or cleanup manifest, and add a lifetime test that destroys 
the operator-owned options before invoking ABORT.



-- 
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