cutelyst  5.0.1
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
unixfork.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2014-2020 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "unixfork.h"
6 
7 #include "server.h"
8 
9 #if defined(HAS_EventLoopEPoll)
10 # include "EventLoopEPoll/eventdispatcher_epoll.h"
11 #endif
12 
13 #if defined(__FreeBSD__) || defined(__GNU_kFreeBSD__)
14 # include <sys/cpuset.h>
15 # include <sys/param.h>
16 #endif
17 
18 #include <cerrno>
19 #include <csignal>
20 #include <cstdio>
21 #include <cstdlib>
22 #include <grp.h>
23 #include <iostream>
24 #include <pwd.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <unistd.h>
30 
31 #include <QAbstractEventDispatcher>
32 #include <QCoreApplication>
33 #include <QFile>
34 #include <QLoggingCategory>
35 #include <QMutex>
36 #include <QSocketNotifier>
37 #include <QThread>
38 #include <QTimer>
39 
40 Q_LOGGING_CATEGORY(C_SERVER_UNIX, "cutelyst.server.unix", QtWarningMsg)
41 
42 #pragma GCC diagnostic push
43 #pragma GCC diagnostic ignored "-Wunused-result"
44 
45 using namespace Qt::StringLiterals;
46 
47 namespace {
48 int signalsFd[2];
49 } // namespace
50 
51 UnixFork::UnixFork(int process, int threads, bool setupSignals, QObject *parent)
52  : AbstractFork(parent)
53  , m_threads(threads)
54  , m_processes(process)
55 {
56  if (setupSignals) {
57  setupUnixSignalHandlers();
58  }
59 }
60 
61 UnixFork::~UnixFork()
62 {
63  if (m_child) {
64  _exit(0);
65  }
66 }
67 
68 bool UnixFork::continueMaster(int *exit)
69 {
70  Q_UNUSED(exit)
71  return true;
72 }
73 
74 int UnixFork::exec(bool lazy, bool master)
75 {
76  if (master) {
77  std::cout << "spawned SERVER master process (pid: " << QCoreApplication::applicationPid()
78  << ")" << '\n';
79  }
80 
81  int ret;
82  if (lazy) {
83  if (master) {
84  ret = internalExec();
85  } else {
86  std::cerr << "*** Master mode must be set on lazy mode" << '\n';
87  ret = -1;
88  }
89  } else {
90  if (m_processes > 0) {
91  ret = internalExec();
92  } else {
93  Q_EMIT forked(0);
94  ret = qApp->exec();
95  }
96  }
97 
98  return ret;
99 }
100 
102 {
103  for (const auto &[key, value] : m_childs.asKeyValueRange()) {
104  value.restart = 1; // Mark as requiring restart
105  terminateChild(key);
106  }
107 
108  setupCheckChildTimer();
109 }
110 
111 int UnixFork::internalExec()
112 {
113  int ret;
114  bool respawn = false;
115  do {
116  if (!createProcess(respawn)) {
117  return 1;
118  }
119  respawn = true;
120 
121  installTouchReload();
122 
123  ret = qApp->exec();
124 
125  removeTouchReload();
126  } while (!m_terminating);
127 
128  return ret;
129 }
130 
131 bool UnixFork::createProcess(bool respawn)
132 {
133  if (respawn) {
134  m_recreateWorker.removeIf([this, respawn](Worker worker) {
135  worker.restart = 0;
136  if (!createChild(worker, respawn)) {
137  std::cout << "CHEAPING worker: " << worker.id << '\n';
138  --m_processes;
139  }
140 
141  return true; // Clean recreate worker list
142  });
143  } else {
144  for (int i = 0; i < m_processes; ++i) {
145  Worker worker;
146  worker.id = i + 1;
147  worker.null = false;
148  createChild(worker, respawn);
149  }
150  }
151 
152  return !m_childs.empty();
153 }
154 
155 void UnixFork::decreaseWorkerRespawn()
156 {
157  int missingRespawn = 0;
158  for (const auto &[key, value] : m_childs.asKeyValueRange()) {
159  if (value.respawn > 0) {
160  --value.respawn;
161  missingRespawn += value.respawn;
162  }
163  }
164 
165  if (missingRespawn) {
166  QTimer::singleShot(std::chrono::seconds{1}, this, &UnixFork::decreaseWorkerRespawn);
167  }
168 }
169 
171 {
172  const auto childs = m_childs.keys();
173  for (qint64 pid : childs) {
174  killChild(pid);
175  }
176 }
177 
178 void UnixFork::killChild(qint64 pid)
179 {
180  // qCDebug(C_SERVER_UNIX) << "SIGKILL " << pid;
181  ::kill(pid_t(pid), SIGKILL);
182 }
183 
185 {
186  const auto childs = m_childs.keys();
187  for (qint64 pid : childs) {
188  terminateChild(pid);
189  }
190 }
191 
192 void UnixFork::terminateChild(qint64 pid)
193 {
194  // qCDebug(C_SERVER_UNIX) << "SIGQUIT " << pid;
195  ::kill(pid_t(pid), SIGQUIT);
196 }
197 
198 void UnixFork::stopSERVER(const QString &pidfile)
199 {
200  QFile file(pidfile);
201  if (!file.open(QFile::ReadOnly | QFile::Text)) {
202  std::cerr << "Failed open pid file " << qPrintable(pidfile) << '\n';
203  exit(1);
204  }
205 
206  QByteArray piddata = file.readLine().simplified();
207  qint64 pid = piddata.toLongLong();
208  if (pid < 2) {
209  std::cerr << "Failed read pid file " << qPrintable(pidfile) << '\n';
210  exit(1);
211  }
212 
213  ::kill(pid_t(pid), SIGINT);
214  exit(0);
215 }
216 
217 bool UnixFork::setUmask(const QByteArray &valueStr)
218 {
219  if (valueStr.size() < 3) {
220  std::cerr << "umask too small" << '\n';
221  return false;
222  }
223 
224  const char *value = valueStr.constData();
225  mode_t mode = 0;
226  if (valueStr.size() == 3) {
227  mode = (mode << 3) + (value[0] - '0');
228  mode = (mode << 3) + (value[1] - '0');
229  mode = (mode << 3) + (value[2] - '0');
230  } else {
231  mode = (mode << 3) + (value[1] - '0');
232  mode = (mode << 3) + (value[2] - '0');
233  mode = (mode << 3) + (value[3] - '0');
234  }
235  std::cout << "umask() " << value << '\n';
236 
237  umask(mode);
238 
239  return true;
240 }
241 
242 void UnixFork::signalHandler(int signal)
243 {
244  // qDebug() << Q_FUNC_INFO << signal << QCoreApplication::applicationPid();
245  char sig = signal;
246  write(signalsFd[0], &sig, sizeof(sig));
247 }
248 
249 void UnixFork::setupCheckChildTimer()
250 {
251  if (!m_checkChildRestart) {
252  m_checkChildRestart = new QTimer(this);
253  m_checkChildRestart->start(std::chrono::milliseconds{500});
254  connect(m_checkChildRestart, &QTimer::timeout, this, &UnixFork::handleSigChld);
255  }
256 }
257 
258 void UnixFork::postFork(int workerId)
259 {
260  // Child must not have parent timers
261  delete m_checkChildRestart;
262 
263  Q_EMIT forked(workerId - 1);
264 }
265 
266 bool UnixFork::setGidUid(const QString &gid, const QString &uid, bool noInitgroups)
267 {
268  bool ok;
269 
270  if (!gid.isEmpty()) {
271  uint gidInt = gid.toUInt(&ok);
272  if (!ok) {
273  const struct group *ugroup = getgrnam(qUtf8Printable(gid));
274  if (ugroup) {
275  gidInt = ugroup->gr_gid;
276  } else {
277  std::cerr << "setgid group %s not found." << qUtf8Printable(gid) << '\n';
278  return false;
279  }
280  }
281 
282  if (setgid(gidInt)) {
283  std::cerr << "Failed to set gid '%s'" << strerror(errno) << '\n';
284  return false;
285  }
286  std::cout << "setgid() to " << gidInt << '\n';
287 
288  if (noInitgroups || uid.isEmpty()) {
289  if (setgroups(0, nullptr)) {
290  std::cerr << "Failed to setgroups()" << '\n';
291  return false;
292  }
293  } else {
294  QByteArray uidname;
295  uint uidInt = uid.toUInt(&ok);
296  if (ok) {
297  const struct passwd *pw = getpwuid(uidInt);
298  if (pw) {
299  uidname = pw->pw_name;
300  }
301  } else {
302  uidname = uid.toUtf8();
303  }
304 
305  if (initgroups(uidname.constData(), gidInt)) {
306  std::cerr << "Failed to setgroups()" << '\n';
307  return false;
308  }
309  }
310  }
311 
312  if (!uid.isEmpty()) {
313  uint uidInt = uid.toUInt(&ok);
314  if (!ok) {
315  const struct passwd *upasswd = getpwnam(qUtf8Printable(uid));
316  if (upasswd) {
317  uidInt = upasswd->pw_uid;
318  } else {
319  std::cerr << "setuid user" << qUtf8Printable(uid) << "not found." << '\n';
320  return false;
321  }
322  }
323 
324  if (setuid(uidInt)) {
325  std::cerr << "Failed to set uid:" << strerror(errno) << '\n';
326  return false;
327  }
328  std::cout << "setuid() to " << uidInt << '\n';
329  }
330  return true;
331 }
332 
333 void UnixFork::chownSocket(const QString &filename, const QString &uidGid)
334 {
335  const QString owner = uidGid.section(u':', 0, 0);
336 
337  bool ok;
338  uid_t new_uid = owner.toUInt(&ok);
339 
340  if (!ok) {
341  const struct passwd *new_user = getpwnam(qUtf8Printable(owner));
342  if (!new_user) {
343  qFatal("unable to find user '%s'", qUtf8Printable(owner));
344  }
345  new_uid = new_user->pw_uid;
346  }
347 
348  gid_t new_gid = -1U;
349  const QString group = uidGid.section(u':', 1, 1);
350  if (!group.isEmpty()) {
351  new_gid = group.toUInt(&ok);
352  if (!ok) {
353  const struct group *new_group = getgrnam(qUtf8Printable(group));
354  if (!new_group) {
355  qFatal("unable to find group '%s'", qUtf8Printable(group));
356  }
357  new_gid = new_group->gr_gid;
358  }
359  }
360 
361  if (chown(qUtf8Printable(filename), new_uid, new_gid)) {
362  qFatal("chown() error '%s'", strerror(errno));
363  }
364 }
365 
366 #ifdef Q_OS_LINUX
367 // static int cpuSockets = -1;
368 
369 // socket/cores
370 int parseProcCpuinfo()
371 {
372  int cpuSockets = 1;
373  // std::pair<int, int> ret;
374 
375  // static QMutex mutex;
376  // QMutexLocker locker(&mutex);
377  QFile file(u"/proc/cpuinfo"_s);
378  if (!file.open(QFile::ReadOnly | QFile::Text)) {
379  qCWarning(C_SERVER_UNIX) << "Failed to open file" << file.errorString();
380  // cpuSockets = 1;
381  // cpuCores = QThread::idealThreadCount();
382  return cpuSockets;
383  }
384 
385  char buf[1024];
386  qint64 lineLength;
387  QByteArrayList physicalIds;
388  // cpuCores = 0;
389  while ((lineLength = file.readLine(buf, sizeof(buf))) != -1) {
390  const QByteArray line(buf, int(lineLength));
391  if (line.startsWith("physical id\t: ")) {
392  const QByteArray id = line.mid(14).trimmed();
393  if (!physicalIds.contains(id)) {
394  physicalIds.push_back(id);
395  }
396  } /* else if (line.startsWith("processor \t: ")) {
397  ++cpuCores;
398  }*/
399  }
400 
401  // if (cpuCores == 0) {
402  // cpuCores = QThread::idealThreadCount();
403  // }
404 
405  if (!physicalIds.isEmpty()) {
406  cpuSockets = physicalIds.size();
407  } else {
408  cpuSockets = 1;
409  }
410  return cpuSockets;
411 }
412 #endif
413 
414 int UnixFork::idealProcessCount()
415 {
416 #ifdef Q_OS_LINUX
417  static int cpuSockets = parseProcCpuinfo();
418 
419  return cpuSockets;
420 #else
421  return 1;
422 #endif
423 }
424 
425 int UnixFork::idealThreadCount()
426 {
427 #ifdef Q_OS_LINUX
428  static int cpuCores = qMax(1, QThread::idealThreadCount());
429 
430  return cpuCores;
431 #else
432  return qMax(1, QThread::idealThreadCount());
433 #endif
434 }
435 
436 void UnixFork::handleSigHup()
437 {
438  // do Qt stuff
439  // qDebug() << Q_FUNC_INFO << QCoreApplication::applicationPid();
440  // m_proc->kill();
441 }
442 
443 void UnixFork::handleSigTerm()
444 {
445  // do Qt stuff
446  // qDebug() << Q_FUNC_INFO << QCoreApplication::applicationPid();
447  // qApp->quit();
448  // m_proc->terminate();
449 }
450 
451 void UnixFork::handleSigInt()
452 {
453  // do Qt stuff
454  // qDebug() << Q_FUNC_INFO << QCoreApplication::applicationPid();
455  m_terminating = true;
456  if (m_child || (m_childs.isEmpty())) {
457  qDebug(C_SERVER_UNIX) << "SIGINT/SIGQUIT received, worker shutting down...";
458  Q_EMIT shutdown();
459  } else {
460  std::cout << "SIGINT/SIGQUIT received, terminating workers..." << '\n';
461  setupCheckChildTimer();
462 
463  static int count = 0;
464  if (count++ > 2) {
465  std::cout << "KILL workers..." << '\n';
466  killChild();
467  QTimer::singleShot(std::chrono::seconds{3}, qApp, &QCoreApplication::quit);
468  } else if (count > 1) {
469  terminateChild();
470  } else {
471  QTimer::singleShot(std::chrono::seconds{30}, this, [this]() {
472  std::cout << "workers terminating timeout, KILL ..." << '\n';
473  killChild();
474  QTimer::singleShot(std::chrono::seconds{30}, qApp, &QCoreApplication::quit);
475  });
476 
477  terminateChild();
478  }
479  }
480 }
481 
482 void UnixFork::handleSigChld()
483 {
484  pid_t p;
485  int status;
486 
487  while ((p = waitpid(-1, &status, WNOHANG)) > 0) {
488  /* Handle the death of pid p */
489  // qCDebug(C_SERVER_UNIX) << "SIGCHLD worker died" << p << WEXITSTATUS(status);
490  // SIGTERM is used when CHEAPED (ie post fork failed)
491  int exitStatus = WEXITSTATUS(status);
492 
493  Worker worker;
494  auto it = m_childs.constFind(p);
495  if (it != m_childs.constEnd()) {
496  worker = it.value();
497  m_childs.erase(it);
498  } else {
499  std::cout << "DAMN ! *UNKNOWN* worker (pid: " << p << ") died, killed by signal "
500  << exitStatus << " :( ignoring .." << '\n';
501  continue;
502  }
503 
504  if (WIFEXITED(status) && exitStatus == 15 && worker.restart == 0) {
505  // Child process cheaping
506  worker.null = true;
507  }
508 
509  if (!worker.null && !m_terminating) {
510  if (worker.restart == 0) {
511  std::cout << "DAMN ! worker " << worker.id << " (pid: " << p
512  << ") died, killed by signal " << exitStatus << " :( trying respawn .."
513  << '\n';
514  }
515  worker.restart = 0;
516  ++worker.respawn;
517  QTimer::singleShot(std::chrono::seconds{1}, this, &UnixFork::decreaseWorkerRespawn);
518  m_recreateWorker.push_back(worker);
519  qApp->quit();
520  } else if (!m_child && m_childs.isEmpty()) {
521  qApp->quit();
522  }
523  }
524 
525  if (m_checkChildRestart) {
526  bool allRestarted = true;
527  for (const auto &[key, value] : m_childs.asKeyValueRange()) {
528  if (value.restart) {
529  if (++value.restart > 10) {
530  killChild(key);
531  }
532  allRestarted = false;
533  }
534  }
535 
536  if (allRestarted) {
537  m_checkChildRestart->deleteLater();
538  m_checkChildRestart = nullptr;
539  }
540  }
541 }
542 
543 void UnixFork::setSched(Cutelyst::Server *server, int workerId, int workerCore)
544 {
545  int cpu_affinity = server->cpuAffinity();
546  if (cpu_affinity) {
547  char buf[4096];
548 
549  int pos =
550  snprintf(buf, 4096, "mapping worker %d core %d to CPUs:", workerId + 1, workerCore + 1);
551  if (pos < 25 || pos >= 4096) {
552  qCCritical(C_SERVER_UNIX) << "unable to initialize cpu affinity !!!";
553  exit(1);
554  }
555 #if defined(__linux__) || defined(__GNU_kFreeBSD__)
556  cpu_set_t cpuset;
557 #elif defined(__FreeBSD__)
558  cpuset_t cpuset;
559 #endif
560 #if defined(__linux__) || defined(__FreeBSD__) || defined(__GNU_kFreeBSD__)
561  int coreCount = idealThreadCount();
562 
563  int workerThreads = 1;
564  if (server->threads().compare(u"auto") == 0) {
565  workerThreads = coreCount;
566  } else if (server->threads().toInt() > 1) {
567  workerThreads = server->threads().toInt();
568  }
569 
570  int base_cpu;
571  if (workerThreads > 1) {
572  base_cpu = (workerId * workerThreads) + workerCore * cpu_affinity;
573  } else {
574  base_cpu = workerId * cpu_affinity;
575  }
576 
577  if (base_cpu >= coreCount) {
578  base_cpu = base_cpu % coreCount;
579  }
580 
581  CPU_ZERO(&cpuset);
582  for (int i = 0; i < cpu_affinity; i++) {
583  if (base_cpu >= coreCount) {
584  base_cpu = 0;
585  }
586  CPU_SET(base_cpu, &cpuset);
587  int ret = snprintf(buf + pos, 4096 - pos, " %d", base_cpu + 1);
588  if (ret < 2 || ret >= 4096) {
589  qCCritical(C_SERVER_UNIX) << "unable to initialize cpu affinity !!!";
590  exit(1);
591  }
592  pos += ret;
593  base_cpu++;
594  }
595 #endif
596 #if defined(__linux__) || defined(__GNU_kFreeBSD__)
597  if (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset)) {
598  qFatal("failed to sched_setaffinity()");
599  }
600 #elif defined(__FreeBSD__)
601  if (cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(cpuset), &cpuset)) {
602  qFatal("cpuset_setaffinity");
603  }
604 #endif
605  std::cout << buf << '\n';
606  }
607 }
608 
609 int UnixFork::setupUnixSignalHandlers()
610 {
611  setupSocketPair(false, true);
612 
613  // struct sigaction hup;
614  // hup.sa_handler = UnixFork::signalHandler;
615  // sigemptyset(&hup.sa_mask);
616  // hup.sa_flags = 0;
617  // hup.sa_flags |= SA_RESTART;
618 
619  // if (sigaction(SIGHUP, &hup, 0) > 0)
620  // return 1;
621 
622  // struct sigaction term;
623  // term.sa_handler = UnixFork::signalHandler;
624  // sigemptyset(&term.sa_mask);
625  // term.sa_flags |= SA_RESTART;
626 
627  // if (sigaction(SIGTERM, &term, 0) > 0)
628  // return 2;
629 
630  struct sigaction action;
631 
632  // qDebug() << Q_FUNC_INFO << QCoreApplication::applicationPid();
633 
634  memset(&action, 0, sizeof(struct sigaction));
635  action.sa_handler = UnixFork::signalHandler;
636  sigemptyset(&action.sa_mask);
637  action.sa_flags |= SA_RESTART;
638  if (sigaction(SIGINT, &action, nullptr) > 0) {
639  return SIGINT;
640  }
641 
642  memset(&action, 0, sizeof(struct sigaction));
643  action.sa_handler = UnixFork::signalHandler;
644  sigemptyset(&action.sa_mask);
645  action.sa_flags |= SA_RESTART;
646  if (sigaction(SIGQUIT, &action, nullptr) > 0) {
647  return SIGQUIT;
648  }
649 
650  memset(&action, 0, sizeof(struct sigaction));
651  action.sa_handler = UnixFork::signalHandler;
652  sigemptyset(&action.sa_mask);
653  action.sa_flags |= SA_RESTART;
654 
655  if (sigaction(SIGCHLD, &action, nullptr) > 0) {
656  return SIGCHLD;
657  }
658 
659  return 0;
660 }
661 
662 void UnixFork::setupSocketPair(bool closeSignalsFD, bool createPair)
663 {
664  if (closeSignalsFD) {
665  close(signalsFd[0]);
666  close(signalsFd[1]);
667  }
668 
669  if (createPair && ::socketpair(AF_UNIX, SOCK_STREAM, 0, signalsFd)) {
670  qFatal("Couldn't create SIGNALS socketpair");
671  }
672  delete m_signalNotifier;
673 
674  m_signalNotifier = new QSocketNotifier(signalsFd[1], QSocketNotifier::Read, this);
675  connect(m_signalNotifier, &QSocketNotifier::activated, this, [this]() {
676  char signal;
677  read(signalsFd[1], &signal, sizeof(signal));
678 
679  // qCDebug(C_SERVER_UNIX) << "Got signal:" << static_cast<int>(signal) << "pid:" <<
680  // QCoreApplication::applicationPid();
681  switch (signal) {
682  case SIGCHLD:
683  QTimer::singleShot(std::chrono::seconds{0}, this, &UnixFork::handleSigChld);
684  break;
685  case SIGINT:
686  case SIGQUIT:
687  handleSigInt();
688  break;
689  default:
690  break;
691  }
692  });
693 }
694 
695 bool UnixFork::createChild(const Worker &worker, bool respawn)
696 {
697  if (m_child) {
698  return false;
699  }
700 
701  delete m_signalNotifier;
702  m_signalNotifier = nullptr;
703 
704  qint64 childPID = fork();
705 
706  if (childPID >= 0) {
707  if (childPID == 0) {
708  if (worker.respawn >= 5) {
709  std::cout << "SERVER worker " << worker.id << " respawned too much, sleeping a bit"
710  << '\n';
711  sleep(2);
712  }
713 
714 #if defined(HAS_EventLoopEPoll)
715  auto epoll = qobject_cast<EventDispatcherEPoll *>(QAbstractEventDispatcher::instance());
716  if (epoll) {
717  epoll->reinstall();
718  }
719 #endif
720 
721  setupSocketPair(true, true);
722 
723  m_child = true;
724  postFork(worker.id);
725 
726  int ret = qApp->exec();
727  _exit(ret);
728  } else {
729  setupSocketPair(false, false);
730 
731  if (respawn) {
732  std::cout << "Respawned SERVER worker " << worker.id << " (new pid: " << childPID
733  << ", cores: " << m_threads << ")" << '\n';
734  } else {
735  if (m_processes == 1) {
736  std::cout << "spawned SERVER worker (and the only) (pid: " << childPID
737  << ", cores: " << m_threads << ")" << '\n';
738  } else {
739  std::cout << "spawned SERVER worker " << worker.id << " (pid: " << childPID
740  << ", cores: " << m_threads << ")" << '\n';
741  }
742  }
743  m_childs.insert(childPID, worker);
744  return true;
745  }
746  } else {
747  qFatal("Fork failed, quitting!!!!!!");
748  }
749 
750  return false;
751 }
752 
753 #include "moc_unixfork.cpp"
void activated(QSocketDescriptor socket, QSocketNotifier::Type type)
QFuture< ArgsType< Signal >> connect(Sender *sender, Signal signal)
QAbstractEventDispatcher * instance(QThread *thread)
QByteArray trimmed() const const
void push_back(parameter_type value)
Implements a web server.
Definition: server.h:59
qsizetype size() const const
virtual void killChild() override
Definition: unixfork.cpp:170
void timeout()
int toInt(bool *ok, int base) const const
bool isEmpty() const const
bool isEmpty() const const
const char * constData() const const
qint64 applicationPid()
QByteArray mid(qsizetype pos, qsizetype len) const const
virtual void terminateChild() override
Definition: unixfork.cpp:184
bool contains(const AT &value) const const
QByteArray simplified() const const
qlonglong toLongLong(bool *ok, int base) const const
QString threads
Definition: server.h:152
int idealThreadCount()
virtual void restart() override
Definition: unixfork.cpp:101
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
virtual int exec(bool lazy, bool master) override
Definition: unixfork.cpp:74
qsizetype size() const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
virtual bool continueMaster(int *exit=nullptr) override
Definition: unixfork.cpp:68
uint toUInt(bool *ok, int base) const const
QByteArray toUtf8() const const