github-actions[bot] commented on code in PR #67778:
URL: https://github.com/apache/doris/pull/67778#discussion_r3977236993
##########
be/src/storage/delete/calc_delete_bitmap_executor.h:
##########
@@ -66,36 +89,40 @@ class CalcDeleteBitmapToken {
// submit a generic function to the thread pool
template <typename Func>
Status submit_func(Func&& func) {
- {
- std::shared_lock rlock(_lock);
- RETURN_IF_ERROR(_status);
- _resource_ctx = thread_context()->resource_ctx();
- }
- return _thread_token->submit_func([this, func =
std::forward<Func>(func)]() {
- SCOPED_ATTACH_TASK(_resource_ctx);
- auto st = func();
- if (!st.ok()) {
- std::lock_guard wlock(_lock);
- if (_status.ok()) {
- _status = st;
- }
- }
- });
+ RETURN_IF_ERROR(_get_status());
+ auto resource_ctx = thread_context()->resource_ctx();
+ return _thread_token->submit_func(
Review Comment:
[P2] Preserve the published cancellation across the submit race
`_get_status()` and the underlying token submission are not atomic. A close
thread can observe OK here, then `LoadChannel::cancel()` can publish
`CANCELLED` and quiesce this token before `ThreadPool::do_submit()` acquires
the pool lock. `do_submit()` then returns synthesized `SERVICE_UNAVAILABLE`
(`token was shut down`) instead of the coordinator's first cancellation status,
and local/cloud close surfaces that misleading error without remapping it.
Please re-read `_get_status()` after a failed underlying submit and prefer a
now-published wrapper/coordinator status, while retaining a genuine pool error
when no cancellation was published, and cover this exact barrier-driven
interleaving.
##########
be/src/storage/delete/calc_delete_bitmap_executor.cpp:
##########
@@ -29,22 +29,63 @@
namespace doris {
using namespace ErrorCode;
+void DeleteBitmapCancellation::_register_token(const
std::shared_ptr<ThreadPoolToken>& token) {
+ {
+ std::lock_guard lock(_lock);
+ if (_status.ok()) {
+ _tokens.emplace_back(token);
+ return;
+ }
+ }
+ // A token created after cancellation must reject submissions too.
+ token->shutdown();
+}
+
+void DeleteBitmapCancellation::cancel(const Status& reason) {
+ DCHECK(!reason.ok());
+ std::vector<std::shared_ptr<ThreadPoolToken>> tokens;
+ {
+ std::lock_guard lock(_lock);
+ _status.update(reason);
+ for (const auto& weak_token : _tokens) {
+ if (auto token = weak_token.lock()) {
+ tokens.push_back(std::move(token));
+ }
+ }
+ }
+ // Publish to all tasks before waiting. Neither registration nor task
completion
+ // needs to wait for this lock while shutdown waits for running tasks.
+ for (const auto& token : tokens) {
Review Comment:
[P1] Avoid one pool-wide queue scan per tablet token
`ThreadPoolToken::shutdown()` scans the executor's entire shared `_queue` to
erase that token. Calling it serially for every registered writer/builder token
makes cancellation O(T*Q), and quadratic when a large MOW load has queued work
on T tablet tokens - the exact overload case this PR is meant to unblock.
Unrelated loads' entries are scanned once per cancelled token too, and each
scan holds the shared executor mutex, delaying their submissions and dispatch.
Please provide a batched quiesce/unlink mechanism grouped by underlying pool so
each queue is traversed once before waiting for running callbacks, and add a
many-token cancellation test that includes unrelated queued work.
##########
be/test/load/delta_writer/delta_writer_cancel_test.cpp:
##########
@@ -0,0 +1,453 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <gtest/gtest.h>
+
+#include <atomic>
+#include <chrono>
+#include <future>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include "cloud/cloud_delta_writer.h"
+#include "cloud/cloud_rowset_builder.h"
+#include "cloud/cloud_rowset_writer.h"
+#include "cloud/cloud_storage_engine.h"
+#include "cloud/cloud_tablets_channel.h"
+#include "load/channel/load_channel_mgr.h"
+#include "load/channel/tablets_channel.h"
+#include "load/delta_writer/delta_writer.h"
+#include "runtime/exec_env.h"
+#include "runtime/fragment_mgr.h"
+#include "runtime/memory/mem_tracker_limiter.h"
+#include "runtime/thread_context.h"
+#include "storage/delete/calc_delete_bitmap_executor.h"
+#include "storage/options.h"
+#include "storage/rowset/beta_rowset_writer.h"
+#include "storage/rowset/group_rowset_writer.h"
+#include "storage/rowset_builder.h"
+#include "storage/storage_engine.h"
+#include "util/countdown_latch.h"
+
+namespace doris {
+
+// Exercise both local/cloud writers, with and without row binlog, without
tablet I/O.
+class DeltaWriterCancelTest : public testing::TestWithParam<int> {
+protected:
+ void SetUp() override {
+ auto tracker =
MemTrackerLimiter::create_shared(MemTrackerLimiter::Type::OTHER,
+
"DeltaWriterCancelTest");
+ _attach_task = std::make_unique<AttachTask>(tracker);
+ ASSERT_TRUE(ThreadPoolBuilder("DeltaWriterCancelTest")
+ .set_min_threads(1)
+ .set_max_threads(1)
+ .build(&_pool)
+ .ok());
+ _previous_fragment_mgr = ExecEnv::GetInstance()->_fragment_mgr;
+ _fragment_mgr = std::make_unique<FragmentMgr>(ExecEnv::GetInstance());
+ ExecEnv::GetInstance()->_fragment_mgr = _fragment_mgr.get();
+ _load_channel = std::make_shared<LoadChannel>(UniqueId {}, 60, false,
"", 0, false, -1);
+ WriteRequest data_req;
+ data_req.delete_bitmap_cancellation =
_load_channel->_delete_bitmap_cancellation;
+ WriteRequest group_req = data_req;
+ group_req.write_req_type = WriteRequestType::GROUP;
+ WriteRequest binlog_req = data_req;
+ binlog_req.write_req_type = WriteRequestType::ROW_BINLOG;
+ if (is_cloud()) {
+ _cloud_engine = std::make_unique<CloudStorageEngine>(EngineOptions
{});
+ if (is_group()) {
+ _writer = std::make_unique<CloudDeltaWriter>(*_cloud_engine,
group_req, data_req,
+ binlog_req,
nullptr, UniqueId {});
+ auto* group =
static_cast<CloudGroupRowsetBuilder*>(_writer->_rowset_builder.get());
+ _builders = {group->data_builder(),
group->row_binlog_builder()};
+ } else {
+ _writer = std::make_unique<CloudDeltaWriter>(*_cloud_engine,
data_req, nullptr,
+ UniqueId {});
+ }
+ } else {
+ _local_engine = std::make_unique<StorageEngine>(EngineOptions {});
+ if (is_group()) {
+ _writer = std::make_unique<DeltaWriter>(*_local_engine,
group_req, data_req,
+ binlog_req, nullptr,
UniqueId {});
+ auto* group =
static_cast<GroupRowsetBuilder*>(_writer->_rowset_builder.get());
+ _builders = {group->txn_rowset_builder(),
group->row_binlog_builder()};
+ } else {
+ _writer = std::make_unique<DeltaWriter>(*_local_engine,
data_req, nullptr,
+ UniqueId {});
+ }
+ }
+ if (!is_group()) {
+ _builders = {_writer->_rowset_builder.get()};
+ }
+ }
+
+ void TearDown() override {
+ // Release the unrelated task before destroying writers, even after a
failed assertion.
+ _release_worker.count_down();
+ if (_pool) {
+ _pool->wait();
+ }
+ _writer.reset();
+ _tokens.clear();
+ _pool.reset();
+ _cloud_engine.reset();
+ _local_engine.reset();
+ _load_channel.reset();
+ if (_fragment_mgr) {
+ _fragment_mgr->stop();
+ ExecEnv::GetInstance()->_fragment_mgr = _previous_fragment_mgr;
+ _fragment_mgr.reset();
+ }
+ _attach_task.reset();
+ }
+
+ bool is_cloud() const { return GetParam() & 1; }
+ bool is_group() const { return GetParam() & 2; }
+
+ void install_tokens() {
+ for (auto* builder : _builders) {
+ std::shared_ptr<BaseBetaRowsetWriter> rowset_writer;
+ if (is_cloud()) {
+ rowset_writer =
std::make_shared<CloudRowsetWriter>(*_cloud_engine);
+ } else {
+ rowset_writer =
std::make_shared<BetaRowsetWriter>(*_local_engine);
+ }
+ // Set up only the state needed by cancellation and destruction.
No files are created.
Review Comment:
[P1] Exercise the production wiring and cleanup lifetime
Every parameterized case bypasses the paths changed by this PR:
`install_tokens()` writes private token/writer fields directly, uses a single
fixture pool plus empty metadata, and queues callbacks that only increment a
counter. `TearDown()` also waits the pool before destroying the writer. The
suite therefore remains green if channel -> request -> builder/writer ->
real-executor propagation is removed, or if callback draining moves back after
local file/cloud-cache cleanup. Please keep these interleaving tests, but add a
production-wired local/cloud MOW case that initializes both real phases, blocks
a real writer-capturing callback against an actual temporary segment, and
proves cancellation plus partial-init destruction drains it before file/cache
cleanup.
--
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]