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

PowerDNS / pdns / 18743945403

23 Oct 2025 09:29AM UTC coverage: 65.845% (+0.02%) from 65.829%
18743945403

Pull #16356

github

web-flow
Merge 8a2027ef1 into efa3637e8
Pull Request #16356: auth 5.0: backport "pdnsutil: fix b2b-migrate to from sql to non-sql"

42073 of 92452 branches covered (45.51%)

Branch coverage included in aggregate %.

128008 of 165855 relevant lines covered (77.18%)

6379935.17 hits per line

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

40.14
/pdns/recursordist/rec-taskqueue.cc
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

23
#include "rec-taskqueue.hh"
24
#include "taskqueue.hh"
25
#include "lock.hh"
26
#include "logging.hh"
27
#include "stat_t.hh"
28
#include "syncres.hh"
29

30
// For rate limiting purposes we maintain a set of tasks recently submitted.
31
class TimedSet
32
{
33
public:
34
  TimedSet(time_t time) :
35
    d_expiry_seconds(time)
36
  {
181✔
37
  }
181✔
38

39
  uint64_t purge(time_t now)
40
  {
592✔
41
    // This purge is relatively cheap, as we're walking an ordered index
42
    uint64_t erased = 0;
592✔
43
    auto& ind = d_set.template get<time_t>();
592✔
44
    auto iter = ind.begin();
592✔
45
    while (iter != ind.end()) {
594✔
46
      if (iter->d_ttd < now) {
592✔
47
        ++erased;
2✔
48
        iter = ind.erase(iter);
2✔
49
      }
2✔
50
      else {
590✔
51
        break;
590✔
52
      }
590✔
53
    }
592✔
54
    return erased;
592✔
55
  }
592✔
56

57
  bool insert(time_t now, const pdns::ResolveTask& task)
58
  {
1,020✔
59
    // We periodically purge
60
    if (++d_count % 1024 == 0) {
1,020!
61
      purge(now);
×
62
    }
×
63
    time_t ttd = now + d_expiry_seconds;
1,020✔
64
    bool inserted = d_set.emplace(task, ttd).second;
1,020✔
65
    if (!inserted) {
1,020✔
66
      uint64_t erased = purge(now);
592✔
67
      // Try again if the purge deleted at least one entry
68
      if (erased > 0) {
592✔
69
        inserted = d_set.emplace(task, ttd).second;
2✔
70
      }
2✔
71
    }
592✔
72
    return inserted;
1,020✔
73
  }
1,020✔
74

75
  void clear()
76
  {
618✔
77
    d_set.clear();
618✔
78
  }
618✔
79

80
private:
81
  struct Entry
82
  {
83
    Entry(pdns::ResolveTask task, time_t ttd) :
84
      d_task(std::move(task)), d_ttd(ttd) {}
1,022✔
85
    pdns::ResolveTask d_task;
86
    time_t d_ttd;
87
  };
88

89
  using timed_set_t = multi_index_container<
90
    Entry,
91
    indexed_by<ordered_unique<tag<pdns::ResolveTask>,
92
                              member<Entry, pdns::ResolveTask, &Entry::d_task>>,
93
               ordered_non_unique<tag<time_t>,
94
                                  member<Entry, time_t, &Entry::d_ttd>>>>;
95
  timed_set_t d_set;
96
  time_t d_expiry_seconds;
97
  unsigned int d_count{0};
98
};
99

100
struct Queue
101
{
102
  pdns::TaskQueue queue;
103
  TimedSet rateLimitSet{60};
104
};
105
static LockGuarded<Queue> s_taskQueue;
106

107
struct taskstats
108
{
109
  pdns::stat_t pushed;
110
  pdns::stat_t run;
111
  pdns::stat_t exceptions;
112
};
113

114
static struct taskstats s_almost_expired_tasks;
115
static struct taskstats s_resolve_tasks;
116

117
// forceNoQM is true means resolve using no qm, false means use default value
118
static void resolveInternal(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task, bool forceNoQM) noexcept
119
{
×
120
  auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()), "netmask", Logging::Loggable(task.d_netmask.empty() ? "" : task.d_netmask.toString()));
×
121
  const string msg = "Exception while running a background ResolveTask";
×
122
  SyncRes resolver(now);
×
123
  vector<DNSRecord> ret;
×
124
  resolver.setRefreshAlmostExpired(task.d_refreshMode);
×
125
  resolver.setQuerySource(task.d_netmask);
×
126
  if (forceNoQM) {
×
127
    resolver.setQNameMinimization(false);
×
128
  }
×
129
  bool exceptionOccurred = true;
×
130
  try {
×
131
    log->info(Logr::Debug, "resolving", "refresh", Logging::Loggable(task.d_refreshMode));
×
132
    int res = resolver.beginResolve(task.d_qname, QType(task.d_qtype), QClass::IN, ret);
×
133
    exceptionOccurred = false;
×
134
    log->info(Logr::Debug, "done", "rcode", Logging::Loggable(res), "records", Logging::Loggable(ret.size()));
×
135
  }
×
136
  catch (const std::exception& e) {
×
137
    log->error(Logr::Warning, msg, e.what());
×
138
  }
×
139
  catch (const PDNSException& e) {
×
140
    log->error(Logr::Warning, msg, e.reason);
×
141
  }
×
142
  catch (const ImmediateServFailException& e) {
×
143
    if (logErrors) {
×
144
      log->error(Logr::Warning, msg, e.reason);
×
145
    }
×
146
  }
×
147
  catch (const PolicyHitException& e) {
×
148
    if (logErrors) {
×
149
      log->error(Logr::Warning, msg, "PolicyHit");
×
150
    }
×
151
  }
×
152
  catch (...) {
×
153
    log->error(Logr::Warning, msg, "Unexpected exception");
×
154
  }
×
155
  if (exceptionOccurred) {
×
156
    if (task.d_refreshMode) {
×
157
      ++s_almost_expired_tasks.exceptions;
×
158
    }
×
159
    else {
×
160
      ++s_resolve_tasks.exceptions;
×
161
    }
×
162
  }
×
163
  else {
×
164
    if (task.d_refreshMode) {
×
165
      ++s_almost_expired_tasks.run;
×
166
    }
×
167
    else {
×
168
      ++s_resolve_tasks.run;
×
169
    }
×
170
  }
×
171
}
×
172

173
static void resolveForceNoQM(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
174
{
×
175
  resolveInternal(now, logErrors, task, true);
×
176
}
×
177

178
static void resolve(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
179
{
×
180
  resolveInternal(now, logErrors, task, false);
×
181
}
×
182

183
static void tryDoT(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
184
{
×
185
  auto log = g_slog->withName("taskq")->withValues("method", Logging::Loggable("tryDoT"), "name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()), "ip", Logging::Loggable(task.d_ip));
×
186
  const string msg = "Exception while running a background tryDoT task";
×
187
  SyncRes resolver(now);
×
188
  vector<DNSRecord> ret;
×
189
  resolver.setRefreshAlmostExpired(false);
×
190
  bool exceptionOccurred = true;
×
191
  try {
×
192
    log->info(Logr::Debug, "trying DoT");
×
193
    bool tryOK = resolver.tryDoT(task.d_qname, QType(task.d_qtype), task.d_nsname, task.d_ip, now.tv_sec);
×
194
    exceptionOccurred = false;
×
195
    log->info(Logr::Debug, "done", "ok", Logging::Loggable(tryOK));
×
196
  }
×
197
  catch (const std::exception& e) {
×
198
    log->error(Logr::Warning, msg, e.what());
×
199
  }
×
200
  catch (const PDNSException& e) {
×
201
    log->error(Logr::Warning, msg, e.reason);
×
202
  }
×
203
  catch (const ImmediateServFailException& e) {
×
204
    if (logErrors) {
×
205
      log->error(Logr::Warning, msg, e.reason);
×
206
    }
×
207
  }
×
208
  catch (const PolicyHitException& e) {
×
209
    if (logErrors) {
×
210
      log->error(Logr::Notice, msg, "PolicyHit");
×
211
    }
×
212
  }
×
213
  catch (...) {
×
214
    log->error(Logr::Warning, msg, "Unexpected exception");
×
215
  }
×
216
  if (exceptionOccurred) {
×
217
    ++s_resolve_tasks.exceptions;
×
218
  }
×
219
  else {
×
220
    ++s_resolve_tasks.run;
×
221
  }
×
222
}
×
223

224
void runTasks(size_t max, bool logErrors)
225
{
292✔
226
  for (size_t count = 0; count < max; count++) {
292!
227
    if (!runTaskOnce(logErrors)) {
292!
228
      // No more tasks in queue
229
      break;
292✔
230
    }
292✔
231
  }
292✔
232
}
292✔
233

234
bool runTaskOnce(bool logErrors)
235
{
3,434✔
236
  pdns::ResolveTask task;
3,434✔
237
  {
3,434✔
238
    auto lock = s_taskQueue.lock();
3,434✔
239
    if (lock->queue.empty()) {
3,434!
240
      return false;
3,434✔
241
    }
3,434✔
242
    task = lock->queue.pop();
×
243
  }
×
244
  bool expired = task.run(logErrors);
×
245
  if (expired) {
×
246
    s_taskQueue.lock()->queue.incExpired();
×
247
  }
×
248
  return true;
×
249
}
3,434✔
250

251
void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask)
252
{
52✔
253
  if (SyncRes::isUnsupported(qtype)) {
52!
254
    auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()), "netmask", Logging::Loggable(netmask.empty() ? "" : netmask.toString()));
×
255
    log->error(Logr::Error, "Cannot push task", "qtype unsupported");
×
256
    return;
×
257
  }
×
258
  pdns::ResolveTask task{qname, qtype, deadline, true, resolve, {}, {}, netmask};
52✔
259
  if (s_taskQueue.lock()->queue.push(std::move(task))) {
52✔
260
    ++s_almost_expired_tasks.pushed;
38✔
261
  }
38✔
262
}
52✔
263

264
void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline, bool forceQMOff)
265
{
1,020✔
266
  if (SyncRes::isUnsupported(qtype)) {
1,020!
267
    auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()));
×
268
    log->error(Logr::Error, "Cannot push task", "qtype unsupported");
×
269
    return;
×
270
  }
×
271
  auto func = forceQMOff ? resolveForceNoQM : resolve;
1,020!
272
  pdns::ResolveTask task{qname, qtype, deadline, false, func, {}, {}, {}};
1,020✔
273
  auto lock = s_taskQueue.lock();
1,020✔
274
  bool inserted = lock->rateLimitSet.insert(now, task);
1,020✔
275
  if (inserted) {
1,020✔
276
    if (lock->queue.push(std::move(task))) {
430!
277
      ++s_resolve_tasks.pushed;
430✔
278
    }
430✔
279
  }
430✔
280
}
1,020✔
281

282
bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ipAddress, time_t deadline, const DNSName& nsname)
283
{
×
284
  if (SyncRes::isUnsupported(qtype)) {
×
285
    auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()));
×
286
    log->error(Logr::Error, "Cannot push task", "qtype unsupported");
×
287
    return false;
×
288
  }
×
289

290
  pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ipAddress, nsname, {}};
×
291
  bool pushed = s_taskQueue.lock()->queue.push(std::move(task));
×
292
  if (pushed) {
×
293
    ++s_almost_expired_tasks.pushed;
×
294
  }
×
295
  return pushed;
×
296
}
×
297

298
uint64_t getTaskPushes()
299
{
198✔
300
  return s_taskQueue.lock()->queue.getPushes();
198✔
301
}
198✔
302

303
uint64_t getTaskExpired()
304
{
198✔
305
  return s_taskQueue.lock()->queue.getExpired();
198✔
306
}
198✔
307

308
uint64_t getTaskSize()
309
{
238✔
310
  return s_taskQueue.lock()->queue.size();
238✔
311
}
238✔
312

313
void taskQueueClear()
314
{
618✔
315
  auto lock = s_taskQueue.lock();
618✔
316
  lock->queue.clear();
618✔
317
  lock->rateLimitSet.clear();
618✔
318
}
618✔
319

320
pdns::ResolveTask taskQueuePop()
321
{
28✔
322
  return s_taskQueue.lock()->queue.pop();
28✔
323
}
28✔
324

325
uint64_t getAlmostExpiredTasksPushed()
326
{
138✔
327
  return s_almost_expired_tasks.pushed;
138✔
328
}
138✔
329

330
uint64_t getAlmostExpiredTasksRun()
331
{
138✔
332
  return s_almost_expired_tasks.run;
138✔
333
}
138✔
334

335
uint64_t getAlmostExpiredTaskExceptions()
336
{
138✔
337
  return s_almost_expired_tasks.exceptions;
138✔
338
}
138✔
339

340
uint64_t getResolveTasksPushed()
341
{
×
342
  return s_almost_expired_tasks.pushed;
×
343
}
×
344

345
uint64_t getResolveTasksRun()
346
{
×
347
  return s_almost_expired_tasks.run;
×
348
}
×
349

350
uint64_t getResolveTaskExceptions()
351
{
×
352
  return s_almost_expired_tasks.exceptions;
×
353
}
×
354

355
bool taskQTypeIsSupported(QType qtype)
356
{
2,817✔
357
  return !SyncRes::isUnsupported(qtype);
2,817✔
358
}
2,817✔
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