• 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

98.45
/src/tests/test_thread_utils.cpp
1
/*
2
* (C) 2019 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "tests.h"
8

9
#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_THREAD_UTILS)
10

11
   #include <botan/exceptn.h>
12
   #include <botan/work_executor.h>
13
   #include <botan/internal/thread_pool.h>
14
   #include <algorithm>
15
   #include <atomic>
16
   #include <chrono>
17
   #include <condition_variable>
18
   #include <deque>
19
   #include <mutex>
20
   #include <thread>
21
   #include <vector>
22

23
namespace Botan_Tests {
24

25
// TODO test Barrier
26
// TODO test Semaphore
27

28
namespace {
29

30
// Runs queued work on its own threads
31
class Test_Executor final : public Botan::Work_Executor {
32
   public:
33
      explicit Test_Executor(size_t threads) {
2 ✔
34
         for(size_t i = 0; i != threads; ++i) {
2 ✔
35
            m_threads.emplace_back([this]() { this->run(); });
2 ✔
36
         }
37
      }
1 ✔
38

39
      ~Test_Executor() override {
1 ✔
40
         {
1 ✔
41
            const std::scoped_lock lock(m_mutex);
1 ✔
42
            m_stop = true;
1 ✔
43
         }
1 ✔
44
         m_cv.notify_all();
1 ✔
45
         for(auto& thread : m_threads) {
2 ✔
46
            thread.join();
1 ✔
47
         }
48
      }
1 ✔
49

50
      Test_Executor(const Test_Executor&) = delete;
51
      Test_Executor& operator=(const Test_Executor&) = delete;
52
      Test_Executor(Test_Executor&&) = delete;
53
      Test_Executor& operator=(Test_Executor&&) = delete;
54

55
      void queue_work(std::function<void()> work) override {
12 ✔
56
         // Work queued from one of the executor's own threads runs directly,
57
         // since that thread would otherwise block waiting on itself
58
         if(owns_thread(std::this_thread::get_id())) {
12 ✔
59
            work();
×
60
            return;
×
61
         }
62

63
         {
12 ✔
64
            const std::scoped_lock lock(m_mutex);
12 ✔
65
            m_queue.push_back(std::move(work));
12 ✔
66
         }
12 ✔
67
         m_cv.notify_one();
12 ✔
68
      }
69

70
      size_t max_concurrency() const override { return m_threads.size(); }
1 ✔
71

72
      bool owns_thread(std::thread::id id) const {
23 ✔
73
         return std::any_of(
12 ✔
74
            m_threads.begin(), m_threads.end(), [id](const std::thread& t) { return t.get_id() == id; });
23 ✔
75
      }
76

77
      size_t work_run() const {
2 ✔
78
         const std::scoped_lock lock(m_mutex);
2 ✔
79
         return m_work_run;
2 ✔
80
      }
2 ✔
81

82
   private:
83
      void run() {
1 ✔
84
         while(const auto work = next_work()) {
13 ✔
85
            work();
12 ✔
86
         }
12 ✔
87
      }
1 ✔
88

89
      // Empty once stopped and the queue is drained
90
      std::function<void()> next_work() {
13 ✔
91
         std::unique_lock<std::mutex> lock(m_mutex);
13 ✔
92
         m_cv.wait(lock, [this] { return m_stop || !m_queue.empty(); });
19 ✔
93

94
         if(m_queue.empty()) {
13 ✔
95
            return nullptr;
1 ✔
96
         }
97

98
         auto work = std::move(m_queue.front());
12 ✔
99
         m_queue.pop_front();
12 ✔
100
         m_work_run += 1;
12 ✔
101
         return work;
24 ✔
102
      }
25 ✔
103

104
      mutable std::mutex m_mutex;
105
      std::condition_variable m_cv;
106
      std::deque<std::function<void()>> m_queue;
107
      bool m_stop = false;
108
      size_t m_work_run = 0;
109
      // Declared last since the threads start in the constructor
110
      std::vector<std::thread> m_threads;
111
};
112

113
// Runs all queued work in the calling thread
114
class Inline_Executor final : public Botan::Work_Executor {
8 ✔
115
   public:
116
      void queue_work(std::function<void()> work) override { work(); }
2 ✔
117

118
      size_t max_concurrency() const override { return 1; }
2 ✔
119
};
120

121
// Refuses all work by throwing
122
class Refusing_Executor final : public Botan::Work_Executor {
2 ✔
123
   public:
124
      void queue_work(std::function<void()> /*work*/) override { throw Botan::Invalid_State("refused"); }
2 ✔
125

126
      size_t max_concurrency() const override { return 4; }
1 ✔
127
};
128

129
std::thread::id whoami() {
19 ✔
130
   return std::this_thread::get_id();
19 ✔
131
}
132

133
Test::Result thread_pool() {
1 ✔
134
   Test::Result result("Thread_Pool");
1 ✔
135

136
   // Using lots of threads since here the works spend most of the time sleeping
137
   Botan::Thread_Pool pool(16);
1 ✔
138

139
   const auto sleep_or_throw = [](size_t x) -> size_t {
101 ✔
140
      std::this_thread::sleep_for(std::chrono::milliseconds((x * 97) % 127));
100 ✔
141

142
      if(x % 2 == 0) {
100 ✔
143
         throw x;  // NOLINT(*-exception-baseclass)
50 ✔
144
      }
145
      return x;
50 ✔
146
   };
147

148
   std::vector<Botan::Joining_Future<size_t>> futures;
1 ✔
149
   for(size_t i = 0; i != 100; ++i) {
101 ✔
150
      auto fut = pool.run(sleep_or_throw, i);
100 ✔
151
      futures.push_back(std::move(fut));
100 ✔
152
   }
100 ✔
153

154
   for(size_t i = 0; i != futures.size(); ++i) {
101 ✔
155
      if(i % 2 == 0) {
100 ✔
156
         try {
50 ✔
157
            futures[i].get();
50 ✔
158
            result.test_failure("Expected future to throw");
×
159
         } catch(size_t x) {
50 ✔
160
            result.test_sz_eq("Expected thrown value", x, i);
50 ✔
161
         }
50 ✔
162
      } else {
163
         result.test_sz_eq("Expected return value", futures[i].get(), i);
50 ✔
164
      }
165
   }
166

167
   pool.shutdown();
1 ✔
168

169
   return result;
1 ✔
170
}
1 ✔
171

172
Test::Result thread_pool_nested() {
1 ✔
173
   Test::Result result("Thread_Pool nested");
1 ✔
174

175
   // A small pool, so that without the immediate execution of tasks
176
   // queued by the pool's own workers, all workers would block in the
177
   // inner get() with the inner tasks stuck behind them in the queue
178
   Botan::Thread_Pool pool(2);
1 ✔
179

180
   const auto fan_out = [&pool](size_t i) -> size_t {
17 ✔
181
      std::vector<Botan::Joining_Future<size_t>> inner;
16 ✔
182
      inner.reserve(4);
16 ✔
183
      for(size_t j = 0; j != 4; ++j) {
80 ✔
184
         inner.push_back(pool.run([i, j]() -> size_t { return i * 4 + j; }));
128 ✔
185
      }
186

187
      size_t sum = 0;
16 ✔
188
      for(auto& fut : inner) {
80 ✔
189
         sum += fut.get();
64 ✔
190
      }
191
      return sum;
16 ✔
192
   };
16 ✔
193

194
   std::vector<Botan::Joining_Future<size_t>> outer;
1 ✔
195
   for(size_t i = 0; i != 16; ++i) {
17 ✔
196
      outer.push_back(pool.run(fan_out, i));
32 ✔
197
   }
198

199
   for(size_t i = 0; i != outer.size(); ++i) {
17 ✔
200
      result.test_sz_eq("Expected nested sum", outer[i].get(), 16 * i + 6);
16 ✔
201
   }
202

203
   pool.shutdown();
1 ✔
204

205
   return result;
1 ✔
206
}
1 ✔
207

208
Test::Result thread_pool_nested_pools() {
1 ✔
209
   Test::Result result("Thread_Pool nested pools");
1 ✔
210

211
   // Work of the disabled pool runs inline on the outer pool's only worker,
212
   // so work it queues on the outer pool must run inline as well; otherwise
213
   // it would wait in the queue behind the worker that is waiting on it
214
   Botan::Thread_Pool outer(1);
1 ✔
215
   Botan::Thread_Pool disabled(std::nullopt);
1 ✔
216

217
   const auto innermost = [&outer]() { return outer.run(whoami).get(); };
2 ✔
218
   const auto via_disabled = [&disabled, &innermost]() { return disabled.run(innermost).get(); };
2 ✔
219

220
   const auto worker_tid = outer.run(whoami).get();
2 ✔
221
   result.test_is_true("Innermost work runs inline on the outer pool's worker",
1 ✔
222
                       outer.run(via_disabled).get() == worker_tid);
2 ✔
223

224
   // Likewise the outer pool must be recognized through the nesting here,
225
   // since set_executor would otherwise join the worker it is running on
226
   {
1 ✔
227
      const auto set_outer = [&outer]() { outer.set_executor(std::make_shared<Inline_Executor>()); };
1 ✔
228
      auto fut = outer.run([&disabled, &set_outer]() { disabled.run(set_outer).get(); });
3 ✔
229
      result.test_throws("Cannot set executor from within nested pool work", [&]() { fut.get(); });
2 ✔
230
   }
1 ✔
231

232
   outer.shutdown();
1 ✔
233
   disabled.shutdown();
1 ✔
234

235
   return result;
1 ✔
236
}
1 ✔
237

238
Test::Result thread_pool_executor() {
1 ✔
239
   Test::Result result("Thread_Pool executor");
1 ✔
240

241
   const auto executor = std::make_shared<Test_Executor>(1);
1 ✔
242
   const auto my_tid = whoami();
1 ✔
243

244
   Botan::Thread_Pool pool(2);
1 ✔
245

246
   {
1 ✔
247
      const auto tid = pool.run(whoami).get();
2 ✔
248
      result.test_is_true("Without an executor work runs on a pool thread",
2 ✔
249
                          tid != my_tid && !executor->owns_thread(tid));
1 ✔
250
      result.test_sz_eq("Worker count is the pool size", pool.worker_count(), 2);
1 ✔
251
   }
252

253
   result.test_throws("Null executor is rejected", [&]() { pool.set_executor(nullptr); });
2 ✔
254

255
   // Installing the executor stops the pool's own threads
256
   pool.set_executor(executor);
1 ✔
257
   result.test_sz_eq("Worker count reflects executor", pool.worker_count(), executor->max_concurrency());
1 ✔
258

259
   {
1 ✔
260
      std::vector<Botan::Joining_Future<std::thread::id>> futures;
1 ✔
261
      for(size_t i = 0; i != 8; ++i) {
9 ✔
262
         futures.push_back(pool.run(whoami));
16 ✔
263
      }
264

265
      for(auto& fut : futures) {
9 ✔
266
         result.test_is_true("Work runs on the executor thread", executor->owns_thread(fut.get()));
8 ✔
267
      }
268

269
      result.test_sz_eq("Executor ran the work", executor->work_run(), 8);
2 ✔
270
   }
1 ✔
271

272
   // Work queued from within work the executor is running must run inline:
273
   // with a single executor thread, queuing it would deadlock since the outer
274
   // work blocks that thread
275
   {
1 ✔
276
      const auto nested = [&pool]() { return pool.run(whoami).get(); };
2 ✔
277
      result.test_is_true("Nested work runs inline on the executor thread",
2 ✔
278
                          executor->owns_thread(pool.run(nested).get()));
2 ✔
279
      result.test_sz_eq("Nested work did not go through the executor", executor->work_run(), 9);
2 ✔
280
   }
281

282
   {
1 ✔
283
      auto fut = pool.run([]() -> size_t { throw Botan::Invalid_Argument("expected"); });
2 ✔
284
      result.test_throws("Exceptions propagate via the future", [&]() { fut.get(); });
2 ✔
285
   }
1 ✔
286

287
   {
1 ✔
288
      auto fut = pool.run([&pool]() { pool.set_executor(std::make_shared<Inline_Executor>()); });
2 ✔
289
      result.test_throws("Cannot set executor from within pool work", [&]() { fut.get(); });
2 ✔
290
   }
1 ✔
291

292
   result.test_throws("Executor can only be set once",
1 ✔
293
                      [&]() { pool.set_executor(std::make_shared<Inline_Executor>()); });
2 ✔
294
   result.test_is_true("Original executor remains in use", executor->owns_thread(pool.run(whoami).get()));
2 ✔
295

296
   pool.shutdown();
1 ✔
297

298
   return result;
2 ✔
299
}
2 ✔
300

301
Test::Result thread_pool_inline_executor() {
1 ✔
302
   Test::Result result("Thread_Pool inline executor");
1 ✔
303

304
   Botan::Thread_Pool pool(2);
1 ✔
305
   pool.set_executor(std::make_shared<Inline_Executor>());
2 ✔
306

307
   result.test_sz_eq("Worker count reflects executor", pool.worker_count(), 1);
1 ✔
308

309
   const auto my_tid = whoami();
1 ✔
310
   result.test_is_true("Work runs in the calling thread", pool.run(whoami).get() == my_tid);
2 ✔
311

312
   const auto nested = [&pool]() { return pool.run(whoami).get(); };
2 ✔
313
   result.test_is_true("Nested work runs in the calling thread", pool.run(nested).get() == my_tid);
2 ✔
314

315
   pool.shutdown();
1 ✔
316

317
   return result;
1 ✔
318
}
1 ✔
319

320
Test::Result thread_pool_refusing_executor() {
1 ✔
321
   Test::Result result("Thread_Pool refusing executor");
1 ✔
322

323
   Botan::Thread_Pool pool(2);
1 ✔
324
   pool.set_executor(std::make_shared<Refusing_Executor>());
2 ✔
325

326
   // Work the executor refuses runs in the calling thread instead
327
   const auto my_tid = whoami();
1 ✔
328
   result.test_is_true("Refused work runs in the calling thread", pool.run(whoami).get() == my_tid);
2 ✔
329
   result.test_sz_eq("Refused work still produces its result", pool.run([]() { return size_t(42); }).get(), 42);
2 ✔
330

331
   pool.shutdown();
1 ✔
332

333
   return result;
1 ✔
334
}
1 ✔
335

336
Test::Result thread_pool_future_joins() {
1 ✔
337
   Test::Result result("Thread_Pool future waits on destruction");
1 ✔
338

339
   Botan::Thread_Pool pool(2);
1 ✔
340

341
   std::atomic<bool> finished = false;
1 ✔
342
   {
1 ✔
343
      const auto fut = pool.run([&finished]() {
3 ✔
344
         std::this_thread::sleep_for(std::chrono::milliseconds(50));
1 ✔
345
         finished = true;
1 ✔
346
      });
1 ✔
347
      // Discarded without get()
348
   }
1 ✔
349
   result.test_is_true("Work finished before its future was destroyed", finished.load());
1 ✔
350

351
   pool.shutdown();
1 ✔
352

353
   return result;
1 ✔
354
}
1 ✔
355

356
BOTAN_REGISTER_TEST_FN("utils", "thread_pool", thread_pool);
357
BOTAN_REGISTER_TEST_FN("utils", "thread_pool_nested", thread_pool_nested);
358
BOTAN_REGISTER_TEST_FN("utils", "thread_pool_nested_pools", thread_pool_nested_pools);
359
BOTAN_REGISTER_TEST_FN("utils", "thread_pool_executor", thread_pool_executor);
360
BOTAN_REGISTER_TEST_FN("utils", "thread_pool_inline_executor", thread_pool_inline_executor);
361
BOTAN_REGISTER_TEST_FN("utils", "thread_pool_refusing_executor", thread_pool_refusing_executor);
362
BOTAN_REGISTER_TEST_FN("utils", "thread_pool_future_joins", thread_pool_future_joins);
363

364
}  // namespace
365

366
}  // namespace Botan_Tests
367

368
#endif
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