feat: add engine alive indicator, debug mode, and orchestrator retry logic

This commit is contained in:
Loic Coenen
2026-05-20 20:59:58 +00:00
committed by Loic Coenen (aider)
parent e79c2ac116
commit f2993eac80
5 changed files with 139 additions and 28 deletions

View File

@@ -1,5 +1,5 @@
CC = gcc 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_INC = -I/usr/include/carla -I/usr/include/carla/includes
CARLA_LIB = -L/usr/lib/carla -Wl,-rpath,/usr/lib/carla -lcarla_standalone2 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) 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 $(CC) $(CFLAGS) $(CARLA_INC) -o $@ $^ $(CARLA_LIB) -ljack -lncurses
test_status_parse: tests/test_status_parse.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) 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) $(CARLA_LIB) -ljack -lncurses $(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) --- # --- Plugin stubs (now real) ---
$(PLUGINS_OBJ): src/plugins.c src/plugins.h $(PLUGINS_OBJ): src/plugins.c src/plugins.h

View File

@@ -1 +1 @@
#include "../engine/src/log.c" #include "../../engine/src/log.c"

View File

@@ -3,6 +3,7 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <ctype.h> #include <ctype.h>
@@ -15,8 +16,15 @@
#include "script.h" #include "script.h"
#include <CarlaHost.h> #include <CarlaHost.h>
/* ---------- engine alive indicator ---------- */
static bool engine_running = false;
static bool debug_mode = false;
/* ---------- FIFO command helper ---------- */ /* ---------- FIFO command helper ---------- */
int send_command(const char *cmd) { 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"); const char *fifo_path = getenv("LOOPER_CMD_FIFO");
if (!fifo_path) fifo_path = "/tmp/looper_cmd"; if (!fifo_path) fifo_path = "/tmp/looper_cmd";
int fd = open(fifo_path, O_WRONLY | O_NONBLOCK); int fd = open(fifo_path, O_WRONLY | O_NONBLOCK);
@@ -162,7 +170,7 @@ static void draw_grid(void) {
} }
clear(); clear();
attron(A_BOLD); 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); attroff(A_BOLD);
for (int r=0; r<GRID_ROWS; r++) for (int r=0; r<GRID_ROWS; r++)
for (int c=0; c<GRID_COLS; c++) for (int c=0; c<GRID_COLS; c++)
@@ -181,6 +189,7 @@ static void draw_grid(void) {
void tui_init(void) { void tui_init(void) {
initscr(); initscr();
cbreak(); noecho(); keypad(stdscr, TRUE); curs_set(0); cbreak(); noecho(); keypad(stdscr, TRUE); curs_set(0);
debug_mode = (getenv("LOOPER_DEBUG") != NULL);
if (!has_colors()) { endwin(); fprintf(stderr,"No colors\n"); exit(1); } if (!has_colors()) { endwin(); fprintf(stderr,"No colors\n"); exit(1); }
start_color(); start_color();
init_pair(COLOR_EMPTY, COLOR_WHITE, COLOR_BLACK); init_pair(COLOR_EMPTY, COLOR_WHITE, COLOR_BLACK);
@@ -234,6 +243,9 @@ void tui_run(void) {
close(fd); close(fd);
} }
/* Check if engine is alive by testing existence of status FIFO */
engine_running = (access(STATUS_FIFO, F_OK) == 0);
/* read any available note events (for script macros) */ /* read any available note events (for script macros) */
int nfd = open(NOTES_FIFO, O_RDONLY | O_NONBLOCK); int nfd = open(NOTES_FIFO, O_RDONLY | O_NONBLOCK);
if (nfd >= 0) { if (nfd >= 0) {

View File

@@ -1,8 +1,10 @@
# Top-level Makefile delegates build/clean/test to subdirectories # Top-level Makefile delegates build/clean/test to subdirectories
CC ?= gcc
SUBDIRS = engine client 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 all: build orchestrator
@@ -15,6 +17,9 @@ orchestrator: orchestrator.c
$(SUBDIRS): $(SUBDIRS):
$(MAKE) -C $@ $(MAKE) -C $@
run: orchestrator
./looper
test: test:
# $(MAKE) -C engine test # $(MAKE) -C engine test
$(MAKE) -C client test $(MAKE) -C client test

View File

@@ -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 <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
@@ -8,27 +15,44 @@
static pid_t engine_pid = 0; static pid_t engine_pid = 0;
static pid_t client_pid = 0; static pid_t client_pid = 0;
static void cleanup(int sig) { static void terminate_children(void) {
(void)sig;
if (engine_pid > 0) kill(engine_pid, SIGTERM); if (engine_pid > 0) kill(engine_pid, SIGTERM);
if (client_pid > 0) kill(client_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); _exit(0);
} }
int main(int argc, char *argv[]) { static pid_t start_engine(void) {
signal(SIGINT, cleanup); pid_t pid = fork();
signal(SIGTERM, cleanup); if (pid == -1) {
perror("fork engine");
engine_pid = fork(); return -1;
if (engine_pid == 0) { }
if (pid == 0) {
execl("./engine/looper", "looper", NULL); execl("./engine/looper", "looper", NULL);
perror("execl engine"); perror("execl engine");
_exit(1); _exit(1);
} }
return pid;
}
client_pid = fork(); static pid_t start_client(int argc, char *argv[]) {
if (client_pid == 0) { pid_t pid = fork();
if (pid == -1) {
perror("fork client");
return -1;
}
if (pid == 0) {
if (argc > 2 && strcmp(argv[1], "-s") == 0) { if (argc > 2 && strcmp(argv[1], "-s") == 0) {
execl("./client/looper-client", "looper-client", "-s", argv[2], NULL); execl("./client/looper-client", "looper-client", "-s", argv[2], NULL);
} else { } else {
@@ -37,15 +61,85 @@ int main(int argc, char *argv[]) {
perror("execl client"); perror("execl client");
_exit(1); _exit(1);
} }
return pid;
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);
} }
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; 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;
}