• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 37723301502

07 Oct 2026 01:16PM UTC coverage: 89.903% (-0.01%) from 89.915%
37723301502

push

github

web-flow
Merge pull request #5999 from randombit/jack/base64-avx512

Add a AVX-512 path for base64 encoding and decoding

131000 of 145712 relevant lines covered (89.9%)

9262170.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.02
/src/lib/utils/thread_utils/thread_pool.cpp
1
/*
2
* (C) 2019,2021,2026 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include <botan/internal/thread_pool.h>
8

9
#include <botan/assert.h>
10
#include <botan/exceptn.h>
11
#include <botan/internal/os_utils.h>
12
#include <botan/internal/parsing.h>
13
#include <botan/internal/target_info.h>
14
#include <algorithm>
15
#include <condition_variable>
16
#include <deque>
17
#include <thread>
18
#include <vector>
19

20
namespace Botan {
21

22
namespace {
23

24
/**
25
* The library's own thread pool, which runs the work unless the application
26
* provides an executor
27
*/
28
class Internal_Thread_Pool final : public Work_Executor {
29
   public:
30
      /// Nullopt disables the pool; all work then runs in the calling thread
31
      explicit Internal_Thread_Pool(std::optional<size_t> pool_size) :
1,720 ✔
32
            m_pool_size(resolve_pool_size(pool_size)), m_shutdown(false) {}
3,440 ✔
33

34
      /// The workers drain the queue and exit
35
      ~Internal_Thread_Pool() override {
1,720 ✔
36
         {
1,720 ✔
37
            const std::scoped_lock lock(m_mutex);
1,720 ✔
38
            m_shutdown = true;
1,720 ✔
39
         }
1,720 ✔
40
         m_more_tasks.notify_all();
1,720 ✔
41

42
         for(auto& worker : m_workers) {
5,195 ✔
43
            worker.join();
3,475 ✔
44
         }
45
      }
1,720 ✔
46

47
      Internal_Thread_Pool(const Internal_Thread_Pool&) = delete;
48
      Internal_Thread_Pool& operator=(const Internal_Thread_Pool&) = delete;
49
      Internal_Thread_Pool(Internal_Thread_Pool&&) = delete;
50
      Internal_Thread_Pool& operator=(Internal_Thread_Pool&&) = delete;
51

52
      void queue_work(std::function<void()> work) override;
53

54
      size_t max_concurrency() const override { return m_pool_size; }
1,720 ✔
55

56
   private:
57
      static size_t resolve_pool_size(std::optional<size_t> pool_size);
58

59
      void start_workers();
60
      void worker_thread();
61
      std::function<void()> next_task();
62

63
      const size_t m_pool_size;
64

65
      std::mutex m_mutex;
66
      std::condition_variable m_more_tasks;
67
      std::deque<std::function<void()>> m_tasks;
68
      std::vector<std::thread> m_workers;
69
      bool m_shutdown;
70
};
71

72
//static
73
size_t Internal_Thread_Pool::resolve_pool_size(std::optional<size_t> pool_size) {
1,720 ✔
74
   if(!pool_size.has_value()) {
1,720 ✔
75
      return 0;  // disabled
76
   } else if(pool_size.value() == 0) {
1,718 ✔
77
      // For large machines don't create too many threads, unless explicitly asked
78
      return std::clamp<size_t>(OS::get_cpu_available(), 2, 16);
3,422 ✔
79
   } else {
80
      return pool_size.value();
81
   }
82
}
83

84
void Internal_Thread_Pool::queue_work(std::function<void()> work) {
428,901 ✔
85
   // Work queued by a worker of this pool never arrives here, since the
86
   // Thread_Pool runs it directly
87

88
   if(m_pool_size == 0) {
428,901 ✔
89
      return work();
130 ✔
90
   }
91

92
   const std::scoped_lock lock(m_mutex);
428,771 ✔
93

94
   if(m_workers.empty()) {
428,771 ✔
95
      start_workers();
868 ✔
96
   }
97

98
   m_tasks.push_back(std::move(work));
428,771 ✔
99
   m_more_tasks.notify_one();
428,771 ✔
100
}
428,771 ✔
101

102
// Requires that m_mutex is held
103
void Internal_Thread_Pool::start_workers() {
868 ✔
104
   // On Linux, it is 16 length max, including terminator
105
   const std::string tname = "Botan thread";
868 ✔
106

107
   m_workers.reserve(m_pool_size);
868 ✔
108

109
   // Added one at a time so that if creating a thread fails, every thread in
110
   // m_workers is joinable and the ones already started still serve the queue
111
   for(size_t i = 0; i != m_pool_size; ++i) {
4,343 ✔
112
      m_workers.emplace_back(&Internal_Thread_Pool::worker_thread, this);
3,475 ✔
113
      OS::set_thread_name(m_workers.back(), tname);
3,475 ✔
114
   }
115
}
868 ✔
116

117
void Internal_Thread_Pool::worker_thread() {
3,475 ✔
118
   while(const auto task = next_task()) {
432,246 ✔
119
      task();
428,771 ✔
120
   }
428,771 ✔
121
}
3,475 ✔
122

123
std::function<void()> Internal_Thread_Pool::next_task() {
432,246 ✔
124
   std::unique_lock<std::mutex> lock(m_mutex);
432,246 ✔
125
   m_more_tasks.wait(lock, [this] { return m_shutdown || !m_tasks.empty(); });
574,126 ✔
126

127
   // Empty, which ends the worker, only once the queue is drained
128
   if(m_tasks.empty()) {
432,246 ✔
129
      return nullptr;
3,475 ✔
130
   }
131

132
   auto task = std::move(m_tasks.front());
428,771 ✔
133
   m_tasks.pop_front();
428,771 ✔
134
   return task;
857,542 ✔
135
}
861,017 ✔
136

137
class Running_Pool_Work;
138

139
/// The innermost pool work this thread is running, if any
140
thread_local const Running_Pool_Work* g_innermost_pool_work = nullptr;  // NOLINT(*-avoid-non-const-global-variables)
141

142
/**
143
* Marks the current thread as running work on behalf of a pool
144
*
145
* Work of one pool can run inline within work of another (a disabled pool, an
146
* inline executor), so the markers form a chain and a pool is found at any depth
147
*/
148
class Running_Pool_Work final {
149
   public:
150
      explicit Running_Pool_Work(const Thread_Pool* pool) : m_pool(pool), m_outer(g_innermost_pool_work) {
428,917 ✔
151
         g_innermost_pool_work = this;
428,917 ✔
152
      }
153

154
      ~Running_Pool_Work() { g_innermost_pool_work = m_outer; }
428,917 ✔
155

156
      Running_Pool_Work(const Running_Pool_Work&) = delete;
157
      Running_Pool_Work& operator=(const Running_Pool_Work&) = delete;
158
      Running_Pool_Work(Running_Pool_Work&&) = delete;
159
      Running_Pool_Work& operator=(Running_Pool_Work&&) = delete;
160

161
      /// True if the current thread is running work of this pool, at any nesting depth
162
      static bool active_for(const Thread_Pool* pool) {
428,990 ✔
163
         for(const auto* work = g_innermost_pool_work; work != nullptr; work = work->m_outer) {
827,320 ✔
164
            if(work->m_pool == pool) {
398,399 ✔
165
               return true;
166
            }
167
         }
168
         return false;
169
      }
170

171
   private:
172
      const Thread_Pool* m_pool;
173
      const Running_Pool_Work* m_outer;
174
};
175

176
std::optional<size_t> global_thread_pool_size() {
1,711 ✔
177
   std::string var;
1,711 ✔
178
   if(OS::read_env_variable(var, "BOTAN_THREAD_POOL_SIZE")) {
1,711 ✔
179
      if(var == "none") {
1 ✔
180
         return std::nullopt;
1 ✔
181
      }
182

183
      // Try to convert to an integer if possible:
184
      if(const auto sz = parse_sz(var)) {
×
185
         return sz;
×
186
      }
187

188
      // If it was neither a number nor a special value, then ignore the env
189
   }
190

191
   /*
192
   * On a few platforms, disable the thread pool by default; it is only
193
   * used if a size is set explicitly in the environment.
194
   */
195

196
#if defined(BOTAN_TARGET_OS_IS_MINGW)
197
   // MinGW seems to have bugs causing deadlock on application exit.
198
   // See https://github.com/randombit/botan/issues/2582 for background.
199
   return std::nullopt;
200
#elif defined(BOTAN_TARGET_OS_IS_EMSCRIPTEN)
201
   // Emscripten's threads are reportedly problematic
202
   // See https://github.com/randombit/botan/issues/4195
203
   return std::nullopt;
204
#else
205
   // Some(0) means choose based on CPU count
206
   return std::optional<size_t>(0);
1,710 ✔
207
#endif
208
}
1,711 ✔
209

210
}  // namespace
211

212
//static
213
Thread_Pool& Thread_Pool::global_instance() {
13,515 ✔
214
   static Thread_Pool g_thread_pool(global_thread_pool_size());
13,515 ✔
215
   return g_thread_pool;
13,515 ✔
216
}
217

218
//static
219
void Work_Executor::set_global(std::shared_ptr<Work_Executor> executor) {
×
220
   Thread_Pool::global_instance().set_executor(std::move(executor));
×
221
}
×
222

223
Thread_Pool::Thread_Pool(std::optional<size_t> opt_pool_size) :
1,720 ✔
224
      m_executor(std::make_shared<Internal_Thread_Pool>(opt_pool_size)),
1,720 ✔
225
      m_worker_count(m_executor->max_concurrency()),
1,720 ✔
226
      m_executor_was_set(false) {}
3,440 ✔
227

228
size_t Thread_Pool::worker_count() const {
81,258 ✔
229
   const std::scoped_lock lock(m_mutex);
81,258 ✔
230
   return m_worker_count;
81,258 ✔
231
}
81,258 ✔
232

233
void Thread_Pool::set_executor(std::shared_ptr<Work_Executor> executor) {
7 ✔
234
   BOTAN_ARG_CHECK(executor != nullptr, "Thread_Pool::set_executor executor must not be null");
7 ✔
235

236
   // Would join the thread this is running on
237
   if(Running_Pool_Work::active_for(this)) {
12 ✔
238
      throw Invalid_State("Cannot set the executor from within work run by the thread pool");
2 ✔
239
   }
240

241
   // Queried outside the lock, since the executor may itself use the library
242
   const size_t concurrency = executor->max_concurrency();
4 ✔
243

244
   // Releasing the internal pool drains its queue and joins its threads, so
245
   // it is held until after the lock is dropped
246
   const auto internal_pool = [&]() {
3 ✔
247
      const std::scoped_lock lock(m_mutex);
4 ✔
248

249
      if(m_executor_was_set) {
4 ✔
250
         throw Invalid_State("Thread pool executor has already been set");
1 ✔
251
      }
252

253
      if(!m_executor) {
3 ✔
254
         throw Invalid_State("Cannot set the executor after the thread pool has shut down");
×
255
      }
256

257
      m_executor_was_set = true;
3 ✔
258
      m_worker_count = concurrency;
3 ✔
259
      return std::exchange(m_executor, std::move(executor));
3 ✔
260
   }();
7 ✔
261
}
3 ✔
262

263
void Thread_Pool::shutdown() {
1,729 ✔
264
   // Releasing the internal pool drains its queue and joins its threads, so
265
   // it is held until after the lock is dropped
266
   const auto executor = [this]() {
1,729 ✔
267
      const std::scoped_lock lock(m_mutex);
1,729 ✔
268
      m_worker_count = 0;
1,729 ✔
269
      return std::exchange(m_executor, nullptr);
1,729 ✔
270
   }();
1,729 ✔
271
}
1,729 ✔
272

273
void Thread_Pool::queue_thunk(const std::function<void()>& work) {
428,984 ✔
274
   /*
275
   * Immediately execute tasks which are queued from within work this pool is
276
   * already running. Otherwise there is risk of deadlock.
277
   *
278
   * This holds even during shutdown: the nested task is part of work that is
279
   * already running, and rejecting it would only cause that work to fail.
280
   *
281
   * This could be improved, with a bit more complexity, by a "helping join";
282
   * instead of just blocking, the thread runs any queued tasks until the one it
283
   * is waiting on has completed.
284
   */
285
   if(Running_Pool_Work::active_for(this)) {
857,968 ✔
286
      return work();
134 ✔
287
   }
288

289
   // The copy keeps the executor alive until the work has been handed over
290
   const auto executor = [this]() {
1,286,751 ✔
291
      const std::scoped_lock lock(m_mutex);
428,917 ✔
292

293
      if(!m_executor) {
428,917 ✔
294
         throw Invalid_State("Cannot add work after thread pool has shut down");
×
295
      }
296

297
      return m_executor;
428,917 ✔
298
   }();
857,834 ✔
299

300
   // Only the pointer value is used, as the marker; in-flight work never
301
   // touches the pool itself, so an executor may finish it after shutdown
302
   const auto wrapped = [this, work]() {
1,715,668 ✔
303
      const Running_Pool_Work scope(this);
428,917 ✔
304
      work();
428,917 ✔
305
   };
857,834 ✔
306

307
   try {
428,917 ✔
308
      executor->queue_work(wrapped);
857,834 ✔
309
   } catch(...) {
2 ✔
310
      // The executor refused the work, so run it here instead
311
      wrapped();
2 ✔
312
   }
2 ✔
313
}
857,834 ✔
314

315
}  // namespace Botan
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc