diff --git a/src/app/daemon.cpp b/src/app/daemon.cpp index e9a47ab..1d7236f 100644 --- a/src/app/daemon.cpp +++ b/src/app/daemon.cpp @@ -21,6 +21,7 @@ #include #include #else +#include #include #include #include @@ -77,21 +78,9 @@ Daemon::Daemon(const std::string& name) { } -void Daemon::stop() { -#ifdef _WIN32 - running_ = false; -#else - running_ = false; -#endif -} +void Daemon::stop() { running_ = false; } -bool Daemon::isRunning() const { -#ifdef _WIN32 - return running_; -#else - return running_; -#endif -} +bool Daemon::isRunning() const { return running_; } bool Daemon::start(MainLoopFunc loop) { #ifdef _WIN32 @@ -102,15 +91,77 @@ bool Daemon::start(MainLoopFunc loop) { running_ = true; return runWithRestart(loop); #else - // Linux: Use traditional daemonization + // linux: Daemonize first, then run with restart monitoring instance_ = this; - runUnix(loop); - return true; + + // check if running from terminal before fork + bool from_terminal = + (isatty(STDIN_FILENO) != 0) || (isatty(STDOUT_FILENO) != 0); + + // first fork: detach from terminal + pid_t pid = fork(); + if (pid < 0) { + std::cerr << "Failed to fork daemon process" << std::endl; + return false; + } + if (pid > 0) _exit(0); + + if (setsid() < 0) { + std::cerr << "Failed to create new session" << std::endl; + return false; + } + + pid = fork(); + if (pid < 0) { + std::cerr << "Failed to fork daemon process (second fork)" << std::endl; + return false; + } + if (pid > 0) _exit(0); + + umask(0); + chdir("/"); + + // redirect file descriptors: keep stdout/stderr if from terminal, else + // redirect to /dev/null + int fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + dup2(fd, STDIN_FILENO); + if (!from_terminal) { + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + } + if (fd > 2) close(fd); + } + + // set up signal handlers + signal(SIGTERM, [](int) { + if (instance_) instance_->stop(); + }); + signal(SIGINT, [](int) { + if (instance_) instance_->stop(); + }); + + // ignore SIGPIPE + signal(SIGPIPE, SIG_IGN); + + // set up SIGCHLD handler to reap zombie processes + struct sigaction sa_chld; + sa_chld.sa_handler = [](int) { + // reap zombie processes + while (waitpid(-1, nullptr, WNOHANG) > 0) { + // continue until no more zombie children + } + }; + sigemptyset(&sa_chld.sa_mask); + sa_chld.sa_flags = SA_RESTART | SA_NOCLDSTOP; + sigaction(SIGCHLD, &sa_chld, nullptr); + + running_ = true; + return runWithRestart(loop); #endif } #ifdef _WIN32 -// windows: execute loop and catch C++ exceptions static int RunLoopCatchCpp(Daemon::MainLoopFunc& loop) { try { loop(); @@ -124,12 +175,11 @@ static int RunLoopCatchCpp(Daemon::MainLoopFunc& loop) { } } -// windows: Use SEH wrapper function to catch system-level crashes static int RunLoopWithSEH(Daemon::MainLoopFunc& loop) { __try { return RunLoopCatchCpp(loop); } __except (EXCEPTION_EXECUTE_HANDLER) { - // Catch system-level crashes (access violation, divide by zero, etc.) + // catch system-level crashes (access violation, divide by zero, etc.) DWORD code = GetExceptionCode(); std::cerr << "System crash detected (SEH exception code: 0x" << std::hex << code << std::dec << ")" << std::endl; @@ -138,9 +188,7 @@ static int RunLoopWithSEH(Daemon::MainLoopFunc& loop) { } #endif -// run function with restart logic (infinite restart) -// use child process monitoring: parent process creates child process to run -// main program, monitors child process status, restarts on crash +// run with restart logic: parent monitors child process and restarts on crash bool Daemon::runWithRestart(MainLoopFunc loop) { int restart_count = 0; std::string exe_path = GetExecutablePath(); @@ -148,7 +196,6 @@ bool Daemon::runWithRestart(MainLoopFunc loop) { std::cerr << "Failed to get executable path, falling back to direct execution" << std::endl; - // fallback to direct execution while (isRunning()) { try { loop(); @@ -170,7 +217,6 @@ bool Daemon::runWithRestart(MainLoopFunc loop) { STARTUPINFOA si = {sizeof(si)}; PROCESS_INFORMATION pi = {0}; - // build command line arguments (add --child flag) std::string cmd_line = "\"" + exe_path + "\" --child"; std::vector cmd_line_buf(cmd_line.begin(), cmd_line.end()); cmd_line_buf.push_back('\0'); @@ -197,7 +243,6 @@ bool Daemon::runWithRestart(MainLoopFunc loop) { continue; } - // wait for child process to exit DWORD exit_code = 0; WaitForSingleObject(pi.hProcess, INFINITE); GetExitCodeProcess(pi.hProcess, &exit_code); @@ -205,56 +250,57 @@ bool Daemon::runWithRestart(MainLoopFunc loop) { CloseHandle(pi.hThread); if (exit_code == 0) { - // normal exit - break; - } else { - // abnormal exit, restart - restart_count++; - std::cerr << "Child process exited with code " << exit_code - << ", restarting... (attempt " << restart_count << ")" - << std::endl; - std::this_thread::sleep_for( - std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); + break; // normal exit } + restart_count++; + std::cerr << "Child process exited with code " << exit_code + << ", restarting... (attempt " << restart_count << ")" + << std::endl; + std::this_thread::sleep_for( + std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); #else // linux: use fork + exec to create child process pid_t pid = fork(); if (pid == 0) { - // child process: execute main program, pass --child argument execl(exe_path.c_str(), exe_path.c_str(), "--child", nullptr); - // if exec fails, exit - _exit(1); + _exit(1); // exec failed } else if (pid > 0) { - // parent process: wait for child process to exit int status = 0; - waitpid(pid, &status, 0); + pid_t waited_pid = waitpid(pid, &status, 0); - if (WIFEXITED(status)) { - int exit_code = WEXITSTATUS(status); - if (exit_code == 0) { - // normal exit - break; - } else { - // abnormal exit, restart - restart_count++; - std::cerr << "Child process exited with code " << exit_code - << ", restarting... (attempt " << restart_count << ")" - << std::endl; - std::this_thread::sleep_for( - std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); - } - } else if (WIFSIGNALED(status)) { - // child process terminated by signal (crash) - int sig = WTERMSIG(status); + if (waited_pid < 0) { restart_count++; - std::cerr << "Child process crashed with signal " << sig + std::cerr << "waitpid failed, errno: " << errno << ", restarting... (attempt " << restart_count << ")" << std::endl; std::this_thread::sleep_for( std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); + continue; } + + if (WIFEXITED(status)) { + int exit_code = WEXITSTATUS(status); + if (exit_code == 0) { + break; // normal exit + } + restart_count++; + std::cerr << "Child process exited with code " << exit_code + << ", restarting... (attempt " << restart_count << ")" + << std::endl; + } else if (WIFSIGNALED(status)) { + restart_count++; + std::cerr << "Child process crashed with signal " << WTERMSIG(status) + << ", restarting... (attempt " << restart_count << ")" + << std::endl; + } else { + restart_count++; + std::cerr << "Child process exited with unknown status, restarting... " + "(attempt " + << restart_count << ")" << std::endl; + } + std::this_thread::sleep_for( + std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); } else { - // fork failed std::cerr << "Failed to fork child process" << std::endl; std::this_thread::sleep_for( std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); @@ -265,132 +311,3 @@ bool Daemon::runWithRestart(MainLoopFunc loop) { return true; } - -#ifndef _WIN32 -void Daemon::runUnix(MainLoopFunc loop) { - struct sigaction sa; - sa.sa_handler = [](int) {}; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sigaction(SIGPIPE, &sa, nullptr); - sigaction(SIGCHLD, &sa, nullptr); - - // daemon mode: fork twice, redirect output - pid_t pid = fork(); - if (pid > 0) _exit(0); - setsid(); - pid = fork(); - if (pid > 0) _exit(0); - umask(0); - chdir("/"); - int fd = open("/dev/null", O_RDWR); - if (fd >= 0) { - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - if (fd > 2) close(fd); - } - - // catch signals for exit - signal(SIGTERM, [](int) { instance_->stop(); }); - signal(SIGINT, [](int) { instance_->stop(); }); - - // catch crash signals, use atomic flags to mark crash - static std::atomic crash_detected{false}; - static std::atomic should_restart{false}; - - // use safer signal handling: only set flags, don't call longjmp - struct sigaction sa_crash; - sa_crash.sa_handler = [](int sig) { - const char* sig_name = "Unknown"; - switch (sig) { - case SIGSEGV: - sig_name = "SIGSEGV"; - break; - case SIGABRT: - sig_name = "SIGABRT"; - break; - case SIGFPE: - sig_name = "SIGFPE"; - break; - case SIGILL: - sig_name = "SIGILL"; - break; - } - std::cerr << "Crash signal detected: " << sig_name - << ", will restart after process exits" << std::endl; - crash_detected = true; - should_restart = true; - // don't call longjmp, let program exit normally, restart by monitoring - // thread - }; - sigemptyset(&sa_crash.sa_mask); - sa_crash.sa_flags = SA_RESETHAND; // handle only once, avoid recursion - - sigaction(SIGSEGV, &sa_crash, nullptr); - sigaction(SIGABRT, &sa_crash, nullptr); - sigaction(SIGFPE, &sa_crash, nullptr); - sigaction(SIGILL, &sa_crash, nullptr); - - running_ = true; - - // run with restart logic (infinite restart) - // run main loop in separate thread, main thread monitors thread status - int restart_count = 0; - while (running_) { - crash_detected = false; - should_restart = false; - std::atomic loop_completed{false}; - std::exception_ptr loop_exception = nullptr; - - // run main loop in separate thread - std::thread loop_thread([&loop, &loop_completed, &loop_exception]() { - try { - loop(); - loop_completed = true; - } catch (const std::exception& e) { - loop_exception = std::current_exception(); - loop_completed = true; - } catch (...) { - loop_exception = std::current_exception(); - loop_completed = true; - } - }); - - // wait for thread to complete - loop_thread.join(); - - // check exit reason - if (loop_exception) { - restart_count++; - try { - std::rethrow_exception(loop_exception); - } catch (const std::exception& e) { - std::cerr << "Exception caught: " << e.what() << std::endl; - } catch (...) { - std::cerr << "Unknown exception caught" << std::endl; - } - std::cerr << "Restarting... (attempt " << restart_count << ")" - << std::endl; - std::this_thread::sleep_for( - std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); - continue; - } - - // check if crash signal detected - if (crash_detected || should_restart) { - restart_count++; - std::cerr << "Crash detected, restarting... (attempt " << restart_count - << ")" << std::endl; - std::this_thread::sleep_for( - std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS)); - continue; - } - - // normal exit - if (loop_completed) { - break; - } - } -} -#endif diff --git a/src/app/daemon.h b/src/app/daemon.h index 86a670b..984bafe 100644 --- a/src/app/daemon.h +++ b/src/app/daemon.h @@ -10,7 +10,6 @@ #include #include -// default restart delay (milliseconds) #define DAEMON_DEFAULT_RESTART_DELAY_MS 1000 class Daemon { @@ -19,13 +18,10 @@ class Daemon { Daemon(const std::string& name); - // start daemon (restart after 1 second by default) bool start(MainLoopFunc loop); - // request exit void stop(); - // check if running bool isRunning() const; private: @@ -36,7 +32,6 @@ class Daemon { bool running_; #else static Daemon* instance_; - void runUnix(MainLoopFunc loop); volatile bool running_; #endif };