Coverage Report

Created: 2026-08-06 18:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/runtime/thread_context.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "runtime/thread_context.h"
19
20
#include "common/signal_handler.h"
21
#include "runtime/exec_env.h"
22
#include "runtime/query_context.h"
23
#include "runtime/runtime_state.h"
24
#include "runtime/workload_management/resource_context.h"
25
26
namespace doris {
27
class MemTracker;
28
29
557
void ThreadContext::attach_task(const std::shared_ptr<ResourceContext>& rc) {
30
    // will only attach_task at the beginning of the thread function, there should be no duplicate attach_task.
31
557
    DCHECK(resource_ctx_ == nullptr);
32
    // Validation of `rc` and its sub-objects is performed by the
33
    // AttachTask::init() / SwitchResourceContext constructor entry
34
    // points before any thread-local or signal mutation, so a thrown
35
    // FatalError does not leak thread-local handle counts or leave a
36
    // stale signal task id behind.
37
557
    resource_ctx_ = rc;
38
557
    thread_mem_tracker_mgr->attach_limiter_tracker(rc->memory_context()->mem_tracker(),
39
557
                                                   rc->workload_group());
40
557
    thread_mem_tracker_mgr->enable_wait_gc();
41
557
}
42
43
263
std::shared_ptr<ResourceContext> ThreadContext::_make_orphan_resource_ctx() {
44
263
    auto ctx = ResourceContext::create_shared();
45
263
    ctx->memory_context()->set_mem_tracker(ExecEnv::GetInstance()->orphan_mem_tracker());
46
263
    return ctx;
47
263
}
48
49
549
void AttachTask::init(const std::shared_ptr<ResourceContext>& rc) {
50
    // Validate the ResourceContext chain before mutating any thread-local
51
    // or signal state. If any link is null we throw immediately, so the
52
    // caller's stack-unwind sees a clean state (no thread-local handle is
53
    // acquired and no signal task id is set). Without these the previous
54
    // code would silently propagate a null mem_tracker into
55
    // ThreadMemTrackerMgr and crash much later inside the allocator.
56
549
    if (UNLIKELY(rc == nullptr)) {
57
0
        throw Exception(
58
0
                Status::FatalError("AttachTask::init: rc is null. signal_query_id={:x}-{:x}",
59
0
                                   signal::query_id_hi, signal::query_id_lo));
60
0
    }
61
549
    if (UNLIKELY(rc->memory_context() == nullptr)) {
62
0
        throw Exception(Status::FatalError(
63
0
                "AttachTask::init: rc->memory_context() is null. signal_query_id={:x}-{:x}",
64
0
                signal::query_id_hi, signal::query_id_lo));
65
0
    }
66
549
    if (UNLIKELY(rc->memory_context()->mem_tracker() == nullptr)) {
67
0
        throw Exception(Status::FatalError(
68
0
                "AttachTask::init: rc->memory_context()->mem_tracker() is null. "
69
0
                "ResourceContext was created but set_mem_tracker has not been called yet "
70
0
                "(likely a half-initialized QueryContext used before _init_query_mem_tracker). "
71
0
                "signal_query_id={:x}-{:x}",
72
0
                signal::query_id_hi, signal::query_id_lo));
73
0
    }
74
549
    if (UNLIKELY(rc->task_controller() == nullptr)) {
75
0
        throw Exception(Status::FatalError(
76
0
                "AttachTask::init: rc->task_controller() is null. signal_query_id={:x}-{:x}",
77
0
                signal::query_id_hi, signal::query_id_lo));
78
0
    }
79
549
    ThreadLocalHandle::create_thread_local_if_not_exits();
80
549
    signal::set_signal_task_id(rc->task_controller()->task_id());
81
549
    thread_context()->attach_task(rc);
82
549
}
83
84
212
AttachTask::AttachTask(const std::shared_ptr<ResourceContext>& rc) {
85
212
    init(rc);
86
212
}
87
88
261
AttachTask::AttachTask(const std::shared_ptr<MemTrackerLimiter>& mem_tracker) {
89
    // if parameter is `orphan_mem_tracker`, if you do not switch thraed mem tracker afterwards,
90
    // alloc or free memory from Allocator will fail DCHECK. unless you know for sure that
91
    // the thread will not alloc or free memory from Allocator later.
92
    //
93
    // Validate before constructing the ResourceContext: MemoryContext::set_mem_tracker()
94
    // immediately dereferences mem_tracker->limit(), so a null shared_ptr would
95
    // crash there before reaching init()'s diagnostics.
96
261
    if (UNLIKELY(mem_tracker == nullptr)) {
97
0
        throw Exception(Status::FatalError(
98
0
                "AttachTask(MemTrackerLimiter): mem_tracker is null. signal_query_id={:x}-{:x}",
99
0
                signal::query_id_hi, signal::query_id_lo));
100
0
    }
101
261
    std::shared_ptr<ResourceContext> rc = ResourceContext::create_shared();
102
261
    rc->memory_context()->set_mem_tracker(mem_tracker);
103
261
    init(rc);
104
261
}
105
106
74
AttachTask::AttachTask(RuntimeState* runtime_state) {
107
    // Walk the chain `runtime_state -> get_query_ctx() -> resource_ctx()`
108
    // step by step so that an unexpected null pinpoints exactly which link
109
    // failed instead of crashing with a generic NPE inside attach_task() or
110
    // even later inside the allocator.
111
74
    if (UNLIKELY(runtime_state == nullptr)) {
112
0
        throw Exception(Status::FatalError(
113
0
                "AttachTask(RuntimeState*): runtime_state is null. signal_query_id={:x}-{:x}",
114
0
                signal::query_id_hi, signal::query_id_lo));
115
0
    }
116
74
    if (UNLIKELY(runtime_state->get_query_ctx() == nullptr)) {
117
0
        throw Exception(Status::FatalError(
118
0
                "AttachTask(RuntimeState*): runtime_state->get_query_ctx() is null. "
119
0
                "signal_query_id={:x}-{:x}",
120
0
                signal::query_id_hi, signal::query_id_lo));
121
0
    }
122
74
    if (UNLIKELY(runtime_state->get_query_ctx()->resource_ctx() == nullptr)) {
123
0
        throw Exception(
124
0
                Status::FatalError("AttachTask(RuntimeState*): query_ctx->resource_ctx() is null. "
125
0
                                   "signal_query_id={:x}-{:x}",
126
0
                                   signal::query_id_hi, signal::query_id_lo));
127
0
    }
128
74
    signal::set_signal_is_nereids(runtime_state->is_nereids());
129
74
    init(runtime_state->get_query_ctx()->resource_ctx());
130
74
}
131
132
2
AttachTask::AttachTask(QueryContext* query_ctx) {
133
2
    if (UNLIKELY(query_ctx == nullptr)) {
134
0
        throw Exception(Status::FatalError(
135
0
                "AttachTask(QueryContext*): query_ctx is null. signal_query_id={:x}-{:x}",
136
0
                signal::query_id_hi, signal::query_id_lo));
137
0
    }
138
2
    init(query_ctx->resource_ctx());
139
2
}
140
141
549
AttachTask::~AttachTask() {
142
549
    signal::set_signal_task_id(TUniqueId());
143
549
    thread_context()->detach_task();
144
549
    ThreadLocalHandle::del_thread_local_if_count_is_zero();
145
549
}
146
147
5
SwitchResourceContext::SwitchResourceContext(const std::shared_ptr<ResourceContext>& rc) {
148
5
    DCHECK(rc != nullptr);
149
    // Validate the chain before mutating any thread-local or signal state,
150
    // symmetric to AttachTask::init(). Throwing after the thread-local
151
    // handle was acquired or the signal task id was set would skip this
152
    // object's destructor (because construction failed) and leak the
153
    // handle / leave a stale signal task id behind.
154
5
    if (UNLIKELY(rc == nullptr)) {
155
0
        throw Exception(
156
0
                Status::FatalError("SwitchResourceContext: rc is null. signal_query_id={:x}-{:x}",
157
0
                                   signal::query_id_hi, signal::query_id_lo));
158
0
    }
159
5
    if (UNLIKELY(rc->memory_context() == nullptr)) {
160
0
        throw Exception(Status::FatalError(
161
0
                "SwitchResourceContext: rc->memory_context() is null. signal_query_id={:x}-{:x}",
162
0
                signal::query_id_hi, signal::query_id_lo));
163
0
    }
164
5
    if (UNLIKELY(rc->memory_context()->mem_tracker() == nullptr)) {
165
0
        throw Exception(Status::FatalError(
166
0
                "SwitchResourceContext: rc->memory_context()->mem_tracker() is null. "
167
0
                "ResourceContext was switched in before _init_query_mem_tracker ran. "
168
0
                "signal_query_id={:x}-{:x}",
169
0
                signal::query_id_hi, signal::query_id_lo));
170
0
    }
171
5
    if (UNLIKELY(rc->task_controller() == nullptr)) {
172
0
        throw Exception(Status::FatalError(
173
0
                "SwitchResourceContext: rc->task_controller() is null. signal_query_id={:x}-{:x}",
174
0
                signal::query_id_hi, signal::query_id_lo));
175
0
    }
176
5
    doris::ThreadLocalHandle::create_thread_local_if_not_exits();
177
5
    DCHECK(thread_context()->is_attach_task());
178
5
    old_resource_ctx_ = thread_context()->resource_ctx();
179
5
    if (rc != old_resource_ctx_) {
180
4
        signal::set_signal_task_id(rc->task_controller()->task_id());
181
4
        thread_context()->resource_ctx_ = rc;
182
4
        thread_context()->thread_mem_tracker_mgr->attach_limiter_tracker(
183
4
                rc->memory_context()->mem_tracker(), rc->workload_group());
184
4
    }
185
5
}
186
187
5
SwitchResourceContext::~SwitchResourceContext() {
188
5
    if (old_resource_ctx_ != thread_context()->resource_ctx()) {
189
4
        DCHECK(old_resource_ctx_ != nullptr);
190
4
        signal::set_signal_task_id(old_resource_ctx_->task_controller()->task_id());
191
4
        thread_context()->resource_ctx_ = old_resource_ctx_;
192
4
        thread_context()->thread_mem_tracker_mgr->detach_limiter_tracker();
193
4
    }
194
5
    doris::ThreadLocalHandle::del_thread_local_if_count_is_zero();
195
5
}
196
197
SwitchThreadMemTrackerLimiter::SwitchThreadMemTrackerLimiter(
198
710k
        const std::shared_ptr<doris::MemTrackerLimiter>& mem_tracker) {
199
710k
    DCHECK(mem_tracker);
200
    // Third entry point that calls attach_limiter_tracker(). Without this
201
    // null guard a null mem_tracker silently propagates and the next
202
    // allocation on this thread would NPE deep inside the allocator. Throw
203
    // before acquiring the thread-local handle / doing any side effect so
204
    // the destructor (which is noexcept) never runs in a dirty state.
205
710k
    if (UNLIKELY(mem_tracker == nullptr)) {
206
0
        throw Exception(Status::FatalError(
207
0
                "SwitchThreadMemTrackerLimiter: mem_tracker is null. signal_query_id={:x}-{:x}",
208
0
                signal::query_id_hi, signal::query_id_lo));
209
0
    }
210
710k
    doris::ThreadLocalHandle::create_thread_local_if_not_exits();
211
710k
    if (mem_tracker != thread_context()->thread_mem_tracker_mgr->limiter_mem_tracker_sptr()) {
212
629k
        thread_context()->thread_mem_tracker_mgr->attach_limiter_tracker(mem_tracker);
213
629k
        is_switched_ = true;
214
629k
    }
215
710k
}
216
217
710k
SwitchThreadMemTrackerLimiter::~SwitchThreadMemTrackerLimiter() {
218
710k
    if (is_switched_) {
219
629k
        thread_context()->thread_mem_tracker_mgr->detach_limiter_tracker();
220
629k
    }
221
710k
    doris::ThreadLocalHandle::del_thread_local_if_count_is_zero();
222
710k
}
223
224
102
AddThreadMemTrackerConsumer::AddThreadMemTrackerConsumer(MemTracker* mem_tracker) {
225
102
    ThreadLocalHandle::create_thread_local_if_not_exits();
226
102
    if (mem_tracker) {
227
102
        _need_pop = thread_context()->thread_mem_tracker_mgr->push_consumer_tracker(mem_tracker);
228
102
    }
229
102
}
230
231
AddThreadMemTrackerConsumer::AddThreadMemTrackerConsumer(
232
        const std::shared_ptr<MemTracker>& mem_tracker)
233
86
        : _mem_tracker(mem_tracker) {
234
86
    ThreadLocalHandle::create_thread_local_if_not_exits();
235
86
    if (_mem_tracker) {
236
86
        _need_pop =
237
86
                thread_context()->thread_mem_tracker_mgr->push_consumer_tracker(_mem_tracker.get());
238
86
    }
239
86
}
240
241
188
AddThreadMemTrackerConsumer::~AddThreadMemTrackerConsumer() {
242
188
    if (_need_pop) {
243
187
        thread_context()->thread_mem_tracker_mgr->pop_consumer_tracker();
244
187
    }
245
188
    ThreadLocalHandle::del_thread_local_if_count_is_zero();
246
188
}
247
248
} // namespace doris