Browse Source

Daemonize before creating core workers

master
evgeny 6 days ago
parent
commit
5d1b8d26e7
  1. 6
      doc/service_lifecycle.md
  2. 14
      src/member_files/member_files.c
  3. 72
      src/utun.c
  4. 8
      tests/Makefile.am
  5. 86
      tests/test_daemon_worker.c

6
doc/service_lifecycle.md

@ -1,5 +1,11 @@
# Жизненный цикл ядра и сервисов
CLI выполняет daemon fork до создания `UASYNC`, SQLite, сокетов и фоновых потоков.
Сервисы и их workers создаются уже в дочернем процессе. Родитель ждёт подтверждения
успешной инициализации и записи PID-файла; ошибка старта возвращается вызывающему.
`test_daemon_worker` проверяет настоящий запуск без `-f`, наличие дискового потока,
штатный SIGTERM и ненулевой статус launcher при ошибке PID-файла.
Все операции выполняются в потоке единственного `UASYNC` экземпляра, вне callbacks
останавливаемых модулей. GUI передаёт команды в этот поток.

14
src/member_files/member_files.c

@ -124,6 +124,10 @@ static int mf_send(struct member_files* f, uint64_t group, uint64_t peer, const
int n = mf_encode(m, wire, sizeof(wire));
if (n < 0) return -1;
int result = etcp_router_conn_send(rc, wire, (size_t)n, ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED);
if (!result) DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA,
"member_files: tx id=%llu kind=%u cmd=%u group=%llx peer=%llx owner=%llx status=%u bytes=%d",
(unsigned long long)m->request_id, m->kind, m->command, (unsigned long long)group,
(unsigned long long)peer, (unsigned long long)m->owner, m->status, n);
if (result) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: send failed group=%llu peer=%llu cmd=%u rc=%d",
(unsigned long long)group, (unsigned long long)peer, m->command, result);
return result ? -1 : 0;
@ -236,6 +240,9 @@ static void mf_server_ready(struct ll_queue* q, void* arg) {
static void mf_server_work(void* arg) {
struct mf_job* j = arg;
if (j->request.command) DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "member_files: disk start id=%llu cmd=%u group=%llx peer=%llx path=%s",
(unsigned long long)j->request.request_id, j->request.command, (unsigned long long)j->group,
(unsigned long long)j->peer, j->request.path);
if (j->request.command) mf_storage_execute(j->files->storage, j->group, j->peer, &j->request, &j->response);
else mf_storage_sweep(j->files->storage, get_time_tb());
}
@ -245,6 +252,8 @@ static void mf_server_done(void* arg, int error) {
struct member_files* f = j->files; f->worker = NULL;
if (f->closing || !j->request.command) { mf_job_free(j); if (!f->closing) mf_server_pump(f); return; }
if (error) { j->response = j->request; j->response.kind = MF_RESPONSE; j->response.status = MF_BUSY; j->response.data_len = 0; }
DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "member_files: disk done id=%llu cmd=%u status=%u error=%d jobs=%u",
(unsigned long long)j->request.request_id, j->request.command, j->response.status, error, f->job_count);
j->state = 2; mf_server_pump(f);
}
@ -316,6 +325,8 @@ static void mf_server_pump(struct member_files* f) {
j->response.data_len = 0; j->state = 2; mf_server_pump(f); return;
}
j->state = 1; f->worker = j;
if (j->request.command) DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "member_files: disk queued id=%llu cmd=%u jobs=%u",
(unsigned long long)j->request.request_id, j->request.command, f->job_count);
mf_worker_submit(f->disk, mf_server_work, j, mf_server_done, j);
}
@ -348,6 +359,9 @@ static void mf_tick(void* arg) {
/* Общая обработка проверенного сетевого сообщения или команды внутри экземпляра. */
static void mf_dispatch(struct member_files* f, uint64_t group, uint64_t peer, const struct mf_message* message) {
struct mf_message m = *message;
DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "member_files: rx id=%llu kind=%u cmd=%u group=%llx peer=%llx owner=%llx jobs=%u worker=%p",
(unsigned long long)m.request_id, m.kind, m.command, (unsigned long long)group,
(unsigned long long)peer, (unsigned long long)m.owner, f->job_count, (void*)f->worker);
if (m.kind == MF_RESPONSE) {
for (struct mf_pending* p = f->pending; p; p = p->next) {
if (p->group != group || p->server != peer || p->request.request_id != m.request_id) continue;

72
src/utun.c

@ -179,28 +179,51 @@ static void remove_pidfile(const char *pidfile) {
}
// Daemonize process
static int daemonize(void) {
static int daemonize(int* ready_fd) {
#ifdef _WIN32
// Windows doesn't support fork/setsid, just return success
(void)ready_fd;
return 0;
#else
int notify[2];
if (pipe(notify) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Daemon startup pipe failed: %s", strerror(errno)); return -1;
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
int error = errno; close(notify[0]); close(notify[1]);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Daemon fork failed: %s", strerror(error));
return -1;
}
if (pid > 0) {
// Parent exits
exit(0);
/* Родитель сообщает успех только после инициализации дочернего процесса. */
close(notify[1]);
uint8_t ready = 0; ssize_t count;
do { count = read(notify[0], &ready, sizeof(ready)); } while (count < 0 && errno == EINTR);
close(notify[0]);
if (count != 1 || ready != 1) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Daemon initialization failed; see log file");
exit(count == 1 && ready == 1 ? 0 : 1);
}
close(notify[0]); *ready_fd = notify[1];
// Child becomes session leader
if (setsid() < 0) {
perror("setsid");
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Daemon setsid failed: %s", strerror(errno));
close(*ready_fd); *ready_fd = -1;
return -1;
}
return 0;
#endif
}
/* После вывода startup-информации, но до запуска потоков сервисов. */
static int daemon_redirect_stdio(void) {
#ifdef _WIN32
return 0;
#else
fflush(stdout); fflush(stderr);
// Close standard file descriptors
close(STDIN_FILENO);
close(STDOUT_FILENO);
@ -277,6 +300,7 @@ void test_tmr(void* arg) {
int main(int argc, char *argv[]) {
cmd_args_t args;
int ready_fd = -1;
parse_args(argc, argv, &args);
if (args.help) {
@ -328,6 +352,11 @@ int main(int argc, char *argv[]) {
}
#endif
/* Fork до event loop, SQLite, сокетов и workers: фоновые потоки в ребёнка не переходят. */
if (!args.foreground && daemonize(&ready_fd) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to daemonize before core initialization"); return 1;
}
srand(time(NULL));
// Create uasync instance
struct UASYNC* ua = uasync_create();
@ -365,6 +394,7 @@ int main(int argc, char *argv[]) {
struct UTUN_INSTANCE *instance = utun_instance_create(ua, args.config_file);
if (!instance) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to create UTUN instance");
uasync_destroy(ua, 0); main_ua = NULL;
return 1;
}
@ -373,11 +403,16 @@ int main(int argc, char *argv[]) {
if (args.foreground) {
print_config(instance->config);
}
if (!args.foreground && daemon_redirect_stdio() < 0) {
utun_instance_destroy(instance); uasync_destroy(ua, 0); main_ua = NULL;
return 1;
}
// Initialize all components (TUN, routing, connections)
if (utun_instance_init(instance) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to initialize instance");
utun_instance_destroy(instance);
uasync_destroy(ua, 0); main_ua = NULL;
return 1;
}
@ -390,21 +425,28 @@ int main(int argc, char *argv[]) {
signal(SIGHUP, signal_handler);
#endif
// Daemonize if not in foreground
if (!args.foreground) {
if (daemonize() < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to daemonize");
utun_instance_destroy(instance);
return 1;
}
}
// Write PID file (only if specified)
if (args.pid_file && write_pidfile(args.pid_file) < 0) {
utun_instance_destroy(instance);
uasync_destroy(ua, 0); main_ua = NULL;
return 1;
}
#ifndef _WIN32
if (ready_fd >= 0) {
uint8_t ready = 1; ssize_t count;
do { count = write(ready_fd, &ready, sizeof(ready)); } while (count < 0 && errno == EINTR);
int error = errno;
close(ready_fd); ready_fd = -1;
if (count != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to notify daemon launcher: %s", strerror(error));
utun_instance_destroy(instance); uasync_destroy(ua, 0); main_ua = NULL;
remove_pidfile(args.pid_file); return 1;
}
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Daemon initialized pid=%ld; core and workers created after fork", (long)getpid());
}
#endif
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Run mainloop");
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Run mainloop");

8
tests/Makefile.am

@ -74,6 +74,7 @@ check_PROGRAMS = \
test_services \
test_member_files \
test_member_files_network \
test_daemon_worker \
test_group_exchange \
test_group_recovery \
test_group_connect \
@ -725,6 +726,13 @@ test_member_files_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_member_files_network_SOURCES = test_member_files_network.c
test_member_files_network_CFLAGS = $(test_services_CFLAGS)
test_member_files_network_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_daemon_worker_SOURCES = test_daemon_worker.c
test_daemon_worker_CFLAGS = $(test_services_CFLAGS) -DUTUN_CLI_PATH='"$(abs_top_builddir)/src/utun"'
test_daemon_worker_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_daemon_worker_DEPENDENCIES = $(LIBUTUN) $(top_builddir)/src/utun
$(top_builddir)/src/utun:
$(MAKE) -C $(top_builddir)/src utun
# Граничные длины и checksum UDP/ICMP после восстановления пакета.
test_proxy_packets_SOURCES = test_proxy_packets.c

86
tests/test_daemon_worker.c

@ -0,0 +1,86 @@
/* Настоящий CLI без -f: daemon должен создать disk worker после fork, завершиться
* по SIGTERM и вернуть launcher ошибку, если PID-файл нельзя создать. Без настоящего TUN. */
#include "../lib/platform_compat.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "test_utils.h"
#include <stdio.h>
#include <string.h>
#ifdef __linux__
#include <dirent.h>
#include <spawn.h>
#include <sys/wait.h>
#include <signal.h>
extern char** environ;
static int launch(const char* config, const char* pidfile, const char* log) {
char* argv[] = { UTUN_CLI_PATH, "-c", (char*)config, "-p", (char*)pidfile, "-l", (char*)log,
"-d", "sys=info,media=debug", NULL };
pid_t launcher;
int error = posix_spawn(&launcher, UTUN_CLI_PATH, NULL, NULL, argv, environ);
if (error) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "daemon test: spawn failed error=%d", error); return -1; }
int status;
while (waitpid(launcher, &status, 0) < 0) {
if (errno == EINTR) continue;
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "daemon test: waitpid failed: %s", strerror(errno)); return -1;
}
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}
static int thread_count(pid_t pid) {
char path[128]; snprintf(path, sizeof(path), "/proc/%ld/task", (long)pid);
DIR* dir = opendir(path);
if (!dir) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "daemon test: task listing failed: %s", strerror(errno)); return -1; }
int count = 0; struct dirent* entry;
while ((entry = readdir(dir))) if (entry->d_name[0] != '.') count++;
closedir(dir); return count;
}
static int stop(pid_t pid, const char* pidfile) {
if (kill(pid, SIGTERM)) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "daemon test: SIGTERM failed: %s", strerror(errno)); return -1; }
uint64_t deadline = get_time_tb() + 20000;
while (access(pidfile, F_OK) == 0 && get_time_tb() < deadline) poll(NULL, 0, 10);
if (access(pidfile, F_OK) == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "daemon test: shutdown stuck pid=%ld; killing own fixture", (long)pid);
if (kill(pid, SIGKILL)) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "daemon test: fixture cleanup failed: %s", strerror(errno));
return -1;
}
return 0;
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO);
char dir[512] = "/tmp/utun_daemon_worker_XXXXXX";
if (test_mkdtemp(dir)) return 1;
char config[768], pidfile[768], log[768], bad_pid[768];
snprintf(config, sizeof(config), "%s/utun.conf", dir); snprintf(pidfile, sizeof(pidfile), "%s/daemon.pid", dir);
snprintf(log, sizeof(log), "%s/daemon.log", dir); snprintf(bad_pid, sizeof(bad_pid), "%s/missing/daemon.pid", dir);
FILE* f = fopen(config, "w"); if (!f) return 1;
fprintf(f, "[global]\nmy_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=192.0.2.1\ntun_test_mode=1\n"
"[server:test]\naddr=127.0.0.1:0\ntype=private\n"
"[chatserver]\ndb_path=%s/db\nstorage_autoload=0\ngroup_autoconnect=0\n", dir);
if (fclose(f) || launch(config, pidfile, log)) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "daemon test: startup failed log=%s", log); return 1;
}
f = fopen(pidfile, "r"); long value = 0;
if (!f || fscanf(f, "%ld", &value) != 1 || value <= 1) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "daemon test: invalid/missing pidfile=%s", pidfile); if (f) fclose(f); return 1;
}
fclose(f); pid_t daemon_pid = (pid_t)value;
int threads = thread_count(daemon_pid);
int stopped = stop(daemon_pid, pidfile);
if (threads < 2 || stopped) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "daemon test: worker lost or shutdown failed threads=%d stopped=%d log=%s", threads, stopped, log);
return 1;
}
if (launch(config, bad_pid, log) == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "daemon test: launcher accepted failed child initialization log=%s", log); return 1;
}
printf("daemon worker: PASS threads=%d, graceful shutdown, startup errors propagate\n", threads);
return 0;
}
#else
int main(void) { printf("daemon worker: SKIP (requires Linux procfs)\n"); return 77; }
#endif
Loading…
Cancel
Save