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> 27 #include <sys/types.h> 31 #include <QAbstractEventDispatcher> 32 #include <QCoreApplication> 34 #include <QLoggingCategory> 36 #include <QSocketNotifier> 40 Q_LOGGING_CATEGORY(C_SERVER_UNIX,
"cutelyst.server.unix", QtWarningMsg)
42 #pragma GCC diagnostic push 43 #pragma GCC diagnostic ignored "-Wunused-result" 51 UnixFork::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) {
103 for (
const auto &[key, value] : m_childs.asKeyValueRange()) {
108 setupCheckChildTimer();
111 int UnixFork::internalExec()
114 bool respawn =
false;
116 if (!createProcess(respawn)) {
121 installTouchReload();
126 }
while (!m_terminating);
131 bool 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();
155 void 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);
198 void 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);
217 bool 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';
242 void UnixFork::signalHandler(
int signal)
246 write(signalsFd[0], &sig,
sizeof(sig));
249 void UnixFork::setupCheckChildTimer()
251 if (!m_checkChildRestart) {
252 m_checkChildRestart =
new QTimer(
this);
253 m_checkChildRestart->start(std::chrono::milliseconds{500});
258 void UnixFork::postFork(
int workerId)
261 delete m_checkChildRestart;
263 Q_EMIT forked(workerId - 1);
266 bool 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';
333 void 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));
370 int 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();
414 int UnixFork::idealProcessCount()
417 static int cpuSockets = parseProcCpuinfo();
425 int UnixFork::idealThreadCount()
436 void UnixFork::handleSigHup()
443 void UnixFork::handleSigTerm()
451 void 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';
482 void UnixFork::handleSigChld()
487 while ((p = waitpid(-1, &status, WNOHANG)) > 0) {
491 int exitStatus = WEXITSTATUS(status);
494 auto it = m_childs.constFind(p);
495 if (it != m_childs.constEnd()) {
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;
537 m_checkChildRestart->deleteLater();
538 m_checkChildRestart =
nullptr;
543 void 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';
609 int 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) {
662 void 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));
695 bool 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" 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)
qsizetype size() const const
virtual void killChild() override
int toInt(bool *ok, int base) const const
bool isEmpty() const const
bool isEmpty() const const
const char * constData() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
virtual void terminateChild() override
bool contains(const AT &value) const const
QByteArray simplified() const const
qlonglong toLongLong(bool *ok, int base) const const
virtual void restart() override
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
virtual int exec(bool lazy, bool master) override
qsizetype size() const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
virtual bool continueMaster(int *exit=nullptr) override
uint toUInt(bool *ok, int base) const const
QByteArray toUtf8() const const