Skip to content

Commit c537bf0

Browse files
committed
fix(foundation): resume spawn backoff after EINTR
Signed-off-by: Rares Popa <2606875+rarepops@users.noreply.github.com>
1 parent 010569f commit c537bf0

4 files changed

Lines changed: 66 additions & 1 deletion

File tree

‎src/foundation/compat.c‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@
1717
#include <sys/stat.h>
1818
#endif
1919

20+
int cbm_nanosleep_full(const struct timespec *req) {
21+
#ifdef _WIN32
22+
return cbm_nanosleep(req, NULL);
23+
#else
24+
struct timespec remaining = *req;
25+
while (nanosleep(&remaining, &remaining) != 0) {
26+
if (errno != EINTR) {
27+
return -1;
28+
}
29+
}
30+
return 0;
31+
#endif
32+
}
33+
2034
/* ── strndup (Windows lacks it) ───────────────────────────────── */
2135

2236
#ifdef _WIN32

‎src/foundation/compat.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ static inline int cbm_nanosleep(const struct timespec *req, struct timespec *rem
9696
#define cbm_nanosleep nanosleep
9797
#endif
9898

99+
/* Sleeps for the full requested duration even when POSIX signals interrupt it. */
100+
int cbm_nanosleep_full(const struct timespec *req);
101+
99102
/* ── gmtime_r (Windows lacks it) ─────────────────────────────── */
100103
#ifdef _WIN32
101104
static inline struct tm *cbm_gmtime_r(const time_t *timep, struct tm *result) {

‎src/foundation/subprocess.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -970,7 +970,7 @@ static void cbm_spawn_backoff(int attempt) {
970970
int shift = attempt < CBM_SPAWN_BACKOFF_MAX_SHIFT ? attempt : CBM_SPAWN_BACKOFF_MAX_SHIFT;
971971
long ms = (long)CBM_SPAWN_BACKOFF_BASE_MS << shift;
972972
struct timespec delay = {ms / 1000L, (ms % 1000L) * 1000L * 1000L};
973-
(void)cbm_nanosleep(&delay, NULL);
973+
(void)cbm_nanosleep_full(&delay);
974974
}
975975

976976
/* fork() fails with EAGAIN under the same pressure posix_spawn does, and the

‎tests/test_subprocess.c‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#ifndef _WIN32
2222
#include <fcntl.h>
2323
#include <signal.h>
24+
#include <sys/time.h>
2425
#include <unistd.h>
2526
#endif
2627

@@ -192,6 +193,52 @@ TEST(subprocess_retries_transient_spawn_refusal) {
192193
#endif
193194
}
194195

196+
#ifndef _WIN32
197+
static volatile sig_atomic_t g_spawn_backoff_alarm_count = 0;
198+
199+
static void spawn_backoff_alarm_handler(int signal_number) {
200+
(void)signal_number;
201+
g_spawn_backoff_alarm_count++;
202+
}
203+
#endif
204+
205+
TEST(subprocess_spawn_backoff_resumes_after_eintr) {
206+
#ifdef _WIN32
207+
SKIP_PLATFORM("POSIX signal interruption");
208+
#else
209+
struct sigaction action = {0};
210+
struct sigaction previous_action = {0};
211+
action.sa_handler = spawn_backoff_alarm_handler;
212+
(void)sigemptyset(&action.sa_mask);
213+
bool handler_installed = sigaction(SIGALRM, &action, &previous_action) == 0;
214+
215+
struct itimerval timer = {
216+
.it_interval = {.tv_sec = 0, .tv_usec = 1000},
217+
.it_value = {.tv_sec = 0, .tv_usec = 1000},
218+
};
219+
g_spawn_backoff_alarm_count = 0;
220+
bool timer_started = handler_installed && setitimer(ITIMER_REAL, &timer, NULL) == 0;
221+
222+
uint64_t started_at = cbm_now_ms();
223+
cbm_subprocess_force_spawn_eagain_for_testing(3);
224+
cbm_proc_result_t result = run_sh("exit 0", 0);
225+
uint64_t elapsed_ms = cbm_now_ms() - started_at;
226+
227+
struct itimerval disabled = {0};
228+
(void)setitimer(ITIMER_REAL, &disabled, NULL);
229+
if (handler_installed) {
230+
(void)sigaction(SIGALRM, &previous_action, NULL);
231+
}
232+
233+
ASSERT_TRUE(handler_installed);
234+
ASSERT_TRUE(timer_started);
235+
ASSERT_TRUE(g_spawn_backoff_alarm_count > 0);
236+
ASSERT_TRUE(elapsed_ms >= 50);
237+
ASSERT_EQ(result.outcome, CBM_PROC_CLEAN);
238+
PASS();
239+
#endif
240+
}
241+
195242
TEST(subprocess_gives_up_after_the_retry_budget) {
196243
#ifdef _WIN32
197244
SKIP_PLATFORM("POSIX fork/EAGAIN path");
@@ -1033,6 +1080,7 @@ SUITE(subprocess) {
10331080
RUN_TEST(subprocess_run_crash_is_crash);
10341081
RUN_TEST(subprocess_run_hang_is_hang);
10351082
RUN_TEST(subprocess_retries_transient_spawn_refusal);
1083+
RUN_TEST(subprocess_spawn_backoff_resumes_after_eintr);
10361084
RUN_TEST(subprocess_gives_up_after_the_retry_budget);
10371085
RUN_TEST(subprocess_run_spawn_failure);
10381086
RUN_TEST(subprocess_run_null_bin_rejected);

0 commit comments

Comments
 (0)