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

PowerDNS / pdns / 12595591960

03 Jan 2025 09:27AM UTC coverage: 62.774% (+2.5%) from 60.245%
12595591960

Pull #15008

github

web-flow
Merge c2a2749d3 into 788f396a7
Pull Request #15008: Do not follow CNAME records for ANY or CNAME queries

30393 of 78644 branches covered (38.65%)

Branch coverage included in aggregate %.

105822 of 138350 relevant lines covered (76.49%)

4613078.44 hits per line

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

87.85
/pdns/recursordist/mtasker.hh
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22
#pragma once
23
#include <cstdint>
24
#include <ctime>
25
#include <queue>
26
#include <memory>
27
#include <stack>
28

29
#include <boost/multi_index_container.hpp>
30
#include <boost/multi_index/ordered_index.hpp>
31
#include <boost/multi_index/key_extractors.hpp>
32

33
#include "misc.hh"
34
#include "mtasker_context.hh"
35

36
// #define MTASKERTIMING 1
37

38
//! The main MTasker class
39
/** The main MTasker class. See the main page for more information.
40
    \tparam EventKey Type of the key with which events are to be identified. Defaults to int.
41
    \tparam EventVal Type of the content or value of an event. Defaults to int. Cannot be set to void.
42
    \note The EventKey needs to have an operator< defined because it is used as the key of an associative array
43
*/
44

45
template <class EventKey = int, class EventVal = int, class Cmp = std::less<EventKey>>
46
class MTasker
47
{
48
public:
49
  struct Waiter
50
  {
51
    EventKey key;
52
    std::shared_ptr<pdns_ucontext_t> context;
53
    struct timeval ttd
54
    {
55
    };
56
    int tid{};
57
  };
58
  struct KeyTag
59
  {
60
  };
61

62
  using waiters_t = boost::multi_index::multi_index_container<
63
    Waiter,
64
    boost::multi_index::indexed_by<
65
      boost::multi_index::ordered_unique<boost::multi_index::member<Waiter, EventKey, &Waiter::key>, Cmp>,
66
      boost::multi_index::ordered_non_unique<boost::multi_index::tag<KeyTag>, boost::multi_index::member<Waiter, struct timeval, &Waiter::ttd>>>>;
67

68
  //! Constructor
69
  /** Constructor with a small default stacksize. If any of your threads exceeds this stack, your application will crash.
70
      This limit applies solely to the stack, the heap is not limited in any way. If threads need to allocate a lot of data,
71
      the use of new/delete is suggested.
72
   */
73
  MTasker(size_t stacksize = static_cast<size_t>(16 * 8192), size_t stackCacheSize = 0) :
74
    d_stacksize(stacksize), d_maxCachedStacks(stackCacheSize), d_waitstatus(Error)
75
  {
814✔
76
    initMainStackBounds();
814✔
77

78
    // make sure our stack is 16-byte aligned to make all the architectures happy
79
    d_stacksize = d_stacksize >> 4 << 4;
814✔
80
  }
814✔
81

82
  using tfunc_t = void(void*); //!< type of the pointer that starts a thread
83
  uint64_t nextWaiterDelayUsec(uint64_t defusecs);
84
  int waitEvent(EventKey& key, EventVal* val = nullptr, unsigned int timeoutMsec = 0, const struct timeval* now = nullptr);
85
  void yield();
86
  int sendEvent(const EventKey& key, const EventVal* val = nullptr);
87
  void makeThread(tfunc_t* start, void* val);
88
  bool schedule(const struct timeval& now);
89

90
  const waiters_t& getWaiters() const
91
  {
24,233✔
92
    return d_waiters;
24,233✔
93
  }
24,233✔
94

95
  //! gives access to the list of Events threads are waiting for
96
  /** The kernel can call this to get a list of Events threads are waiting for. This is very useful
97
      to setup 'select' or 'poll' or 'aio' events needed to satisfy these requests.
98
      getEvents clears the events parameter before filling it.
99

100
      \param events Vector which is to be filled with keys threads are waiting for
101
  */
102
  void getEvents(std::vector<EventKey>& events) const
103
  {
4✔
104
    events.clear();
4✔
105
    for (const auto& waiter : d_waiters) {
4!
106
      events.emplace_back(waiter.key);
107
    }
108
  }
4✔
109

110
  //! returns true if there are no processes
111
  /** Call this to check if no processes are running anymore
112
      \return true if no processes are left
113
  */
114
  [[nodiscard]] bool noProcesses() const
115
  {
12✔
116
    return d_threadsCount == 0;
12✔
117
  }
12✔
118

119
  //! returns the number of processes running
120
  /** Call this to perhaps limit activities if too many threads are running
121
      \return number of processes running
122
  */
123
  [[nodiscard]] unsigned int numProcesses() const
124
  {
13,808✔
125
    return d_threadsCount;
13,808✔
126
  }
13,808✔
127

128
  //! Returns the current Thread ID (tid)
129
  /** Processes can call this to get a numerical representation of their current thread ID.
130
      This can be useful for logging purposes.
131
  */
132
  [[nodiscard]] int getTid() const
133
  {
4,749✔
134
    return d_tid;
4,749✔
135
  }
4,749✔
136

137
  //! Returns the maximum stack usage so far of this MThread
138
  [[nodiscard]] uint64_t getMaxStackUsage() const
139
  {
4,728✔
140
    return d_threads.at(d_tid).startOfStack - d_threads.at(d_tid).highestStackSeen;
4,728✔
141
  }
4,728✔
142

143
  //! Returns the maximum stack usage so far of this MThread
144
  [[nodiscard]] unsigned int getUsec() const
145
  {
146
#ifdef MTASKERTIMING
147
    return d_threads.at(d_tid).totTime + d_threads.at(d_tid).dt.ndiff() / 1000;
148
#else
149
    return 0;
150
#endif
151
  }
152

153
private:
154
  EventKey d_eventkey; // for waitEvent, contains exact key it was awoken for
155
  EventVal d_waitval;
156

157
  pdns_ucontext_t d_kernel;
158
  std::queue<int> d_runQueue;
159
  std::queue<int> d_zombiesQueue;
160

161
  struct ThreadInfo
162
  {
163
    std::shared_ptr<pdns_ucontext_t> context;
164
    std::function<void(void)> start;
165
    const char* startOfStack{};
166
    const char* highestStackSeen{};
167
#ifdef MTASKERTIMING
168
    CPUTime dt;
169
    unsigned int totTime;
170
#endif
171
  };
172

173
  using pdns_mtasker_stack_t = std::vector<char, lazy_allocator<char>>;
174
  using mthreads_t = std::map<int, ThreadInfo>;
175

176
  mthreads_t d_threads;
177
  std::stack<pdns_mtasker_stack_t> d_cachedStacks;
178
  waiters_t d_waiters;
179
  size_t d_stacksize;
180
  size_t d_threadsCount{0};
181
  size_t d_maxCachedStacks{0};
182
  int d_tid{0};
183
  int d_maxtid{0};
184
  bool d_used{true}; // was d_eventkey consumed?
185
  enum waitstatusenum : int8_t
186
  {
187
    Error = -1,
188
    TimeOut = 0,
189
    Answer = 1,
190
  } d_waitstatus;
191

192
  std::shared_ptr<pdns_ucontext_t> getUContext();
193

194
  void initMainStackBounds()
195
  {
814✔
196
#ifdef HAVE_FIBER_SANITIZER
814✔
197

198
#ifdef HAVE_PTHREAD_GETATTR_NP
814✔
199
    pthread_attr_t attr;
814✔
200
    pthread_attr_init(&attr);
814✔
201
    pthread_getattr_np(pthread_self(), &attr);
814✔
202
    pthread_attr_getstack(&attr, &t_mainStack, &t_mainStackSize);
814✔
203
    pthread_attr_destroy(&attr);
814✔
204
#elif defined(HAVE_PTHREAD_GET_STACKSIZE_NP) && defined(HAVE_PTHREAD_GET_STACKADDR_NP)
205
    t_mainStack = pthread_get_stackaddr_np(pthread_self());
206
    t_mainStackSize = pthread_get_stacksize_np(pthread_self());
207
#else
208
#error Cannot determine stack size and base on this platform
209
#endif
210

211
#endif /* HAVE_FIBER_SANITIZER */
814✔
212
  }
814✔
213
};
214

215
#ifdef PDNS_USE_VALGRIND
216
#include <valgrind/valgrind.h>
217
#endif /* PDNS_USE_VALGRIND */
218

219
template <class EventKey, class EventVal, class Cmp>
220
uint64_t MTasker<EventKey, EventVal, Cmp>::nextWaiterDelayUsec(uint64_t defusecs)
221
{
24,912✔
222
  if (d_waiters.empty()) {
24,912✔
223
    // no waiters
224
    return defusecs;
15,696✔
225
  }
15,696✔
226
  auto& ttdindex = boost::multi_index::get<KeyTag>(d_waiters);
9,216✔
227
  auto iter = ttdindex.begin();
9,216✔
228
  timeval rnow{};
9,216✔
229
  gettimeofday(&rnow, nullptr);
9,216✔
230
  if (iter->ttd.tv_sec != 0) {
9,216✔
231
    // we have a waiter with a timeout specified
232
    if (rnow < iter->ttd) {
9,176✔
233
      // we should not wait longer than the default timeout
234
      return std::min(defusecs, uSec(iter->ttd - rnow));
9,170✔
235
    }
9,170✔
236
    // already expired
237
    return 0;
2,147,483,649✔
238
  }
9,174✔
239
  return defusecs;
42✔
240
}
9,216✔
241

242
//! puts a thread to sleep waiting until a specified event arrives
243
/** Threads can call waitEvent to register that they are waiting on an event with a certain key.
244
    If so desired, the event can carry data which is returned in val in case that is non-zero.
245

246
    Furthermore, a timeout can be specified in seconds.
247

248
    Only one thread can be waiting on a key, results of trying to have more threads
249
    waiting on the same key are undefined.
250

251
    \param key Event key to wait for. Needs to match up to a key reported to sendEvent
252
    \param val If non-zero, the value of the event will be stored in *val
253
    \param timeout If non-zero, number of seconds to wait for an event.
254

255
    \return returns -1 in case of error, 0 in case of timeout, 1 in case of an answer
256
*/
257
template <class EventKey, class EventVal, class Cmp>
258
int MTasker<EventKey, EventVal, Cmp>::waitEvent(EventKey& key, EventVal* val, unsigned int timeoutMsec, const struct timeval* now)
259
{
9,044✔
260
  if (d_waiters.count(key)) { // there was already an exact same waiter
9,044!
261
    return -1;
×
262
  }
×
263

264
  Waiter waiter;
9,044✔
265
  waiter.context = std::make_shared<pdns_ucontext_t>();
9,044✔
266
  waiter.ttd.tv_sec = 0;
9,044✔
267
  waiter.ttd.tv_usec = 0;
9,044✔
268
  if (timeoutMsec != 0) {
9,044!
269
    struct timeval increment
9,037✔
270
    {
9,037✔
271
    };
9,037✔
272
    increment.tv_sec = timeoutMsec / 1000;
9,037✔
273
    increment.tv_usec = static_cast<decltype(increment.tv_usec)>(1000 * (timeoutMsec % 1000));
9,037✔
274
    if (now != nullptr) {
9,037!
275
      waiter.ttd = increment + *now;
8,996✔
276
    }
8,996✔
277
    else {
41✔
278
      struct timeval realnow
41✔
279
      {
41✔
280
      };
41✔
281
      gettimeofday(&realnow, nullptr);
41✔
282
      waiter.ttd = increment + realnow;
41✔
283
    }
41✔
284
  }
9,037✔
285

286
  waiter.tid = d_tid;
9,044✔
287
  waiter.key = key;
9,044✔
288

289
  d_waiters.insert(waiter);
9,044✔
290
#ifdef MTASKERTIMING
291
  unsigned int diff = d_threads[d_tid].dt.ndiff() / 1000;
292
  d_threads[d_tid].totTime += diff;
293
#endif
294
  notifyStackSwitchToKernel();
9,044✔
295
  pdns_swapcontext(*d_waiters.find(key)->context, d_kernel); // 'A' will return here when 'key' has arrived, hands over control to kernel first
9,044✔
296
  notifyStackSwitchDone();
9,044✔
297
#ifdef MTASKERTIMING
298
  d_threads[d_tid].dt.start();
299
#endif
300
  if (val && d_waitstatus == Answer) {
9,044!
301
    *val = std::move(d_waitval);
9,035✔
302
  }
9,035✔
303
  d_tid = waiter.tid;
9,044✔
304
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
305
  if (auto* waiterAddress = reinterpret_cast<char*>(&waiter); waiterAddress < d_threads[d_tid].highestStackSeen) {
9,044!
306
    d_threads[d_tid].highestStackSeen = waiterAddress;
3,857✔
307
  }
3,857✔
308
  assert(!d_used);
9,044✔
309
  key = std::move(d_eventkey);
×
310
  d_used = true;
9,044✔
311
  return d_waitstatus;
9,044✔
312
}
9,044✔
313

314
//! yields control to the kernel or other threads
315
/** Hands over control to the kernel, allowing other processes to run, or events to arrive */
316

317
template <class Key, class Val, class Cmp>
318
void MTasker<Key, Val, Cmp>::yield()
319
{
320
  d_runQueue.push(d_tid);
321
  notifyStackSwitchToKernel();
322
  pdns_swapcontext(*d_threads[d_tid].context, d_kernel); // give control to the kernel
323
  notifyStackSwitchDone();
324
}
325

326
//! reports that an event took place for which threads may be waiting
327
/** From the kernel loop, sendEvent can be called to report that something occurred for which there may be waiters.
328
    \param key Key of the event for which threads may be waiting
329
    \param val If non-zero, pointer to the content of the event
330
    \return Returns -1 in case of error, 0 if there were no waiters, 1 if a thread was woken up.
331

332
    WARNING: when passing val as zero, d_waitval is undefined, and hence waitEvent will return undefined!
333
*/
334
template <class EventKey, class EventVal, class Cmp>
335
int MTasker<EventKey, EventVal, Cmp>::sendEvent(const EventKey& key, const EventVal* val)
336
{
9,038✔
337
  auto waiter = d_waiters.find(key);
9,038✔
338

339
  if (waiter == d_waiters.end()) {
9,038!
340
    return 0;
×
341
  }
×
342
  d_waitstatus = Answer;
9,038✔
343
  if (val) {
9,038!
344
    d_waitval = *val;
9,038✔
345
  }
9,038✔
346
  d_tid = waiter->tid; // set tid
9,038✔
347
  d_eventkey = waiter->key; // pass waitEvent the exact key it was woken for
9,038✔
348
  d_used = false;
9,038✔
349
  auto userspace = std::move(waiter->context);
9,038✔
350
  d_waiters.erase(waiter); // removes the waitpoint
9,038✔
351
  notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
9,038✔
352
  try {
9,038✔
353
    pdns_swapcontext(d_kernel, *userspace); // swaps back to the above point 'A'
9,038✔
354
  }
9,038✔
355
  catch (...) {
9,038✔
356
    notifyStackSwitchDone();
×
357
    throw;
×
358
  }
×
359
  notifyStackSwitchDone();
9,038✔
360
  return 1;
9,038✔
361
}
9,038✔
362

363
template <class Key, class Val, class Cmp>
364
std::shared_ptr<pdns_ucontext_t> MTasker<Key, Val, Cmp>::getUContext()
365
{
3,702✔
366
  auto ucontext = std::make_shared<pdns_ucontext_t>();
3,702✔
367
  if (d_cachedStacks.empty()) {
3,702!
368
    ucontext->uc_stack.resize(d_stacksize + 1);
1,702✔
369
  }
1,702✔
370
  else {
2,000✔
371
    ucontext->uc_stack = std::move(d_cachedStacks.top());
2,000✔
372
    d_cachedStacks.pop();
2,000✔
373
  }
2,000✔
374

375
  ucontext->uc_link = &d_kernel; // come back to kernel after dying
3,702✔
376

377
#ifdef PDNS_USE_VALGRIND
378
  ucontext->valgrind_id = VALGRIND_STACK_REGISTER(&ucontext->uc_stack[0], &ucontext->uc_stack[ucontext->uc_stack.size() - 1]);
379
#endif /* PDNS_USE_VALGRIND */
380

381
  return ucontext;
3,702✔
382
}
3,702✔
383

384
//! launches a new thread
385
/** The kernel can call this to make a new thread, which starts at the function start and gets passed the val void pointer.
386
    \param start Pointer to the function which will form the start of the thread
387
    \param val A void pointer that can be used to pass data to the thread
388
*/
389
template <class Key, class Val, class Cmp>
390
void MTasker<Key, Val, Cmp>::makeThread(tfunc_t* start, void* val)
391
{
3,704✔
392
  auto ucontext = getUContext();
3,704✔
393

394
  ++d_threadsCount;
3,704✔
395
  auto& thread = d_threads[d_maxtid];
3,704✔
396
  // we will get a better approximation when the task is executed, but that prevents notifying a stack at nullptr
397
  // on the first invocation
398
  d_threads[d_maxtid].startOfStack = &ucontext->uc_stack[ucontext->uc_stack.size() - 1];
3,704✔
399
  thread.start = [start, val, this]() {
3,705✔
400
    char dummy{};
3,585✔
401
    d_threads[d_tid].startOfStack = d_threads[d_tid].highestStackSeen = &dummy;
3,585✔
402
    auto const tid = d_tid;
3,585✔
403
    start(val);
3,585✔
404
    d_zombiesQueue.push(tid);
3,585✔
405
  };
3,585✔
406
  pdns_makecontext(*ucontext, thread.start);
3,704✔
407

408
  thread.context = std::move(ucontext);
3,704✔
409
  d_runQueue.push(d_maxtid++); // will run at next schedule invocation
3,704✔
410
}
3,704✔
411

412
//! needs to be called periodically so threads can run and housekeeping can be performed
413
/** The kernel should call this function every once in a while. It makes sense
414
    to call this function if you:
415
    - reported an event
416
    - called makeThread
417
    - want to have threads running waitEvent() to get a timeout if enough time passed
418

419
    \return Returns if there is more work scheduled and recalling schedule now would be useful
420

421
*/
422
template <class Key, class Val, class Cmp>
423
bool MTasker<Key, Val, Cmp>::schedule(const struct timeval& now)
424
{
32,040✔
425
  if (!d_runQueue.empty()) {
32,040✔
426
    d_tid = d_runQueue.front();
3,588✔
427
#ifdef MTASKERTIMING
428
    d_threads[d_tid].dt.start();
429
#endif
430
    notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
3,588✔
431
    try {
3,588✔
432
      pdns_swapcontext(d_kernel, *d_threads[d_tid].context);
3,588✔
433
    }
3,588✔
434
    catch (...) {
3,588✔
435
      notifyStackSwitchDone();
2✔
436
      // It is not clear if the d_runQueue.pop() should be done in this case
437
      throw;
2✔
438
    }
2✔
439
    notifyStackSwitchDone();
3,587✔
440

441
    d_runQueue.pop();
3,587✔
442
    return true;
3,587✔
443
  }
3,588✔
444
  if (!d_zombiesQueue.empty()) {
28,452✔
445
    auto zombi = d_zombiesQueue.front();
3,586✔
446
    if (d_cachedStacks.size() < d_maxCachedStacks) {
3,587!
447
      auto thread = d_threads.find(zombi);
3,480✔
448
      if (thread != d_threads.end()) {
3,480!
449
        d_cachedStacks.push(std::move(thread->second.context->uc_stack));
3,480✔
450
      }
3,480✔
451
      d_threads.erase(thread);
3,480✔
452
    }
3,480✔
453
    else {
2,147,483,754✔
454
      d_threads.erase(zombi);
2,147,483,754✔
455
    }
2,147,483,754✔
456
    --d_threadsCount;
3,586✔
457
    d_zombiesQueue.pop();
3,586✔
458
    return true;
3,586✔
459
  }
3,586✔
460
  if (!d_waiters.empty()) {
24,866✔
461
    auto& ttdindex = boost::multi_index::get<KeyTag>(d_waiters);
9,179✔
462

463
    for (auto i = ttdindex.begin(); i != ttdindex.end();) {
9,196✔
464
      if (i->ttd.tv_sec && i->ttd < now) {
9,185!
465
        d_waitstatus = TimeOut;
6✔
466
        d_eventkey = i->key; // pass waitEvent the exact key it was woken for
6✔
467
        d_used = false;
6✔
468
        auto ucontext = i->context;
6✔
469
        d_tid = i->tid;
6✔
470
        ttdindex.erase(i++); // removes the waitpoint
6✔
471

472
        notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
6✔
473
        try {
6✔
474
          pdns_swapcontext(d_kernel, *ucontext); // swaps back to the above point 'A'
6✔
475
        }
6✔
476
        catch (...) {
6✔
477
          notifyStackSwitchDone();
×
478
          throw;
×
479
        }
×
480
        notifyStackSwitchDone();
6✔
481
      }
6✔
482
      else if (i->ttd.tv_sec != 0) {
9,179!
483
        break;
9,170✔
484
      }
9,170✔
485
      else {
2,147,483,653✔
486
        ++i;
2,147,483,653✔
487
      }
2,147,483,653✔
488
    }
9,182✔
489
  }
9,179✔
490
  return false;
24,866✔
491
}
24,866✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc