Coverage Report

Created: 2024-11-21 23:52

/root/doris/be/src/util/thread.cpp
Line
Count
Source (jump to first uncovered line)
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
// This file is copied from
18
// https://github.com/apache/impala/blob/branch-2.9.0/be/src/util/thread.cc
19
// and modified by Doris
20
21
#include "thread.h"
22
23
#include <sys/resource.h>
24
25
#ifndef __APPLE__
26
// IWYU pragma: no_include <bits/types/struct_sched_param.h>
27
#include <sched.h>
28
#include <sys/prctl.h>
29
#else
30
#include <pthread.h>
31
32
#include <cstdint>
33
#endif
34
35
// IWYU pragma: no_include <bthread/errno.h>
36
#include <errno.h> // IWYU pragma: keep
37
#include <sys/syscall.h>
38
#include <time.h>
39
#include <unistd.h>
40
41
#include <algorithm>
42
// IWYU pragma: no_include <bits/chrono.h>
43
#include <chrono> // IWYU pragma: keep
44
#include <cstring>
45
#include <functional>
46
#include <limits>
47
#include <map>
48
#include <memory>
49
#include <mutex>
50
#include <ostream>
51
#include <string>
52
#include <vector>
53
54
#include "common/config.h"
55
#include "common/logging.h"
56
#include "gutil/atomicops.h"
57
#include "gutil/dynamic_annotations.h"
58
#include "gutil/map-util.h"
59
#include "gutil/stringprintf.h"
60
#include "gutil/strings/substitute.h"
61
#include "http/web_page_handler.h"
62
#include "runtime/thread_context.h"
63
#include "util/debug/sanitizer_scopes.h"
64
#include "util/easy_json.h"
65
#include "util/os_util.h"
66
#include "util/scoped_cleanup.h"
67
#include "util/url_coding.h"
68
69
namespace doris {
70
71
class ThreadMgr;
72
73
__thread Thread* Thread::_tls = nullptr;
74
75
// Singleton instance of ThreadMgr. Only visible in this file, used only by Thread.
76
// // The Thread class adds a reference to thread_manager while it is supervising a thread so
77
// // that a race between the end of the process's main thread (and therefore the destruction
78
// // of thread_manager) and the end of a thread that tries to remove itself from the
79
// // manager after the destruction can be avoided.
80
static std::shared_ptr<ThreadMgr> thread_manager;
81
//
82
// Controls the single (lazy) initialization of thread_manager.
83
static std::once_flag once;
84
85
// A singleton class that tracks all live threads, and groups them together for easy
86
// auditing. Used only by Thread.
87
class ThreadMgr {
88
public:
89
1
    ThreadMgr() : _threads_started_metric(0), _threads_running_metric(0) {}
90
91
0
    ~ThreadMgr() {
92
0
        std::unique_lock<std::mutex> lock(_lock);
93
0
        _thread_categories.clear();
94
0
    }
95
96
    static void set_thread_name(const std::string& name, int64_t tid);
97
98
#ifndef __APPLE__
99
    static void set_idle_sched(int64_t tid);
100
101
    static void set_thread_nice_value(int64_t tid);
102
#endif
103
104
    // not the system TID, since pthread_t is less prone to being recycled.
105
    void add_thread(const pthread_t& pthread_id, const std::string& name,
106
                    const std::string& category, int64_t tid);
107
108
    // Removes a thread from the supplied category. If the thread has
109
    // already been removed, this is a no-op.
110
    void remove_thread(const pthread_t& pthread_id, const std::string& category);
111
112
    void display_thread_callback(const WebPageHandler::ArgumentMap& args, EasyJson* ej) const;
113
114
private:
115
    // Container class for any details we want to capture about a thread
116
    // TODO: Add start-time.
117
    // TODO: Track fragment ID.
118
    class ThreadDescriptor {
119
    public:
120
2.44k
        ThreadDescriptor() {}
121
        ThreadDescriptor(std::string category, std::string name, int64_t thread_id)
122
2.44k
                : _name(std::move(name)), _category(std::move(category)), _thread_id(thread_id) {}
123
124
0
        const std::string& name() const { return _name; }
125
0
        const std::string& category() const { return _category; }
126
0
        int64_t thread_id() const { return _thread_id; }
127
128
    private:
129
        std::string _name;
130
        std::string _category;
131
        int64_t _thread_id;
132
    };
133
134
    void summarize_thread_descriptor(const ThreadDescriptor& desc, EasyJson* ej) const;
135
136
    // A ThreadCategory is a set of threads that are logically related.
137
    // TODO: unordered_map is incompatible with pthread_t, but would be more
138
    // efficient here.
139
    typedef std::map<const pthread_t, ThreadDescriptor> ThreadCategory;
140
141
    // All thread categories, keyed on the category name.
142
    typedef std::map<std::string, ThreadCategory> ThreadCategoryMap;
143
144
    // Protects _thread_categories and thread metrics.
145
    mutable std::mutex _lock;
146
147
    // All thread categories that ever contained a thread, even if empty
148
    ThreadCategoryMap _thread_categories;
149
150
    // Counters to track all-time total number of threads, and the
151
    // current number of running threads.
152
    uint64_t _threads_started_metric;
153
    uint64_t _threads_running_metric;
154
155
    DISALLOW_COPY_AND_ASSIGN(ThreadMgr);
156
};
157
158
2.65k
void ThreadMgr::set_thread_name(const std::string& name, int64_t tid) {
159
2.65k
    if (tid == getpid()) {
160
0
        return;
161
0
    }
162
#ifdef __APPLE__
163
    int err = pthread_setname_np(name.c_str());
164
#else
165
2.65k
    int err = prctl(PR_SET_NAME, name.c_str());
166
2.65k
#endif
167
2.65k
    if (err < 0 && errno != EPERM) {
168
0
        LOG(ERROR) << "set_thread_name";
169
0
    }
170
2.65k
}
171
172
#ifndef __APPLE__
173
0
void ThreadMgr::set_idle_sched(int64_t tid) {
174
0
    if (tid == getpid()) {
175
0
        return;
176
0
    }
177
0
    struct sched_param sp = {.sched_priority = 0};
178
0
    int err = sched_setscheduler(0, SCHED_IDLE, &sp);
179
0
    if (err < 0 && errno != EPERM) {
180
0
        LOG(ERROR) << "set_thread_idle_sched";
181
0
    }
182
0
}
183
184
0
void ThreadMgr::set_thread_nice_value(int64_t tid) {
185
0
    if (tid == getpid()) {
186
0
        return;
187
0
    }
188
    // From Linux kernel:
189
    // In the current implementation, each unit of difference in the nice values of two
190
    // processes results in a factor of 1.25 in the degree to which the
191
    // scheduler favors the higher priority process.  This causes very
192
    // low nice values (+19) to truly provide little CPU to a process
193
    // whenever there is any other higher priority load on the system,
194
    // and makes high nice values (-20) deliver most of the CPU to
195
    // applications that require it (e.g., some audio applications).
196
197
    // Choose 5 as lower priority value, default is 0
198
0
    int err = setpriority(PRIO_PROCESS, 0, config::scan_thread_nice_value);
199
0
    if (err < 0 && errno != EPERM) {
200
0
        LOG(ERROR) << "set_thread_low_priority";
201
0
    }
202
0
}
203
#endif
204
205
void ThreadMgr::add_thread(const pthread_t& pthread_id, const std::string& name,
206
2.43k
                           const std::string& category, int64_t tid) {
207
    // These annotations cause TSAN to ignore the synchronization on lock_
208
    // without causing the subsequent mutations to be treated as data races
209
    // in and of themselves (that's what IGNORE_READS_AND_WRITES does).
210
    //
211
    // Why do we need them here and in SuperviseThread()? TSAN operates by
212
    // observing synchronization events and using them to establish "happens
213
    // before" relationships between threads. Where these relationships are
214
    // not built, shared state access constitutes a data race. The
215
    // synchronization events here, in RemoveThread(), and in
216
    // SuperviseThread() may cause TSAN to establish a "happens before"
217
    // relationship between thread functors, ignoring potential data races.
218
    // The annotations prevent this from happening.
219
2.43k
    ANNOTATE_IGNORE_SYNC_BEGIN();
220
2.43k
    debug::ScopedTSANIgnoreReadsAndWrites ignore_tsan;
221
2.43k
    {
222
2.43k
        std::unique_lock<std::mutex> l(_lock);
223
2.43k
        _thread_categories[category][pthread_id] = ThreadDescriptor(category, name, tid);
224
2.43k
        _threads_running_metric++;
225
2.43k
        _threads_started_metric++;
226
2.43k
    }
227
2.43k
    ANNOTATE_IGNORE_SYNC_END();
228
2.43k
}
229
230
2.42k
void ThreadMgr::remove_thread(const pthread_t& pthread_id, const std::string& category) {
231
2.42k
    ANNOTATE_IGNORE_SYNC_BEGIN();
232
2.42k
    debug::ScopedTSANIgnoreReadsAndWrites ignore_tsan;
233
2.42k
    {
234
2.42k
        std::unique_lock<std::mutex> l(_lock);
235
2.42k
        auto category_it = _thread_categories.find(category);
236
2.42k
        DCHECK(category_it != _thread_categories.end());
237
2.42k
        category_it->second.erase(pthread_id);
238
2.42k
        _threads_running_metric--;
239
2.42k
    }
240
2.42k
    ANNOTATE_IGNORE_SYNC_END();
241
2.42k
}
242
243
void ThreadMgr::display_thread_callback(const WebPageHandler::ArgumentMap& args,
244
0
                                        EasyJson* ej) const {
245
0
    const auto* category_name = FindOrNull(args, "group");
246
0
    if (category_name) {
247
0
        bool requested_all = (*category_name == "all");
248
0
        ej->Set("requested_thread_group", EasyJson::kObject);
249
0
        (*ej)["group_name"] = escape_for_html_to_string(*category_name);
250
0
        (*ej)["requested_all"] = requested_all;
251
252
        // The critical section is as short as possible so as to minimize the delay
253
        // imposed on new threads that acquire the lock in write mode.
254
0
        std::vector<ThreadDescriptor> descriptors_to_print;
255
0
        if (!requested_all) {
256
0
            std::unique_lock<std::mutex> l(_lock);
257
0
            const auto* category = FindOrNull(_thread_categories, *category_name);
258
0
            if (!category) {
259
0
                return;
260
0
            }
261
0
            for (const auto& elem : *category) {
262
0
                descriptors_to_print.emplace_back(elem.second);
263
0
            }
264
0
        } else {
265
0
            std::unique_lock<std::mutex> l(_lock);
266
0
            for (const auto& category : _thread_categories) {
267
0
                for (const auto& elem : category.second) {
268
0
                    descriptors_to_print.emplace_back(elem.second);
269
0
                }
270
0
            }
271
0
        }
272
273
0
        EasyJson found = (*ej).Set("found", EasyJson::kObject);
274
0
        EasyJson threads = found.Set("threads", EasyJson::kArray);
275
0
        for (const auto& desc : descriptors_to_print) {
276
0
            summarize_thread_descriptor(desc, &threads);
277
0
        }
278
0
    } else {
279
        // List all thread groups and the number of threads running in each.
280
0
        std::vector<std::pair<string, uint64_t>> thread_categories_info;
281
0
        uint64_t running;
282
0
        {
283
0
            std::unique_lock<std::mutex> l(_lock);
284
0
            running = _threads_running_metric;
285
0
            thread_categories_info.reserve(_thread_categories.size());
286
0
            for (const auto& category : _thread_categories) {
287
0
                thread_categories_info.emplace_back(category.first, category.second.size());
288
0
            }
289
290
0
            (*ej)["total_threads_running"] = running;
291
0
            EasyJson groups = ej->Set("groups", EasyJson::kArray);
292
0
            for (const auto& elem : thread_categories_info) {
293
0
                string category_arg;
294
0
                url_encode(elem.first, &category_arg);
295
0
                EasyJson group = groups.PushBack(EasyJson::kObject);
296
0
                group["encoded_group_name"] = category_arg;
297
0
                group["group_name"] = elem.first;
298
0
                group["threads_running"] = elem.second;
299
0
            }
300
0
        }
301
0
    }
302
0
}
303
304
void ThreadMgr::summarize_thread_descriptor(const ThreadMgr::ThreadDescriptor& desc,
305
0
                                            EasyJson* ej) const {
306
0
    ThreadStats stats;
307
0
    Status status = get_thread_stats(desc.thread_id(), &stats);
308
0
    if (!status.ok()) {
309
0
        LOG(WARNING) << "Could not get per-thread statistics: " << status.to_string();
310
0
    }
311
312
0
    EasyJson thread = ej->PushBack(EasyJson::kObject);
313
0
    thread["thread_name"] = desc.name();
314
0
    thread["user_sec"] = static_cast<double>(stats.user_ns) / 1e9;
315
0
    thread["kernel_sec"] = static_cast<double>(stats.kernel_ns) / 1e9;
316
0
    thread["iowait_sec"] = static_cast<double>(stats.iowait_ns) / 1e9;
317
0
}
318
319
2.42k
Thread::~Thread() {
320
2.42k
    if (_joinable) {
321
1.41k
        int ret = pthread_detach(_thread);
322
1.41k
        CHECK_EQ(ret, 0);
323
1.41k
    }
324
2.42k
}
325
326
214
void Thread::set_self_name(const std::string& name) {
327
214
    ThreadMgr::set_thread_name(name, current_thread_id());
328
214
}
329
330
#ifndef __APPLE__
331
0
void Thread::set_idle_sched() {
332
0
    ThreadMgr::set_idle_sched(current_thread_id());
333
0
}
334
335
0
void Thread::set_thread_nice_value() {
336
0
    ThreadMgr::set_thread_nice_value(current_thread_id());
337
0
}
338
#endif
339
340
1.01k
void Thread::join() {
341
1.01k
    static_cast<void>(ThreadJoiner(this).join());
342
1.01k
}
343
344
1.00k
int64_t Thread::tid() const {
345
1.00k
    int64_t t = base::subtle::Acquire_Load(&_tid);
346
1.00k
    if (t != PARENT_WAITING_TID) {
347
1.00k
        return _tid;
348
1.00k
    }
349
1
    return wait_for_tid();
350
1.00k
}
351
352
0
pthread_t Thread::pthread_id() const {
353
0
    return _thread;
354
0
}
355
356
2.43k
const std::string& Thread::name() const {
357
2.43k
    return _name;
358
2.43k
}
359
360
4.86k
const std::string& Thread::category() const {
361
4.86k
    return _category;
362
4.86k
}
363
364
0
std::string Thread::to_string() const {
365
0
    return strings::Substitute("Thread $0 (name: \"$1\", category: \"$2\")", tid(), _name,
366
0
                               _category);
367
0
}
368
369
8.63k
Thread* Thread::current_thread() {
370
8.63k
    return _tls;
371
8.63k
}
372
373
0
int64_t Thread::unique_thread_id() {
374
#ifdef __APPLE__
375
    uint64_t tid;
376
    pthread_threadid_np(pthread_self(), &tid);
377
    return tid;
378
#else
379
0
    return static_cast<int64_t>(pthread_self());
380
0
#endif
381
0
}
382
383
2.65k
int64_t Thread::current_thread_id() {
384
#ifdef __APPLE__
385
    uint64_t tid;
386
    pthread_threadid_np(nullptr, &tid);
387
    return tid;
388
#else
389
2.65k
    return syscall(SYS_gettid);
390
2.65k
#endif
391
2.65k
}
392
393
1
int64_t Thread::wait_for_tid() const {
394
1
    int loop_count = 0;
395
42
    while (true) {
396
42
        int64_t t = Acquire_Load(&_tid);
397
42
        if (t != PARENT_WAITING_TID) {
398
1
            return t;
399
1
        }
400
        // copied from boost::detail::yield
401
41
        int k = loop_count++;
402
41
        if (k < 32 || k & 1) {
403
36
            sched_yield();
404
36
        } else {
405
            // g++ -Wextra warns on {} or {0}
406
5
            struct timespec rqtp = {0, 0};
407
408
            // POSIX says that timespec has tv_sec and tv_nsec
409
            // But it doesn't guarantee order or placement
410
411
5
            rqtp.tv_sec = 0;
412
5
            rqtp.tv_nsec = 1000;
413
414
5
            nanosleep(&rqtp, 0);
415
5
        }
416
41
    }
417
1
}
418
419
Status Thread::start_thread(const std::string& category, const std::string& name,
420
                            const ThreadFunctor& functor, uint64_t flags,
421
2.44k
                            scoped_refptr<Thread>* holder) {
422
2.44k
    std::call_once(once, init_threadmgr);
423
424
    // Temporary reference for the duration of this function.
425
2.44k
    scoped_refptr<Thread> t(new Thread(category, name, functor));
426
427
    // Optional, and only set if the thread was successfully created.
428
    //
429
    // We have to set this before we even start the thread because it's
430
    // allowed for the thread functor to access 'holder'.
431
2.44k
    if (holder) {
432
1.03k
        *holder = t;
433
1.03k
    }
434
435
2.44k
    t->_tid = PARENT_WAITING_TID;
436
437
    // Add a reference count to the thread since SuperviseThread() needs to
438
    // access the thread object, and we have no guarantee that our caller
439
    // won't drop the reference as soon as we return. This is dereferenced
440
    // in FinishThread().
441
2.44k
    t->AddRef();
442
443
2.44k
    auto cleanup = MakeScopedCleanup([&]() {
444
        // If we failed to create the thread, we need to undo all of our prep work.
445
0
        t->_tid = INVALID_TID;
446
0
        t->Release();
447
0
    });
448
449
2.44k
    int ret = pthread_create(&t->_thread, nullptr, &Thread::supervise_thread, t.get());
450
2.44k
    if (ret) {
451
0
        return Status::RuntimeError("Could not create thread. (error {}) {}", ret, strerror(ret));
452
0
    }
453
454
    // The thread has been created and is now joinable.
455
    //
456
    // Why set this in the parent and not the child? Because only the parent
457
    // (or someone communicating with the parent) can join, so joinable must
458
    // be set before the parent returns.
459
2.44k
    t->_joinable = true;
460
2.44k
    cleanup.cancel();
461
462
2.44k
    VLOG_NOTICE << "Started thread " << t->tid() << " - " << category << ":" << name;
463
2.44k
    return Status::OK();
464
2.44k
}
465
466
2.43k
void* Thread::supervise_thread(void* arg) {
467
2.43k
    Thread* t = static_cast<Thread*>(arg);
468
2.43k
    int64_t system_tid = Thread::current_thread_id();
469
2.43k
    PCHECK(system_tid != -1);
470
471
    // Take an additional reference to the thread manager, which we'll need below.
472
2.43k
    ANNOTATE_IGNORE_SYNC_BEGIN();
473
2.43k
    std::shared_ptr<ThreadMgr> thread_mgr_ref = thread_manager;
474
2.43k
    ANNOTATE_IGNORE_SYNC_END();
475
476
    // Set up the TLS.
477
    //
478
    // We could store a scoped_refptr in the TLS itself, but as its
479
    // lifecycle is poorly defined, we'll use a bare pointer. We
480
    // already incremented the reference count in StartThread.
481
2.43k
    Thread::_tls = t;
482
483
    // Create thread context, there is no need to create it when func is executed.
484
2.43k
    ThreadLocalHandle::create_thread_local_if_not_exits();
485
486
    // Publish our tid to '_tid', which unblocks any callers waiting in
487
    // WaitForTid().
488
2.43k
    Release_Store(&t->_tid, system_tid);
489
490
2.43k
    std::string name = strings::Substitute("$0-$1", t->name(), system_tid);
491
2.43k
    thread_manager->set_thread_name(name, t->_tid);
492
2.43k
    thread_manager->add_thread(pthread_self(), name, t->category(), t->_tid);
493
494
    // FinishThread() is guaranteed to run (even if functor_ throws an
495
    // exception) because pthread_cleanup_push() creates a scoped object
496
    // whose destructor invokes the provided callback.
497
2.43k
    pthread_cleanup_push(&Thread::finish_thread, t);
498
2.43k
    t->_functor();
499
2.43k
    pthread_cleanup_pop(true);
500
501
2.43k
    return nullptr;
502
2.43k
}
503
504
2.42k
void Thread::finish_thread(void* arg) {
505
2.42k
    Thread* t = static_cast<Thread*>(arg);
506
507
    // We're here either because of the explicit pthread_cleanup_pop() in
508
    // SuperviseThread() or through pthread_exit(). In either case,
509
    // thread_manager is guaranteed to be live because thread_mgr_ref in
510
    // SuperviseThread() is still live.
511
2.42k
    thread_manager->remove_thread(pthread_self(), t->category());
512
513
    // Signal any Joiner that we're done.
514
2.42k
    t->_done.count_down();
515
516
2.42k
    VLOG_CRITICAL << "Ended thread " << t->_tid << " - " << t->category() << ":" << t->name();
517
2.42k
    t->Release();
518
    // NOTE: the above 'Release' call could be the last reference to 'this',
519
    // so 'this' could be destructed at this point. Do not add any code
520
    // following here!
521
522
2.42k
    ThreadLocalHandle::del_thread_local_if_count_is_zero();
523
2.42k
}
524
525
1
void Thread::init_threadmgr() {
526
1
    thread_manager.reset(new ThreadMgr());
527
1
}
528
529
ThreadJoiner::ThreadJoiner(Thread* thr)
530
        : _thread(CHECK_NOTNULL(thr)),
531
          _warn_after_ms(kDefaultWarnAfterMs),
532
          _warn_every_ms(kDefaultWarnEveryMs),
533
1.01k
          _give_up_after_ms(kDefaultGiveUpAfterMs) {}
534
535
1
ThreadJoiner& ThreadJoiner::warn_after_ms(int ms) {
536
1
    _warn_after_ms = ms;
537
1
    return *this;
538
1
}
539
540
1
ThreadJoiner& ThreadJoiner::warn_every_ms(int ms) {
541
1
    _warn_every_ms = ms;
542
1
    return *this;
543
1
}
544
545
1
ThreadJoiner& ThreadJoiner::give_up_after_ms(int ms) {
546
1
    _give_up_after_ms = ms;
547
1
    return *this;
548
1
}
549
550
1.02k
Status ThreadJoiner::join() {
551
1.02k
    if (Thread::current_thread() && Thread::current_thread()->tid() == _thread->tid()) {
552
1
        return Status::InvalidArgument("Can't join on own thread. (error {}) {}", -1,
553
1
                                       _thread->_name);
554
1
    }
555
556
    // Early exit: double join is a no-op.
557
1.01k
    if (!_thread->_joinable) {
558
1
        return Status::OK();
559
1
    }
560
561
1.01k
    int waited_ms = 0;
562
1.01k
    bool keep_trying = true;
563
1.02k
    while (keep_trying) {
564
1.02k
        if (waited_ms >= _warn_after_ms) {
565
10
            LOG(WARNING) << strings::Substitute("Waited for $0ms trying to join with $1 (tid $2)",
566
10
                                                waited_ms, _thread->_name, _thread->_tid);
567
10
        }
568
569
1.02k
        int remaining_before_giveup = std::numeric_limits<int>::max();
570
1.02k
        if (_give_up_after_ms != -1) {
571
1
            remaining_before_giveup = _give_up_after_ms - waited_ms;
572
1
        }
573
574
1.02k
        int remaining_before_next_warn = _warn_every_ms;
575
1.02k
        if (waited_ms < _warn_after_ms) {
576
1.01k
            remaining_before_next_warn = _warn_after_ms - waited_ms;
577
1.01k
        }
578
579
1.02k
        if (remaining_before_giveup < remaining_before_next_warn) {
580
1
            keep_trying = false;
581
1
        }
582
583
1.02k
        int wait_for = std::min(remaining_before_giveup, remaining_before_next_warn);
584
585
1.02k
        if (_thread->_done.wait_for(std::chrono::milliseconds(wait_for))) {
586
            // Unconditionally join before returning, to guarantee that any TLS
587
            // has been destroyed (pthread_key_create() destructors only run
588
            // after a pthread's user method has returned).
589
1.01k
            int ret = pthread_join(_thread->_thread, nullptr);
590
1.01k
            CHECK_EQ(ret, 0);
591
1.01k
            _thread->_joinable = false;
592
1.01k
            return Status::OK();
593
1.01k
        }
594
11
        waited_ms += wait_for;
595
11
    }
596
1
    return Status::Aborted("Timed out after {}ms joining on {}", waited_ms, _thread->_name);
597
1.01k
}
598
599
1
void register_thread_display_page(WebPageHandler* web_page_handler) {
600
1
    web_page_handler->register_template_page(
601
1
            "/threadz", "Threads",
602
1
            std::bind(&ThreadMgr::display_thread_callback, thread_manager.get(),
603
1
                      std::placeholders::_1, std::placeholders::_2),
604
1
            true);
605
1
}
606
} // namespace doris