From f2993eac809d8f76b2e7905466a913308445b31f Mon Sep 17 00:00:00 2001 From: Loic Coenen Date: Wed, 20 May 2026 20:59:58 +0000 Subject: [PATCH] feat: add engine alive indicator, debug mode, and orchestrator retry logic --- client/makefile | 6 +-- client/src/log.c | 2 +- client/src/tui.c | 14 ++++- makefile | 7 ++- orchestrator.c | 138 +++++++++++++++++++++++++++++++++++++++-------- 5 files changed, 139 insertions(+), 28 deletions(-) diff --git a/client/makefile b/client/makefile index 0a060ea..2f69324 100644 --- a/client/makefile +++ b/client/makefile @@ -1,5 +1,5 @@ CC = gcc -CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -Isrc +CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -Isrc -I../engine/src CARLA_INC = -I/usr/include/carla -I/usr/include/carla/includes CARLA_LIB = -L/usr/lib/carla -Wl,-rpath,/usr/lib/carla -lcarla_standalone2 @@ -22,8 +22,8 @@ all: looper-client test_status_parse looper-client: src/main.c src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) $(SCRIPT_OBJ) $(LOG_OBJ) $(CC) $(CFLAGS) $(CARLA_INC) -o $@ $^ $(CARLA_LIB) -ljack -lncurses -test_status_parse: tests/test_status_parse.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) - $(CC) $(CFLAGS) $(CARLA_INC) -o test_status_parse tests/test_status_parse.c src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) $(CARLA_LIB) -ljack -lncurses +test_status_parse: tests/test_status_parse.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) $(SCRIPT_OBJ) + $(CC) $(CFLAGS) $(CARLA_INC) -o test_status_parse tests/test_status_parse.c src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) $(SCRIPT_OBJ) $(CARLA_LIB) -ljack -lncurses # --- Plugin stubs (now real) --- $(PLUGINS_OBJ): src/plugins.c src/plugins.h diff --git a/client/src/log.c b/client/src/log.c index 1d88176..c74973c 100644 --- a/client/src/log.c +++ b/client/src/log.c @@ -1 +1 @@ -#include "../engine/src/log.c" +#include "../../engine/src/log.c" diff --git a/client/src/tui.c b/client/src/tui.c index 5084bd6..e501940 100644 --- a/client/src/tui.c +++ b/client/src/tui.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -15,8 +16,15 @@ #include "script.h" #include +/* ---------- engine alive indicator ---------- */ +static bool engine_running = false; +static bool debug_mode = false; + /* ---------- FIFO command helper ---------- */ int send_command(const char *cmd) { + if (debug_mode) { + fprintf(stderr, "DEBUG: send_command(%s)\n", cmd); + } const char *fifo_path = getenv("LOOPER_CMD_FIFO"); if (!fifo_path) fifo_path = "/tmp/looper_cmd"; int fd = open(fifo_path, O_WRONLY | O_NONBLOCK); @@ -162,7 +170,7 @@ static void draw_grid(void) { } clear(); attron(A_BOLD); - mvprintw(0,0,"JACK Looper - Client (FIFO only)"); + mvprintw(0,0,"JACK Looper - Client (FIFO only) %s", engine_running ? "[online]" : "[offline]"); attroff(A_BOLD); for (int r=0; r= 0) { diff --git a/makefile b/makefile index 7ed3e75..5be1bd3 100644 --- a/makefile +++ b/makefile @@ -1,8 +1,10 @@ # Top-level Makefile – delegates build/clean/test to subdirectories +CC ?= gcc + SUBDIRS = engine client -.PHONY: all build clean test check format orchestrator $(SUBDIRS) +.PHONY: all build clean test check format orchestrator run $(SUBDIRS) all: build orchestrator @@ -15,6 +17,9 @@ orchestrator: orchestrator.c $(SUBDIRS): $(MAKE) -C $@ +run: orchestrator + ./looper + test: # $(MAKE) -C engine test $(MAKE) -C client test diff --git a/orchestrator.c b/orchestrator.c index 4e3d588..8c336c9 100644 --- a/orchestrator.c +++ b/orchestrator.c @@ -1,3 +1,10 @@ +/* + * orchestrator.c - Launches both the engine and client processes, + * forwards signals, and waits for either to exit before cleaning up + * the other. If a child exits abnormally it is retried up to 3 times. + */ +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L #include #include #include @@ -8,27 +15,44 @@ static pid_t engine_pid = 0; static pid_t client_pid = 0; -static void cleanup(int sig) { - (void)sig; +static void terminate_children(void) { if (engine_pid > 0) kill(engine_pid, SIGTERM); if (client_pid > 0) kill(client_pid, SIGTERM); - while (wait(NULL) > 0); +} + +static void wait_children(void) { + int status; + while (waitpid(-1, &status, 0) > 0); +} + +static void cleanup(int sig) { + (void)sig; + terminate_children(); + wait_children(); _exit(0); } -int main(int argc, char *argv[]) { - signal(SIGINT, cleanup); - signal(SIGTERM, cleanup); - - engine_pid = fork(); - if (engine_pid == 0) { +static pid_t start_engine(void) { + pid_t pid = fork(); + if (pid == -1) { + perror("fork engine"); + return -1; + } + if (pid == 0) { execl("./engine/looper", "looper", NULL); perror("execl engine"); _exit(1); } + return pid; +} - client_pid = fork(); - if (client_pid == 0) { +static pid_t start_client(int argc, char *argv[]) { + pid_t pid = fork(); + if (pid == -1) { + perror("fork client"); + return -1; + } + if (pid == 0) { if (argc > 2 && strcmp(argv[1], "-s") == 0) { execl("./client/looper-client", "looper-client", "-s", argv[2], NULL); } else { @@ -37,15 +61,85 @@ int main(int argc, char *argv[]) { perror("execl client"); _exit(1); } - - int status; - pid_t exited = wait(&status); - if (exited == engine_pid) { - kill(client_pid, SIGTERM); - wait(NULL); - } else if (exited == client_pid) { - kill(engine_pid, SIGTERM); - wait(NULL); - } - return 0; + return pid; +} + +int main(int argc, char *argv[]) { + signal(SIGINT, cleanup); + signal(SIGTERM, cleanup); + + int i; + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "--debug") == 0) { + setenv("LOOPER_DEBUG", "1", 1); + break; + } + } + + int attempt = 0; + const int MAX_ATTEMPTS = 3; + + while (attempt < MAX_ATTEMPTS) { + attempt++; + + engine_pid = start_engine(); + if (engine_pid == -1) { + if (attempt >= MAX_ATTEMPTS) { + fprintf(stderr, "Failed to start engine after %d attempts\n", MAX_ATTEMPTS); + return 1; + } + usleep(500000); + continue; + } + + client_pid = start_client(argc, argv); + if (client_pid == -1) { + kill(engine_pid, SIGTERM); + waitpid(engine_pid, NULL, 0); + if (attempt >= MAX_ATTEMPTS) { + fprintf(stderr, "Failed to start client after %d attempts\n", MAX_ATTEMPTS); + return 1; + } + usleep(500000); + continue; + } + + /* Both children have started. Wait for either to exit. */ + int status; + pid_t exited = waitpid(-1, &status, 0); + pid_t other = 0; + if (exited == engine_pid) { + other = client_pid; + } else if (exited == client_pid) { + other = engine_pid; + } else { + /* unexpected waitpid failure */ + terminate_children(); + wait_children(); + return 1; + } + + /* Kill the other child now that one has exited. */ + if (other > 0) { + kill(other, SIGTERM); + waitpid(other, NULL, 0); + } + + /* Normal clean exit (zero status) means we are done. */ + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + return 0; + } + + if (attempt >= MAX_ATTEMPTS) { + fprintf(stderr, "Child exited abnormally after %d attempts. Quitting.\n", + MAX_ATTEMPTS); + return 1; + } + + fprintf(stderr, "Child exited abnormally, retrying...\n"); + usleep(500000); + /* loop back to try another fresh start */ + } + + return 1; }