Skip to content

Commit 1424934

Browse files
authored
Merge pull request DeusData#1799 from rarepops/fix/nanosleep-eintr-1616
fix(foundation): resume spawn backoff after EINTR (DeusData#1616)
2 parents b44e584 + c537bf0 commit 1424934

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
#else
2627
#include <windows.h>
@@ -194,6 +195,52 @@ TEST(subprocess_retries_transient_spawn_refusal) {
194195
#endif
195196
}
196197

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

0 commit comments

Comments
 (0)