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