Coverage Report

Created: 2026-08-18 16:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/work_thread_pool.hpp
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
#pragma once
19
20
#include <mutex>
21
22
#include "util/blocking_priority_queue.hpp"
23
#include "util/blocking_queue.hpp"
24
#include "util/thread.h"
25
#include "util/thread_group.h"
26
27
namespace doris {
28
29
// Simple threadpool which processes items (of type T) in parallel which were placed on a
30
// blocking queue by Offer(). Each item is processed by a single user-supplied method.
31
template <bool Priority = false>
32
class WorkThreadPool {
33
public:
34
    // Signature of a work-processing function. Takes the integer id of the thread which is
35
    // calling it (ids run from 0 to num_threads - 1) and a reference to the item to
36
    // process.
37
    using WorkFunction = std::function<void()>;
38
39
    struct Task {
40
    public:
41
        int priority;
42
        WorkFunction work_function;
43
0
        bool operator<(const Task& o) const { return priority < o.priority; }
44
45
420
        Task& operator++() {
46
420
            priority += 2;
47
420
            return *this;
48
420
        }
49
    };
50
51
    using WorkQueue =
52
            std::conditional_t<Priority, BlockingPriorityQueue<Task>, BlockingQueue<Task>>;
53
54
    // Creates a new thread pool and start num_threads threads.
55
    //  -- num_threads: how many threads are part of this pool
56
    //  -- queue_size: the maximum size of the queue on which work items are offered. If the
57
    //     queue exceeds this size, subsequent calls to Offer will block until there is
58
    //     capacity available.
59
    WorkThreadPool(uint32_t num_threads, uint32_t queue_size, const std::string& name)
60
88
            : _work_queue(queue_size), _shutdown(false), _name(name), _active_threads(0) {
61
4.51k
        for (int i = 0; i < num_threads; ++i) {
62
4.42k
            _threads.create_thread(
63
4.42k
                    std::bind<void>(std::mem_fn(&WorkThreadPool::work_thread), this, i));
64
4.42k
        }
65
88
        LOG(INFO) << fmt::format("{} '{}' initialized: num_threads={}, queue_size={}",
66
88
                                 (Priority ? "PriorityThreadPool" : "WorkThreadPool"), _name,
67
88
                                 num_threads, queue_size);
68
88
    }
_ZN5doris14WorkThreadPoolILb1EEC2EjjRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Line
Count
Source
60
38
            : _work_queue(queue_size), _shutdown(false), _name(name), _active_threads(0) {
61
190
        for (int i = 0; i < num_threads; ++i) {
62
152
            _threads.create_thread(
63
152
                    std::bind<void>(std::mem_fn(&WorkThreadPool::work_thread), this, i));
64
152
        }
65
38
        LOG(INFO) << fmt::format("{} '{}' initialized: num_threads={}, queue_size={}",
66
38
                                 (Priority ? "PriorityThreadPool" : "WorkThreadPool"), _name,
67
38
                                 num_threads, queue_size);
68
38
    }
_ZN5doris14WorkThreadPoolILb0EEC2EjjRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Line
Count
Source
60
50
            : _work_queue(queue_size), _shutdown(false), _name(name), _active_threads(0) {
61
4.32k
        for (int i = 0; i < num_threads; ++i) {
62
4.27k
            _threads.create_thread(
63
4.27k
                    std::bind<void>(std::mem_fn(&WorkThreadPool::work_thread), this, i));
64
4.27k
        }
65
50
        LOG(INFO) << fmt::format("{} '{}' initialized: num_threads={}, queue_size={}",
66
50
                                 (Priority ? "PriorityThreadPool" : "WorkThreadPool"), _name,
67
50
                                 num_threads, queue_size);
68
50
    }
69
70
    // Destructor ensures that all threads are terminated before this object is freed
71
    // (otherwise they may continue to run and reference member variables)
72
58
    virtual ~WorkThreadPool() {
73
58
        shutdown();
74
58
        join();
75
58
    }
_ZN5doris14WorkThreadPoolILb1EED2Ev
Line
Count
Source
72
20
    virtual ~WorkThreadPool() {
73
20
        shutdown();
74
20
        join();
75
20
    }
_ZN5doris14WorkThreadPoolILb0EED2Ev
Line
Count
Source
72
38
    virtual ~WorkThreadPool() {
73
38
        shutdown();
74
38
        join();
75
38
    }
76
77
    // Blocking operation that puts a work item on the queue. If the queue is full, blocks
78
    // until there is capacity available.
79
    //
80
    // 'work' is copied into the work queue, but may be referenced at any time in the
81
    // future. Therefore the caller needs to ensure that any data referenced by work (if T
82
    // is, e.g., a pointer type) remains valid until work has been processed, and it's up to
83
    // the caller to provide their own signalling mechanism to detect this (or to wait until
84
    // after DrainAndshutdown returns).
85
    //
86
    // Returns true if the work item was successfully added to the queue, false otherwise
87
    // (which typically means that the thread pool has already been shut down).
88
3
    virtual bool offer(Task task) { return _work_queue.blocking_put(task); }
_ZN5doris14WorkThreadPoolILb1EE5offerENS1_4TaskE
Line
Count
Source
88
3
    virtual bool offer(Task task) { return _work_queue.blocking_put(task); }
Unexecuted instantiation: _ZN5doris14WorkThreadPoolILb0EE5offerENS1_4TaskE
89
90
216k
    virtual bool offer(WorkFunction func) {
91
216k
        WorkThreadPool::Task task = {0, func};
92
216k
        return _work_queue.blocking_put(task);
93
216k
    }
_ZN5doris14WorkThreadPoolILb1EE5offerESt8functionIFvvEE
Line
Count
Source
90
216k
    virtual bool offer(WorkFunction func) {
91
216k
        WorkThreadPool::Task task = {0, func};
92
216k
        return _work_queue.blocking_put(task);
93
216k
    }
Unexecuted instantiation: _ZN5doris14WorkThreadPoolILb0EE5offerESt8functionIFvvEE
94
95
4.45k
    virtual bool try_offer(WorkFunction func) {
96
4.45k
        WorkThreadPool::Task task = {0, func};
97
4.45k
        return _work_queue.try_put(task);
98
4.45k
    }
Unexecuted instantiation: _ZN5doris14WorkThreadPoolILb1EE9try_offerESt8functionIFvvEE
_ZN5doris14WorkThreadPoolILb0EE9try_offerESt8functionIFvvEE
Line
Count
Source
95
4.45k
    virtual bool try_offer(WorkFunction func) {
96
4.45k
        WorkThreadPool::Task task = {0, func};
97
4.45k
        return _work_queue.try_put(task);
98
4.45k
    }
99
100
    // Shuts the thread pool down, causing the work queue to cease accepting offered work
101
    // and the worker threads to terminate once they have processed their current work item.
102
    // Returns once the shutdown flag has been set, does not wait for the threads to
103
    // terminate.
104
71
    virtual void shutdown() {
105
71
        _shutdown = true;
106
71
        _work_queue.shutdown();
107
71
    }
_ZN5doris14WorkThreadPoolILb1EE8shutdownEv
Line
Count
Source
104
33
    virtual void shutdown() {
105
33
        _shutdown = true;
106
33
        _work_queue.shutdown();
107
33
    }
_ZN5doris14WorkThreadPoolILb0EE8shutdownEv
Line
Count
Source
104
38
    virtual void shutdown() {
105
38
        _shutdown = true;
106
38
        _work_queue.shutdown();
107
38
    }
108
109
    // Blocks until all threads are finished. shutdown does not need to have been called,
110
    // since it may be called on a separate thread.
111
59
    virtual void join() { static_cast<void>(_threads.join_all()); }
_ZN5doris14WorkThreadPoolILb1EE4joinEv
Line
Count
Source
111
21
    virtual void join() { static_cast<void>(_threads.join_all()); }
_ZN5doris14WorkThreadPoolILb0EE4joinEv
Line
Count
Source
111
38
    virtual void join() { static_cast<void>(_threads.join_all()); }
112
113
6.70k
    virtual uint32_t get_queue_size() const { return _work_queue.get_size(); }
_ZNK5doris14WorkThreadPoolILb1EE14get_queue_sizeEv
Line
Count
Source
113
273
    virtual uint32_t get_queue_size() const { return _work_queue.get_size(); }
_ZNK5doris14WorkThreadPoolILb0EE14get_queue_sizeEv
Line
Count
Source
113
6.43k
    virtual uint32_t get_queue_size() const { return _work_queue.get_size(); }
114
384
    virtual uint32_t get_active_threads() const { return _active_threads; }
Unexecuted instantiation: _ZNK5doris14WorkThreadPoolILb1EE18get_active_threadsEv
_ZNK5doris14WorkThreadPoolILb0EE18get_active_threadsEv
Line
Count
Source
114
384
    virtual uint32_t get_active_threads() const { return _active_threads; }
115
116
    // Blocks until the work queue is empty, and then calls shutdown to stop the worker
117
    // threads and Join to wait until they are finished.
118
    // Any work Offer()'ed during DrainAndshutdown may or may not be processed.
119
0
    virtual void drain_and_shutdown() {
120
0
        {
121
0
            std::unique_lock l(_lock);
122
0
            while (_work_queue.get_size() != 0) {
123
0
                _empty_cv.wait(l);
124
0
            }
125
0
        }
126
0
        shutdown();
127
0
        join();
128
0
    }
Unexecuted instantiation: _ZN5doris14WorkThreadPoolILb1EE18drain_and_shutdownEv
Unexecuted instantiation: _ZN5doris14WorkThreadPoolILb0EE18drain_and_shutdownEv
129
130
6.31k
    std::string get_info() const {
131
6.31k
        return (Priority ? "PriorityThreadPool" : "FifoThreadPool") +
132
6.31k
               fmt::format(
133
6.31k
                       "(name={}, queue_size={}/{}, active_thread={}/{}, "
134
6.31k
                       "total_get_wait_time={}, total_put_wait_time={}, is_shutdown={})",
135
6.31k
                       _name, get_queue_size(), _work_queue.get_capacity(), _active_threads,
136
6.31k
                       _threads.size(), _work_queue.total_get_wait_time(),
137
6.31k
                       _work_queue.total_put_wait_time(), is_shutdown());
138
6.31k
    }
_ZNK5doris14WorkThreadPoolILb1EE8get_infoB5cxx11Ev
Line
Count
Source
130
273
    std::string get_info() const {
131
273
        return (Priority ? "PriorityThreadPool" : "FifoThreadPool") +
132
273
               fmt::format(
133
273
                       "(name={}, queue_size={}/{}, active_thread={}/{}, "
134
273
                       "total_get_wait_time={}, total_put_wait_time={}, is_shutdown={})",
135
273
                       _name, get_queue_size(), _work_queue.get_capacity(), _active_threads,
136
273
                       _threads.size(), _work_queue.total_get_wait_time(),
137
273
                       _work_queue.total_put_wait_time(), is_shutdown());
138
273
    }
_ZNK5doris14WorkThreadPoolILb0EE8get_infoB5cxx11Ev
Line
Count
Source
130
6.04k
    std::string get_info() const {
131
6.04k
        return (Priority ? "PriorityThreadPool" : "FifoThreadPool") +
132
6.04k
               fmt::format(
133
6.04k
                       "(name={}, queue_size={}/{}, active_thread={}/{}, "
134
6.04k
                       "total_get_wait_time={}, total_put_wait_time={}, is_shutdown={})",
135
6.04k
                       _name, get_queue_size(), _work_queue.get_capacity(), _active_threads,
136
6.04k
                       _threads.size(), _work_queue.total_get_wait_time(),
137
6.04k
                       _work_queue.total_put_wait_time(), is_shutdown());
138
6.04k
    }
139
140
protected:
141
233k
    virtual bool is_shutdown() const { return _shutdown; }
_ZNK5doris14WorkThreadPoolILb1EE11is_shutdownEv
Line
Count
Source
141
217k
    virtual bool is_shutdown() const { return _shutdown; }
_ZNK5doris14WorkThreadPoolILb0EE11is_shutdownEv
Line
Count
Source
141
16.5k
    virtual bool is_shutdown() const { return _shutdown; }
142
143
    // Collection of worker threads that process work from the queue.
144
    ThreadGroup _threads;
145
146
    // Guards _empty_cv
147
    std::mutex _lock;
148
149
    // Signalled when the queue becomes empty
150
    std::condition_variable _empty_cv;
151
152
private:
153
    // Driver method for each thread in the pool. Continues to read work from the queue
154
    // until the pool is shutdown.
155
4.42k
    void work_thread(int thread_id) {
156
4.42k
        Thread::set_self_name(_name);
157
4.42k
        LOG(INFO) << "WorkThreadPool started: " << get_info();
158
229k
        while (!is_shutdown()) {
159
225k
            Task task;
160
225k
            if (_work_queue.blocking_get(&task)) {
161
220k
                _active_threads++;
162
220k
                task.work_function();
163
220k
                _active_threads--;
164
220k
            }
165
225k
            if (_work_queue.get_size() == 0) {
166
6.33k
                _empty_cv.notify_all();
167
6.33k
            }
168
225k
        }
169
        LOG(INFO) << "WorkThreadPool shutdown: " << get_info();
170
4.42k
    }
_ZN5doris14WorkThreadPoolILb1EE11work_threadEi
Line
Count
Source
155
151
    void work_thread(int thread_id) {
156
151
        Thread::set_self_name(_name);
157
151
        LOG(INFO) << "WorkThreadPool started: " << get_info();
158
216k
        while (!is_shutdown()) {
159
216k
            Task task;
160
216k
            if (_work_queue.blocking_get(&task)) {
161
216k
                _active_threads++;
162
216k
                task.work_function();
163
216k
                _active_threads--;
164
216k
            }
165
216k
            if (_work_queue.get_size() == 0) {
166
130
                _empty_cv.notify_all();
167
130
            }
168
216k
        }
169
        LOG(INFO) << "WorkThreadPool shutdown: " << get_info();
170
151
    }
_ZN5doris14WorkThreadPoolILb0EE11work_threadEi
Line
Count
Source
155
4.27k
    void work_thread(int thread_id) {
156
4.27k
        Thread::set_self_name(_name);
157
4.27k
        LOG(INFO) << "WorkThreadPool started: " << get_info();
158
12.9k
        while (!is_shutdown()) {
159
8.71k
            Task task;
160
8.71k
            if (_work_queue.blocking_get(&task)) {
161
4.44k
                _active_threads++;
162
4.44k
                task.work_function();
163
4.44k
                _active_threads--;
164
4.44k
            }
165
8.71k
            if (_work_queue.get_size() == 0) {
166
6.20k
                _empty_cv.notify_all();
167
6.20k
            }
168
8.71k
        }
169
        LOG(INFO) << "WorkThreadPool shutdown: " << get_info();
170
4.27k
    }
171
172
    WorkQueue _work_queue;
173
174
    // Set to true when threads should stop doing work and terminate.
175
    std::atomic<bool> _shutdown;
176
    std::string _name;
177
    std::atomic<int> _active_threads;
178
};
179
180
using PriorityThreadPool = WorkThreadPool<true>;
181
using FifoThreadPool = WorkThreadPool<false>;
182
183
} // namespace doris