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"
45using namespace Qt::StringLiterals;
51UnixFork::UnixFork(
int process,
int threads,
bool setupSignals,
QObject *parent)
54 , m_processes(process)
57 setupUnixSignalHandlers();
86 std::cerr <<
"*** Master mode must be set on lazy mode" <<
'\n';
90 if (m_processes > 0) {
108 setupCheckChildTimer();
111int UnixFork::internalExec()
114 bool respawn =
false;
116 if (!createProcess(respawn)) {
121 installTouchReload();
126 }
while (!m_terminating);
131bool UnixFork::createProcess(
bool respawn)
134 m_recreateWorker.removeIf([
this, respawn](
Worker worker) {
136 if (!createChild(worker, respawn)) {
137 std::cout <<
"CHEAPING worker: " << worker.id <<
'\n';
144 for (
int i = 0; i < m_processes; ++i) {
148 createChild(worker, respawn);
152 return !m_childs.
empty();
155void UnixFork::decreaseWorkerRespawn()
157 int missingRespawn = 0;
158 for (
const auto &[key, value] : m_childs.asKeyValueRange()) {
159 if (value.respawn > 0) {
161 missingRespawn += value.respawn;
165 if (missingRespawn) {
172 const auto childs = m_childs.
keys();
173 for (qint64 pid : childs) {
181 ::kill(pid_t(pid), SIGKILL);
186 const auto childs = m_childs.
keys();
187 for (qint64 pid : childs) {
195 ::kill(pid_t(pid), SIGQUIT);
198void UnixFork::stopSERVER(
const QString &pidfile)
202 std::cerr <<
"Failed open pid file " << qPrintable(pidfile) <<
'\n';
209 std::cerr <<
"Failed read pid file " << qPrintable(pidfile) <<
'\n';
213 ::kill(pid_t(pid), SIGINT);
217bool UnixFork::setUmask(
const QByteArray &valueStr)
219 if (valueStr.
size() < 3) {
220 std::cerr <<
"umask too small" <<
'\n';
224 const char *value = valueStr.
constData();
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');
231 mode = (mode << 3) + (value[1] -
'0');
232 mode = (mode << 3) + (value[2] -
'0');
233 mode = (mode << 3) + (value[3] -
'0');
235 std::cout <<
"umask() " << value <<
'\n';
242void UnixFork::signalHandler(
int signal)
246 write(signalsFd[0], &sig,
sizeof(sig));
249void UnixFork::setupCheckChildTimer()
251 if (!m_checkChildRestart) {
252 m_checkChildRestart =
new QTimer(
this);
253 m_checkChildRestart->
start(std::chrono::milliseconds{500});
258void UnixFork::postFork(
int workerId)
261 delete m_checkChildRestart;
263 Q_EMIT forked(workerId - 1);
266bool UnixFork::setGidUid(
const QString &gid,
const QString &uid,
bool noInitgroups)
271 uint gidInt = gid.
toUInt(&ok);
273 const struct group *ugroup = getgrnam(qUtf8Printable(gid));
275 gidInt = ugroup->gr_gid;
277 std::cerr <<
"setgid group %s not found." << qUtf8Printable(gid) <<
'\n';
282 if (setgid(gidInt)) {
283 std::cerr <<
"Failed to set gid '%s'" << strerror(errno) <<
'\n';
286 std::cout <<
"setgid() to " << gidInt <<
'\n';
288 if (noInitgroups || uid.
isEmpty()) {
289 if (setgroups(0,
nullptr)) {
290 std::cerr <<
"Failed to setgroups()" <<
'\n';
295 uint uidInt = uid.
toUInt(&ok);
297 const struct passwd *pw = getpwuid(uidInt);
299 uidname = pw->pw_name;
305 if (initgroups(uidname.
constData(), gidInt)) {
306 std::cerr <<
"Failed to setgroups()" <<
'\n';
313 uint uidInt = uid.
toUInt(&ok);
315 const struct passwd *upasswd = getpwnam(qUtf8Printable(uid));
317 uidInt = upasswd->pw_uid;
319 std::cerr <<
"setuid user" << qUtf8Printable(uid) <<
"not found." <<
'\n';
324 if (setuid(uidInt)) {
325 std::cerr <<
"Failed to set uid:" << strerror(errno) <<
'\n';
328 std::cout <<
"setuid() to " << uidInt <<
'\n';
333void UnixFork::chownSocket(
const QString &filename,
const QString &uidGid)
338 uid_t new_uid = owner.
toUInt(&ok);
341 const struct passwd *new_user = getpwnam(qUtf8Printable(owner));
343 qFatal(
"unable to find user '%s'", qUtf8Printable(owner));
345 new_uid = new_user->pw_uid;
351 new_gid = group.
toUInt(&ok);
353 const struct group *new_group = getgrnam(qUtf8Printable(group));
355 qFatal(
"unable to find group '%s'", qUtf8Printable(group));
357 new_gid = new_group->gr_gid;
361 if (chown(qUtf8Printable(filename), new_uid, new_gid)) {
362 qFatal(
"chown() error '%s'", strerror(errno));
370int parseProcCpuinfo()
377 QFile file(u
"/proc/cpuinfo"_s);
379 qCWarning(C_SERVER_UNIX) <<
"Failed to open file" << file.errorString();
389 while ((lineLength = file.readLine(buf,
sizeof(buf))) != -1) {
391 if (line.startsWith(
"physical id\t: ")) {
406 cpuSockets = physicalIds.
size();
414int UnixFork::idealProcessCount()
417 static int cpuSockets = parseProcCpuinfo();
425int UnixFork::idealThreadCount()
436void UnixFork::handleSigHup()
443void UnixFork::handleSigTerm()
451void UnixFork::handleSigInt()
455 m_terminating =
true;
456 if (m_child || (m_childs.
isEmpty())) {
457 qDebug(C_SERVER_UNIX) <<
"SIGINT/SIGQUIT received, worker shutting down...";
460 std::cout <<
"SIGINT/SIGQUIT received, terminating workers..." <<
'\n';
461 setupCheckChildTimer();
463 static int count = 0;
465 std::cout <<
"KILL workers..." <<
'\n';
468 }
else if (count > 1) {
472 std::cout <<
"workers terminating timeout, KILL ..." <<
'\n';
482void UnixFork::handleSigChld()
487 while ((p = waitpid(-1, &status, WNOHANG)) > 0) {
491 int exitStatus = WEXITSTATUS(status);
499 std::cout <<
"DAMN ! *UNKNOWN* worker (pid: " << p <<
") died, killed by signal "
500 << exitStatus <<
" :( ignoring .." <<
'\n';
504 if (WIFEXITED(status) && exitStatus == 15 && worker.restart == 0) {
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 .."
518 m_recreateWorker.push_back(worker);
520 }
else if (!m_child && m_childs.
isEmpty()) {
525 if (m_checkChildRestart) {
526 bool allRestarted =
true;
527 for (
const auto &[key, value] : m_childs.asKeyValueRange()) {
529 if (++value.restart > 10) {
532 allRestarted =
false;
538 m_checkChildRestart =
nullptr;
543void UnixFork::setSched(
Cutelyst::Server *server,
int workerId,
int workerCore)
545 int cpu_affinity = server->cpuAffinity();
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 !!!";
555#if defined(__linux__) || defined(__GNU_kFreeBSD__)
557#elif defined(__FreeBSD__)
560#if defined(__linux__) || defined(__FreeBSD__) || defined(__GNU_kFreeBSD__)
561 int coreCount = idealThreadCount();
563 int workerThreads = 1;
565 workerThreads = coreCount;
571 if (workerThreads > 1) {
572 base_cpu = (workerId * workerThreads) + workerCore * cpu_affinity;
574 base_cpu = workerId * cpu_affinity;
577 if (base_cpu >= coreCount) {
578 base_cpu = base_cpu % coreCount;
582 for (
int i = 0; i < cpu_affinity; i++) {
583 if (base_cpu >= coreCount) {
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 !!!";
596#if defined(__linux__) || defined(__GNU_kFreeBSD__)
597 if (sched_setaffinity(0,
sizeof(cpu_set_t), &cpuset)) {
598 qFatal(
"failed to sched_setaffinity()");
600#elif defined(__FreeBSD__)
601 if (cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1,
sizeof(cpuset), &cpuset)) {
602 qFatal(
"cpuset_setaffinity");
605 std::cout << buf <<
'\n';
609int UnixFork::setupUnixSignalHandlers()
611 setupSocketPair(
false,
true);
630 struct sigaction action;
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) {
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) {
650 memset(&action, 0,
sizeof(
struct sigaction));
651 action.sa_handler = UnixFork::signalHandler;
652 sigemptyset(&action.sa_mask);
653 action.sa_flags |= SA_RESTART;
655 if (sigaction(SIGCHLD, &action,
nullptr) > 0) {
662void UnixFork::setupSocketPair(
bool closeSignalsFD,
bool createPair)
664 if (closeSignalsFD) {
669 if (createPair && ::socketpair(AF_UNIX, SOCK_STREAM, 0, signalsFd)) {
670 qFatal(
"Couldn't create SIGNALS socketpair");
672 delete m_signalNotifier;
677 read(signalsFd[1], &signal,
sizeof(signal));
695bool UnixFork::createChild(
const Worker &worker,
bool respawn)
701 delete m_signalNotifier;
702 m_signalNotifier =
nullptr;
704 qint64 childPID = fork();
708 if (worker.respawn >= 5) {
709 std::cout <<
"SERVER worker " << worker.id <<
" respawned too much, sleeping a bit"
714#if defined(HAS_EventLoopEPoll)
721 setupSocketPair(
true,
true);
726 int ret = qApp->exec();
729 setupSocketPair(
false,
false);
732 std::cout <<
"Respawned SERVER worker " << worker.id <<
" (new pid: " << childPID
733 <<
", cores: " << m_threads <<
")" <<
'\n';
735 if (m_processes == 1) {
736 std::cout <<
"spawned SERVER worker (and the only) (pid: " << childPID
737 <<
", cores: " << m_threads <<
")" <<
'\n';
739 std::cout <<
"spawned SERVER worker " << worker.id <<
" (pid: " << childPID
740 <<
", cores: " << m_threads <<
")" <<
'\n';
743 m_childs.
insert(childPID, worker);
747 qFatal(
"Fork failed, quitting!!!!!!");
753#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
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
QHash::const_iterator constEnd() const const
QHash::const_iterator constFind(const Key &key) 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
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
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