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

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


The following commit(s) were added to refs/heads/master by this push:
     new 0ca0f1968d7 [fix](pipeline) Make PipelineTask::set_running an 
unconditional exchange (#67032)
0ca0f1968d7 is described below

commit 0ca0f1968d7e6571d8c3a541bc5f7179282c2caa
Author: Raghvendra Singh <[email protected]>
AuthorDate: Mon Oct 12 09:03:20 2026 +0530

    [fix](pipeline) Make PipelineTask::set_running an unconditional exchange 
(#67032)
    
    ### What problem does this PR solve?
    
    Issue Number: close #67031
    
    Problem Summary:
    
    `PipelineTask::set_running` is the scheduler's double-execute gate
    (`TaskScheduler::_do_work` re-queues a task when it returns true) and,
    via `RevokableTask`'s delegation, the mutual exclusion between a task
    and its in-flight spill revocation. It was implemented as a single
    `compare_exchange_weak` attempt. Weak CAS may fail spuriously on LL/SC
    architectures (aarch64): on a spurious failure with `_running == false`
    the caller is told "was not running" while the flag is never set, so a
    second worker acquires the same task and both execute it concurrently —
    one thread's `reset_hash_table`/`close()` then races the other's
    `sink_impl` on shared local state (production SIGSEGV @0x0 in
    `AggSinkOperatorX::sink_impl` on Graviton BEs, including two workers
    crashing in the same second attached to the same query). x86 compiles
    weak CAS as never-spurious, so this only reproduces on ARM.
    
    `std::atomic::exchange` keeps the exact intended semantics (return the
    old value, always store the new one) and cannot fail spuriously. Every
    other `compare_exchange_weak` in be/src sits in a retry loop; this was
    the only no-retry gate.
    
    Adds `TEST_SET_RUNNING_MUTUAL_EXCLUSION`: 8 threads hammer
    acquire/release and assert at most one concurrent holder (catches the
    old implementation probabilistically on ARM hardware; a hard invariant
    for the new one).
    
    ### Release note
    
    Fix a BE crash on aarch64 where two pipeline workers could execute the
    same task concurrently under spill/memory-pressure churn.
    
    ### Check List (For Author)
    
    - Test
        - [x] Unit Test
    
    - Behavior changed:
        - [x] No.
    
    - Does this need documentation?
        - [x] No.
    
    Co-authored-by: Claude Fable 5 <[email protected]>
---
 be/src/exec/pipeline/pipeline_task.h         | 12 ++++--
 be/test/exec/pipeline/pipeline_task_test.cpp | 58 ++++++++++++++++++++++++++++
 2 files changed, 67 insertions(+), 3 deletions(-)

diff --git a/be/src/exec/pipeline/pipeline_task.h 
b/be/src/exec/pipeline/pipeline_task.h
index dbe5d57f23d..a6e62568c57 100644
--- a/be/src/exec/pipeline/pipeline_task.h
+++ b/be/src/exec/pipeline/pipeline_task.h
@@ -157,9 +157,15 @@ public:
 
     bool is_running() { return _running.load(); }
     virtual bool set_running(bool running) {
-        bool old_value = !running;
-        _running.compare_exchange_weak(old_value, running);
-        return old_value;
+        // This is a mutual-exclusion gate: TaskScheduler::_do_work skips a 
task when
+        // set_running(true) returns true, and RevokableTask delegates here so 
an
+        // in-flight revocation excludes its underlying task. It must be an
+        // unconditional exchange: compare_exchange_weak may fail spuriously 
on LL/SC
+        // architectures (aarch64), and a spurious failure with _running == 
false
+        // returns "was not running" WITHOUT setting the flag — two workers 
then
+        // execute the same task concurrently and race on its local state
+        // (shared hash table / _places).
+        return _running.exchange(running);
     }
 
     virtual RuntimeState* runtime_state() const { return _state; }
diff --git a/be/test/exec/pipeline/pipeline_task_test.cpp 
b/be/test/exec/pipeline/pipeline_task_test.cpp
index cd00dff86d9..34f8df73810 100644
--- a/be/test/exec/pipeline/pipeline_task_test.cpp
+++ b/be/test/exec/pipeline/pipeline_task_test.cpp
@@ -1992,4 +1992,62 @@ TEST_F(PipelineTaskTest, TEST_TERMINATE_RACE_FIX) {
     config::enable_debug_points = false;
 }
 
+TEST_F(PipelineTaskTest, TEST_SET_RUNNING_MUTUAL_EXCLUSION) {
+    auto num_instances = 1;
+    auto pip_id = 0;
+    auto task_id = 0;
+    auto pip = std::make_shared<Pipeline>(pip_id, num_instances, 
num_instances);
+    {
+        OperatorPtr source_op;
+        source_op.reset(new DummyOperator());
+        EXPECT_TRUE(pip->add_operator(source_op, num_instances).ok());
+
+        int op_id = 1;
+        int node_id = 2;
+        int dest_id = 3;
+        DataSinkOperatorPtr sink_op;
+        sink_op.reset(new DummySinkOperatorX(op_id, node_id, dest_id));
+        EXPECT_TRUE(pip->set_sink(sink_op).ok());
+    }
+    auto profile = std::make_shared<RuntimeProfile>("Pipeline : " + 
std::to_string(pip_id));
+    std::map<int,
+             std::pair<std::shared_ptr<BasicSharedState>, 
std::vector<std::shared_ptr<Dependency>>>>
+            shared_state_map;
+    auto task = std::make_shared<PipelineTask>(pip, task_id, 
_runtime_state.get(), _context,
+                                               profile.get(), 
shared_state_map, task_id);
+
+    // set_running(true) is the scheduler's double-execute gate (and 
RevokableTask's mutual
+    // exclusion with its underlying task): at most one caller may observe 
"was not running"
+    // until the matching set_running(false). The former compare_exchange_weak 
implementation
+    // could fail spuriously on LL/SC architectures and report acquisition 
without setting
+    // the flag, admitting two concurrent holders.
+    std::atomic<int> holders {0};
+    std::atomic<int64_t> acquired {0};
+    std::atomic<bool> violation {false};
+    constexpr int kThreads = 8;
+    constexpr int kItersPerThread = 200000;
+    std::vector<std::thread> threads;
+    threads.reserve(kThreads);
+    for (int t = 0; t < kThreads; ++t) {
+        threads.emplace_back([&]() {
+            for (int i = 0; i < kItersPerThread; ++i) {
+                if (!task->set_running(true)) {
+                    if (holders.fetch_add(1) != 0) {
+                        violation = true;
+                    }
+                    acquired.fetch_add(1, std::memory_order_relaxed);
+                    holders.fetch_sub(1);
+                    task->set_running(false);
+                }
+            }
+        });
+    }
+    for (auto& th : threads) {
+        th.join();
+    }
+    EXPECT_FALSE(violation.load()) << "two threads acquired the running gate 
concurrently";
+    EXPECT_GT(acquired.load(), 0);
+    EXPECT_FALSE(task->is_running());
+}
+
 } // namespace doris


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

Reply via email to