Несмотря на развитие лингвистических моделей, я подумал, что моя версия супервизора может быть достаточно интересна для размещения в статье. Назначение супервизора - поднять повторно программу, которая по каким-то причинам упала с ошибкой. Причём если программа завершила работу без ошибки, то она перезапущена не будет, как и не будут создаваться логи. В логах пишется время падения и тип ошибки. Универсальный Makefile может быть интересен тем, что его достаточно закинуть в папку с исходниками, добавить необходимые пути вида:
LDFLAGS = -I/usr/include/boost
LIBS = -lboost_serialization
Тема статьи не претендует на новизну, но может оказаться кому-то полезной. В первую очередь - это бэкенд, так как непрерывность работы там более важна. Хочется отметить, что в настоящее время С++ итак достаточно надёжный язык программирования. Вопрос в том, что в учебных заведениях, как правило, сначала изучается Си, а только потом С++ и зачастую стиль кода на С++ - Си с классами. Естественно, это влияет на репутацию языка как недостаточно надёжного. С наступлением эпохи лингвистических моделей код на С++ стал существенно надёжнее, так как ошибок с памятью я вот не встречал в сгенерированном коде, а логические ошибки - явление нередкое, но сам код создаёт впечатление образцового.
Базовый код получился сравнительно небольшим, я решил его не перегружать функционалом. Основной поток оставлен пустым для возможностей дописывания под свои нужны, отслеживаемая программа запускается в дополнительном потоке.
Непосредственно код супервизора
#include <iostream> #include <thread> #include <atomic> #include <unistd.h> #include <sys/wait.h> #include <fcntl.h> #include <csignal> #include <fstream> #include <ctime> #include <string> #include <iomanip> #include <sstream> #include <condition_variable> #include <mutex> // Функция, возвращающая строку с текущей датой и временем std::string getCurrentDateTime() { // Получаем текущее время std::time_t now = std::time(nullptr); std::tm* timeInfo = std::localtime(&now); // Создаём поток для форматирования времени std::ostringstream oss; oss << std::put_time(timeInfo, "%Y-%m-%d %H:%M:%S"); return oss.str(); // Возвращаем строку с датой и временем } std::ofstream createLogfile() { // Получаем текущее время std::time_t now = std::time(nullptr); std::tm* timeInfo = std::localtime(&now); // Форматируем дату и время (например: 2025-10-05_14-30-45) std::ostringstream oss; oss << std::put_time(timeInfo, "%Y-%m-%d_%H-%M-%S"); std::string timestamp = oss.str(); std::string filename = "logfile_" + timestamp + ".txt"; // Создаем и открываем файл std::ofstream logFile(filename); if (logFile.is_open()) { std::cout << "Лог-файл создан: " << std::ctime(&now) << filename << std::endl; } else { std::cerr << "Не удалось создать лог-файл!" << std::endl; } return logFile; // Возвращаем ofstream } int runApp(const std::string& program, int maxRestarts, std::atomic<bool>& shouldExit, std::condition_variable &cv) { int restartCount = 0; int status = 0; std::ofstream log; bool log_is_created=false; while (restartCount < maxRestarts) { pid_t pid = fork(); if (pid == 0) { // В дочернем процессе запускаем указанную программу execl(program.c_str(), program.c_str(), nullptr); perror("execl"); exit(EXIT_FAILURE); } else if (pid < 0) { perror("fork"); exit(EXIT_FAILURE); } else { // В родительском процессе ждем завершения дочернего waitpid(pid, &status, 0); if (WIFEXITED(status)) { std::cerr << "Program exited with status " << WEXITSTATUS(status) << std::endl; shouldExit = true; cv.notify_all(); return 0; } else if (WIFSIGNALED(status)) { int sig=WTERMSIG(status); std::cerr << "Program was killed by signal " << sig << std::endl; switch (sig) { case SIGSEGV: std::cout << "Segmentation fault" << std::endl; break; case SIGABRT: std::cout << "Aborted" << std::endl; break; case SIGFPE: std::cout << "Floating point exception" << std::endl; break; case SIGILL: std::cout << "Illegal instruction" << std::endl; break; case SIGINT: std::cout << "Interrupted by user (Ctrl+C)" << std::endl; break; case SIGTERM: std::cout << "Termination signal received" << std::endl; break; default: std::cout << "Unknown signal." << std::endl; } if(log_is_created==false) {log = createLogfile();log_is_created=true;} if (log.is_open()) { log<<getCurrentDateTime(); switch (sig) { case SIGSEGV: log << " Segmentation fault" << std::endl; break; case SIGABRT: log << " Aborted" << std::endl; break; case SIGFPE: log << " Floating point exception" << std::endl; break; case SIGILL: log << " Illegal instruction" << std::endl; break; case SIGINT: log << " Interrupted by user (Ctrl+C)" << std::endl; break; case SIGTERM: log << " Termination signal received" << std::endl; break; default: log << " Unknown signal." << std::endl; } } log.close(); } restartCount++; std::cout << "Restart count: " << restartCount << "/" << maxRestarts << std::endl; } } if (restartCount >= maxRestarts) { std::cerr << "Max restarts reached. Exiting." << std::endl; shouldExit = true; // Устанавливаем флаг завершения cv.notify_all(); } return 0; } int main(int argc, char* argv[]) { if (argc != 3) { std::cerr << "Usage: " << argv[0] << " <program> <max_restarts>" << std::endl; return EXIT_FAILURE; } std::string program = argv[1]; int maxRestarts = std::stoi(argv[2]); // Переменная для синхронизации между потоками std::atomic<bool> shouldExit(false); std::mutex mtx; std::condition_variable cv; try { std::thread appThread(runApp, program, maxRestarts, std::ref(shouldExit), std::ref(cv)); appThread.detach(); } catch (const std::system_error& e) { std::cerr << "Failed to create thread: " << e.what() << std::endl; return EXIT_FAILURE; } // Основной поток ждет завершения работы runApp //while (!shouldExit.load()) { // sleep(1); // Снижаем нагрузку на процессор //} std::unique_lock<std::mutex> lock(mtx); while (!shouldExit.load()) { cv.wait(lock); // Ждем сигнала от другого потока } std::cout << "Main thread exiting." << std::endl; return 0; }
Далее перейдём к универсальному (частично универсальному) Makefile. Он не является прямым конкурентом другим системам сборки, может служить для мелких и средних проектов, для быстрого прототипирования и проверок сгенерированного LLM код��. Например в папке есть какое-то количество исходников (.cpp и .h файлов). То есть это может быть небольшой проект с тестами. Определяются файлы, которые содержат "int main". Из них получаются исполняемые файлы. Из остальных .cpp файлов получаются объектные файлы, которые хранятся в obj. Да, возможна ситуация, когда "int main" будет где-то в комментариях или в строках. Регулярное выражение служит чтобы можно было в этой же папке компилировать исходники тестов, иначе будет ошибка, что функция main уже дублируется.
Код Makefile
CXX = g++ CXXFLAGS = -Wall -Wextra -std=c++2a -O2 -s -fdata-sections -ffunction-sections -flto LDFLAGS = -I/usr/include/boost LIBS = -lboost_serialization # Получение списка всех .cpp файлов в текущей директории SRCS = $(wildcard *.cpp) # Находим файлы, содержащие функцию main (с использованием регулярного выражения) MAIN_SRCS = $(shell grep -l "^\s*int\s\+main\s*" $(SRCS)) MAIN_EXES = $(MAIN_SRCS:.cpp=) # Остальные файлы для компиляции только в объектные файлы OTHER_SRCS = $(filter-out $(MAIN_SRCS),$(SRCS)) OTHER_OBJS = $(patsubst %.cpp, obj/%.o, $(OTHER_SRCS)) MAIN_OBJS = $(patsubst %.cpp, obj/%.o, $(MAIN_SRCS)) # Основная цель all: obj $(OTHER_OBJS) $(MAIN_OBJS) $(MAIN_EXES) @echo "Проверка наличия Makefile в поддиректориях..." @for dir in */ ; do \ if [ "$$dir" != "obj/" ] && [ "$$dir" != "*/" ] && [ -f "$$dir/Makefile" ]; then \ echo "Найден Makefile в директории: $$dir"; \ $(MAKE) -C "$${dir%*/}"; \ elif [ "$$dir" != "obj/" ] && [ "$$dir" != "*/" ]; then \ echo "В директории $$dir нет файла Makefile"; \ fi; \ done # Создание папки obj, если она не существует obj: mkdir -p obj # Правило для создания объектных файлов с зависимостями от .h файлов obj/%.o: %.cpp $(CXX) $(CXXFLAGS) -MMD -MP -c -o $@ $< # Правило для создания исполняемых файлов из файлов с main # Каждый .cpp файл с main компилир��ется в отдельный исполняемый файл # и линкуется только с общими объектными файлами %: obj/%.o $(OTHER_OBJS) @echo "Linking $@" $(CXX) -o $@ $< $(OTHER_OBJS) $(LDFLAGS) $(LIBS) # Подключение сгенерированных зависимостей -include $(OTHER_OBJS:.o=.d) $(MAIN_OBJS:.o=.d) # Очистка clean: rm -rf obj $(MAIN_EXES) @echo "Очистка поддиректорий..." @for dir in */ ; do \ if [ "$$dir" != "obj/" ] && [ "$$dir" != "*/" ] && [ -f "$$dir/Makefile" ]; then \ echo "Выполняется очистка в директории: $$dir"; \ $(MAKE) -C "$${dir%*/}" clean; \ elif [ "$$dir" != "obj/" ] && [ "$$dir" != "*/" ]; then \ echo "В директории $$dir нет файла Makefile для очистки"; \ fi; \ done .PHONY: all clean
