9#if defined(HAS_EventLoopEPoll)
10# include "EventLoopEPoll/eventdispatcher_epoll.h"
13#if defined(__FreeBSD__) || defined(__GNU_kFreeBSD__)
14# include <sys/cpuset.h>
15# include <sys/param.h>
25#include <sys/socket.h>
31#include <QAbstractEventDispatcher>
32#include <QCoreApplication>
34#include <QLoggingCategory>
36#include <QSocketNotifier>
40Q_LOGGING_CATEGORY(C_SERVER_UNIX,
"cutelyst.server.unix", QtWarningMsg)
42#pragma GCC diagnostic push
43#pragma GCC diagnostic ignored "-Wunused-result"
49UnixFork::UnixFork(
int process,
int threads,
bool setupSignals, QObject *parent)
52 , m_processes(process)
55 setupUnixSignalHandlers();
75 std::cout <<
"spawned WSGI master process (pid: " << QCoreApplication::applicationPid()
84 std::cerr <<
"*** Master mode must be set on lazy mode" <<
'\n';
88 if (m_processes > 0) {
101 for (
const auto &[key, value] : m_childs.asKeyValueRange()) {
106 setupCheckChildTimer();
109int UnixFork::internalExec()
112 bool respawn =
false;
114 if (!createProcess(respawn)) {
119 installTouchReload();
124 }
while (!m_terminating);
129bool UnixFork::createProcess(
bool respawn)
132 m_recreateWorker.removeIf([
this, respawn](
Worker worker) {
134 if (!createChild(worker, respawn)) {
135 std::cout <<
"CHEAPING worker: " << worker.id <<
'\n';
142 for (
int i = 0; i < m_processes; ++i) {
146 createChild(worker, respawn);
150 return !m_childs.empty();
153void UnixFork::decreaseWorkerRespawn()
155 int missingRespawn = 0;
156 for (
const auto &[key, value] : m_childs.asKeyValueRange()) {
157 if (value.respawn > 0) {
159 missingRespawn += value.respawn;
163 if (missingRespawn) {
164 QTimer::singleShot(std::chrono::seconds{1},
this, &UnixFork::decreaseWorkerRespawn);
170 const auto childs = m_childs.keys();
171 for (qint64 pid : childs) {
179 ::kill(pid_t(pid), SIGKILL);
184 const auto childs = m_childs.keys();
185 for (qint64 pid : childs) {
193 ::kill(pid_t(pid), SIGQUIT);
196void UnixFork::stopWSGI(
const QString &pidfile)
199 if (!file.open(QFile::ReadOnly | QFile::Text)) {
200 std::cerr <<
"Failed open pid file " << qPrintable(pidfile) <<
'\n';
204 QByteArray piddata = file.readLine().simplified();
205 qint64 pid = piddata.toLongLong();
207 std::cerr <<
"Failed read pid file " << qPrintable(pidfile) <<
'\n';
211 ::kill(pid_t(pid), SIGINT);
215bool UnixFork::setUmask(
const QByteArray &valueStr)
217 if (valueStr.size() < 3) {
218 std::cerr <<
"umask too small" <<
'\n';
222 const char *value = valueStr.constData();
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');
229 mode = (mode << 3) + (value[1] -
'0');
230 mode = (mode << 3) + (value[2] -
'0');
231 mode = (mode << 3) + (value[3] -
'0');
233 std::cout <<
"umask() " << value <<
'\n';
240void UnixFork::signalHandler(
int signal)
244 write(signalsFd[0], &sig,
sizeof(sig));
247void UnixFork::setupCheckChildTimer()
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);
256void UnixFork::postFork(
int workerId)
259 delete m_checkChildRestart;
261 Q_EMIT forked(workerId - 1);
264bool UnixFork::setGidUid(
const QString &gid,
const QString &uid,
bool noInitgroups)
268 if (!gid.isEmpty()) {
269 uint gidInt = gid.toUInt(&ok);
271 struct group *ugroup = getgrnam(qUtf8Printable(gid));
273 gidInt = ugroup->gr_gid;
275 std::cerr <<
"setgid group %s not found." << qUtf8Printable(gid) <<
'\n';
280 if (setgid(gidInt)) {
281 std::cerr <<
"Failed to set gid '%s'" << strerror(errno) <<
'\n';
284 std::cout <<
"setgid() to " << gidInt <<
'\n';
286 if (noInitgroups || uid.isEmpty()) {
287 if (setgroups(0,
nullptr)) {
288 std::cerr <<
"Failed to setgroups()" <<
'\n';
293 uint uidInt = uid.toUInt(&ok);
295 struct passwd *pw = getpwuid(uidInt);
297 uidname = pw->pw_name;
300 uidname = uid.toUtf8();
303 if (initgroups(uidname.constData(), gidInt)) {
304 std::cerr <<
"Failed to setgroups()" <<
'\n';
310 if (!uid.isEmpty()) {
311 uint uidInt = uid.toUInt(&ok);
313 struct passwd *upasswd = getpwnam(qUtf8Printable(uid));
315 uidInt = upasswd->pw_uid;
317 std::cerr <<
"setuid user" << qUtf8Printable(uid) <<
"not found." <<
'\n';
322 if (setuid(uidInt)) {
323 std::cerr <<
"Failed to set uid:" << strerror(errno) <<
'\n';
326 std::cout <<
"setuid() to " << uidInt <<
'\n';
331void UnixFork::chownSocket(
const QString &filename,
const QString &uidGid)
333 struct group *new_group =
nullptr;
334 struct passwd *new_user =
nullptr;
336 const QString owner = uidGid.section(QLatin1Char(
':'), 0, 0);
339 uid_t new_uid = owner.toUInt(&ok);
342 new_user = getpwnam(qUtf8Printable(owner));
344 qFatal(
"unable to find user '%s'", qUtf8Printable(owner));
346 new_uid = new_user->pw_uid;
350 const QString group = uidGid.section(QLatin1Char(
':'), 1, 1);
351 if (!group.isEmpty()) {
352 new_gid = group.toUInt(&ok);
354 new_group = getgrnam(qUtf8Printable(group));
356 qFatal(
"unable to find group '%s'", qUtf8Printable(group));
358 new_gid = new_group->gr_gid;
362 if (chown(qUtf8Printable(filename), new_uid, new_gid)) {
363 qFatal(
"chown() error '%s'", strerror(errno));
371int parseProcCpuinfo()
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();
388 QByteArrayList physicalIds;
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);
406 if (!physicalIds.isEmpty()) {
407 cpuSockets = physicalIds.size();
415int UnixFork::idealProcessCount()
418 static int cpuSockets = parseProcCpuinfo();
426int UnixFork::idealThreadCount()
429 static int cpuCores = qMax(1, QThread::idealThreadCount());
433 return qMax(1, QThread::idealThreadCount());
437void UnixFork::handleSigHup()
444void UnixFork::handleSigTerm()
452void UnixFork::handleSigInt()
456 m_terminating =
true;
457 if (m_child || (m_childs.isEmpty())) {
458 qDebug(C_SERVER_UNIX) <<
"SIGINT/SIGQUIT received, worker shutting down...";
461 std::cout <<
"SIGINT/SIGQUIT received, terminating workers..." <<
'\n';
462 setupCheckChildTimer();
464 static int count = 0;
466 std::cout <<
"KILL workers..." <<
'\n';
468 QTimer::singleShot(std::chrono::seconds{3}, qApp, &QCoreApplication::quit);
469 }
else if (count > 1) {
472 QTimer::singleShot(std::chrono::seconds{30},
this, [
this]() {
473 std::cout <<
"workers terminating timeout, KILL ..." <<
'\n';
475 QTimer::singleShot(std::chrono::seconds{30}, qApp, &QCoreApplication::quit);
483void UnixFork::handleSigChld()
488 while ((p = waitpid(-1, &status, WNOHANG)) > 0) {
492 int exitStatus = WEXITSTATUS(status);
495 auto it = m_childs.constFind(p);
496 if (it != m_childs.constEnd()) {
500 std::cout <<
"DAMN ! *UNKNOWN* worker (pid: " << p <<
") died, killed by signal "
501 << exitStatus <<
" :( ignoring .." <<
'\n';
505 if (WIFEXITED(status) && exitStatus == 15 && worker.restart == 0) {
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 .."
518 QTimer::singleShot(std::chrono::seconds{1},
this, &UnixFork::decreaseWorkerRespawn);
519 m_recreateWorker.push_back(worker);
521 }
else if (!m_child && m_childs.isEmpty()) {
526 if (m_checkChildRestart) {
527 bool allRestarted =
true;
528 for (
const auto &[key, value] : m_childs.asKeyValueRange()) {
530 if (++value.restart > 10) {
533 allRestarted =
false;
538 m_checkChildRestart->deleteLater();
539 m_checkChildRestart =
nullptr;
544void UnixFork::setSched(
Cutelyst::Server *wsgi,
int workerId,
int workerCore)
546 int cpu_affinity = wsgi->cpuAffinity();
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 !!!";
556#if defined(__linux__) || defined(__GNU_kFreeBSD__)
558#elif defined(__FreeBSD__)
561#if defined(__linux__) || defined(__FreeBSD__) || defined(__GNU_kFreeBSD__)
562 int coreCount = idealThreadCount();
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();
572 if (workerThreads > 1) {
573 base_cpu = (workerId * workerThreads) + workerCore * cpu_affinity;
575 base_cpu = workerId * cpu_affinity;
578 if (base_cpu >= coreCount) {
579 base_cpu = base_cpu % coreCount;
583 for (
int i = 0; i < cpu_affinity; i++) {
584 if (base_cpu >= coreCount) {
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 !!!";
597#if defined(__linux__) || defined(__GNU_kFreeBSD__)
598 if (sched_setaffinity(0,
sizeof(cpu_set_t), &cpuset)) {
599 qFatal(
"failed to sched_setaffinity()");
601#elif defined(__FreeBSD__)
602 if (cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1,
sizeof(cpuset), &cpuset)) {
603 qFatal(
"cpuset_setaffinity");
606 std::cout << buf <<
'\n';
610int UnixFork::setupUnixSignalHandlers()
612 setupSocketPair(
false,
true);
631 struct sigaction action;
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) {
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) {
651 memset(&action, 0,
sizeof(
struct sigaction));
652 action.sa_handler = UnixFork::signalHandler;
653 sigemptyset(&action.sa_mask);
654 action.sa_flags |= SA_RESTART;
656 if (sigaction(SIGCHLD, &action,
nullptr) > 0) {
663void UnixFork::setupSocketPair(
bool closeSignalsFD,
bool createPair)
665 if (closeSignalsFD) {
670 if (createPair && ::socketpair(AF_UNIX, SOCK_STREAM, 0, signalsFd)) {
671 qFatal(
"Couldn't create SIGNALS socketpair");
673 delete m_signalNotifier;
675 m_signalNotifier =
new QSocketNotifier(signalsFd[1], QSocketNotifier::Read,
this);
676 connect(m_signalNotifier, &QSocketNotifier::activated,
this, [
this]() {
678 read(signalsFd[1], &signal,
sizeof(signal));
684 QTimer::singleShot(std::chrono::seconds{0},
this, &UnixFork::handleSigChld);
696bool UnixFork::createChild(
const Worker &worker,
bool respawn)
702 delete m_signalNotifier;
703 m_signalNotifier =
nullptr;
705 qint64 childPID = fork();
709 if (worker.respawn >= 5) {
710 std::cout <<
"WSGI worker " << worker.id <<
" respawned too much, sleeping a bit"
715#if defined(HAS_EventLoopEPoll)
716 auto epoll = qobject_cast<EventDispatcherEPoll *>(QAbstractEventDispatcher::instance());
722 setupSocketPair(
true,
true);
727 int ret = qApp->exec();
730 setupSocketPair(
false,
false);
733 std::cout <<
"Respawned WSGI worker " << worker.id <<
" (new pid: " << childPID
734 <<
", cores: " << m_threads <<
")" <<
'\n';
736 if (m_processes == 1) {
737 std::cout <<
"spawned WSGI worker (and the only) (pid: " << childPID
738 <<
", cores: " << m_threads <<
")" <<
'\n';
740 std::cout <<
"spawned WSGI worker " << worker.id <<
" (pid: " << childPID
741 <<
", cores: " << m_threads <<
")" <<
'\n';
744 m_childs.insert(childPID, worker);
748 qFatal(
"Fork failed, quitting!!!!!!");
754#include "moc_unixfork.cpp"
virtual void terminateChild() override
virtual bool continueMaster(int *exit=nullptr) override
virtual void restart() override
virtual int exec(bool lazy, bool master) override
virtual void killChild() override