Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/foundation/compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@
#include <sys/stat.h>
#endif

int cbm_nanosleep_full(const struct timespec *req) {
#ifdef _WIN32
return cbm_nanosleep(req, NULL);
#else
struct timespec remaining = *req;
while (nanosleep(&remaining, &remaining) != 0) {
if (errno != EINTR) {
return -1;
}
}
return 0;
#endif
}

/* ── strndup (Windows lacks it) ───────────────────────────────── */

#ifdef _WIN32
Expand Down
3 changes: 3 additions & 0 deletions src/foundation/compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ static inline int cbm_nanosleep(const struct timespec *req, struct timespec *rem
#define cbm_nanosleep nanosleep
#endif

/* Sleeps for the full requested duration even when POSIX signals interrupt it. */
int cbm_nanosleep_full(const struct timespec *req);

/* ── gmtime_r (Windows lacks it) ─────────────────────────────── */
#ifdef _WIN32
static inline struct tm *cbm_gmtime_r(const time_t *timep, struct tm *result) {
Expand Down
2 changes: 1 addition & 1 deletion src/foundation/subprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ static void cbm_spawn_backoff(int attempt) {
int shift = attempt < CBM_SPAWN_BACKOFF_MAX_SHIFT ? attempt : CBM_SPAWN_BACKOFF_MAX_SHIFT;
long ms = (long)CBM_SPAWN_BACKOFF_BASE_MS << shift;
struct timespec delay = {ms / 1000L, (ms % 1000L) * 1000L * 1000L};
(void)cbm_nanosleep(&delay, NULL);
(void)cbm_nanosleep_full(&delay);
}

/* fork() fails with EAGAIN under the same pressure posix_spawn does, and the
Expand Down
48 changes: 48 additions & 0 deletions tests/test_subprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#ifndef _WIN32
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#endif

Expand Down Expand Up @@ -192,6 +193,52 @@ TEST(subprocess_retries_transient_spawn_refusal) {
#endif
}

#ifndef _WIN32
static volatile sig_atomic_t g_spawn_backoff_alarm_count = 0;

static void spawn_backoff_alarm_handler(int signal_number) {
(void)signal_number;
g_spawn_backoff_alarm_count++;
}
#endif

TEST(subprocess_spawn_backoff_resumes_after_eintr) {
#ifdef _WIN32
SKIP_PLATFORM("POSIX signal interruption");
#else
struct sigaction action = {0};
struct sigaction previous_action = {0};
action.sa_handler = spawn_backoff_alarm_handler;
(void)sigemptyset(&action.sa_mask);
bool handler_installed = sigaction(SIGALRM, &action, &previous_action) == 0;

struct itimerval timer = {
.it_interval = {.tv_sec = 0, .tv_usec = 1000},
.it_value = {.tv_sec = 0, .tv_usec = 1000},
};
g_spawn_backoff_alarm_count = 0;
bool timer_started = handler_installed && setitimer(ITIMER_REAL, &timer, NULL) == 0;

uint64_t started_at = cbm_now_ms();
cbm_subprocess_force_spawn_eagain_for_testing(3);
cbm_proc_result_t result = run_sh("exit 0", 0);
uint64_t elapsed_ms = cbm_now_ms() - started_at;

struct itimerval disabled = {0};
(void)setitimer(ITIMER_REAL, &disabled, NULL);
if (handler_installed) {
(void)sigaction(SIGALRM, &previous_action, NULL);
}

ASSERT_TRUE(handler_installed);
ASSERT_TRUE(timer_started);
ASSERT_TRUE(g_spawn_backoff_alarm_count > 0);
ASSERT_TRUE(elapsed_ms >= 50);
ASSERT_EQ(result.outcome, CBM_PROC_CLEAN);
PASS();
#endif
}

TEST(subprocess_gives_up_after_the_retry_budget) {
#ifdef _WIN32
SKIP_PLATFORM("POSIX fork/EAGAIN path");
Expand Down Expand Up @@ -1033,6 +1080,7 @@ SUITE(subprocess) {
RUN_TEST(subprocess_run_crash_is_crash);
RUN_TEST(subprocess_run_hang_is_hang);
RUN_TEST(subprocess_retries_transient_spawn_refusal);
RUN_TEST(subprocess_spawn_backoff_resumes_after_eintr);
RUN_TEST(subprocess_gives_up_after_the_retry_budget);
RUN_TEST(subprocess_run_spawn_failure);
RUN_TEST(subprocess_run_null_bin_rejected);
Expand Down
Loading