cutelyst 5.0.0
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
40Q_LOGGING_CATEGORY(C_SERVER_UNIX, "cutelyst.server.unix", QtWarningMsg)
41
42#pragma GCC diagnostic push
43#pragma GCC diagnostic ignored "-Wunused-result"
44
45using namespace Qt::StringLiterals;
46
47namespace {
48int signalsFd[2];
49} // namespace
50
51UnixFork::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
61UnixFork::~UnixFork()
62{
63 if (m_child) {
64 _exit(0);
65 }
66}
67
69{
70 Q_UNUSED(exit)
71 return true;
72}
73
74int 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
111int 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
131bool 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
155void 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
178void 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
192void UnixFork::terminateChild(qint64 pid)
193{
194 // qCDebug(C_SERVER_UNIX) << "SIGQUIT " << pid;
195 ::kill(pid_t(pid), SIGQUIT);
196}
197
198void 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
217bool 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
242void 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
249void 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
258void 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
266bool 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
333void 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
370int 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
414int 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
425int 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
436void UnixFork::handleSigHup()
437{
438 // do Qt stuff
439 // qDebug() << Q_FUNC_INFO << QCoreApplication::applicationPid();
440 // m_proc->kill();
441}
442
443void UnixFork::handleSigTerm()
444{
445 // do Qt stuff
446 // qDebug() << Q_FUNC_INFO << QCoreApplication::applicationPid();
447 // qApp->quit();
448 // m_proc->terminate();
449}
450
451void 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) {
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
478 }
479 }
480}
481
482void 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
543void 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
609int 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
662void 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
695bool 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"
Implements a web server.
Definition server.h:60
QString threads
Definition server.h:150
virtual void terminateChild() override
Definition unixfork.cpp:184
virtual bool continueMaster(int *exit=nullptr) override
Definition unixfork.cpp:68
virtual void restart() override
Definition unixfork.cpp:101
virtual int exec(bool lazy, bool master) override
Definition unixfork.cpp:74
virtual void killChild() override
Definition unixfork.cpp:170
QAbstractEventDispatcher * instance(QThread *thread)
const char * constData() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray simplified() const const
qsizetype size() const const
qlonglong toLongLong(bool *ok, int base) const const
QByteArray trimmed() const const
qint64 applicationPid()
auto asKeyValueRange() &
QHash::const_iterator constEnd() const const
QHash::const_iterator constFind(const Key &key) const const
bool empty() const const
QHash::iterator erase(QHash::const_iterator pos)
QHash::iterator insert(const Key &key, const T &value)
bool isEmpty() const const
QList< Key > keys() const const
bool contains(const AT &value) const const
bool isEmpty() const const
void push_back(QList::parameter_type value)
qsizetype size() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void activated(QSocketDescriptor socket, QSocketNotifier::Type type)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
bool isEmpty() const const
QString section(QChar sep, qsizetype start, qsizetype end, QString::SectionFlags flags) const const
int toInt(bool *ok, int base) const const
uint toUInt(bool *ok, int base) const const
QByteArray toUtf8() const const
int idealThreadCount()
void start()
void timeout()