Skip to content

Commit b9cde7e

Browse files
authored
Merge pull request #1618 from moffermann/fix/spawn-backoff-clamp
fix(subprocess): bound the spawn backoff by the wait, not by the budget
2 parents a6afd15 + e1921bc commit b9cde7e

1 file changed

Lines changed: 22 additions & 8 deletions

File tree

‎src/foundation/subprocess.c‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -912,8 +912,9 @@ enum { CBM_SPAWN_RETRY = 2, CBM_SPAWN_RETRY_ATTEMPTS = 6 };
912912
/* Exponential backoff, doubling from 10ms: 10, 20, 40, 80, 160, 320 — ~630ms of
913913
* total patience on an ordinary build, and three further doublings (640, 1280,
914914
* 2560) to roughly 5s on a sanitized one. The waits follow from
915-
* CBM_SPAWN_RETRY_ATTEMPTS above rather than being listed separately here, so
916-
* changing the budget cannot leave this description behind.
915+
* CBM_SPAWN_RETRY_ATTEMPTS above and from the per-wait ceiling at
916+
* cbm_spawn_backoff, rather than being listed separately here, so changing the
917+
* budget cannot leave this description behind.
917918
*
918919
* The first version waited a flat 3 x 10ms, which was enough for a momentary
919920
* dip and NOT enough for the real thing: a CI runner building and testing in
@@ -948,13 +949,26 @@ static bool cbm_spawn_eagain_injected(void) {
948949
}
949950
#endif
950951

952+
/* The bound is on a single WAIT, and 2560ms (10ms << 8) is the largest wait
953+
* either budget produces today: 10..320 on an ordinary build, 10..2560 on a
954+
* sanitized one. So this changes nothing now — it changes what happens next.
955+
*
956+
* The clamp it replaces bounded `attempt` by CBM_SPAWN_RETRY_ATTEMPTS, which no
957+
* caller can reach: both retry loops return before passing the budget, so the
958+
* clamp never fired and the comment claiming "the budget is the only bound
959+
* needed" described a bound that did not exist. Doubling with nothing to stop
960+
* it is the actual risk, and it grows fast — a budget of 12 would make the last
961+
* wait ~20s and the total ~41s, which is precisely the "hang instead of fail
962+
* fast" the retry was written to avoid. Flattening at the ceiling keeps a
963+
* budget increase linear.
964+
*
965+
* Spelled as a shift so the value cannot overflow `long` on the way to being
966+
* clamped. */
967+
enum { CBM_SPAWN_BACKOFF_BASE_MS = 10, CBM_SPAWN_BACKOFF_MAX_SHIFT = 8 };
968+
951969
static void cbm_spawn_backoff(int attempt) {
952-
/* Cap the shift at the budget, not at a constant: with the sanitized budget
953-
* of 9 a hard-coded 6 would flatten the last three waits to 320ms each
954-
* instead of continuing to double. The budget is the only bound needed —
955-
* callers never exceed it, and a second clamp would be dead code. */
956-
int shift = attempt < CBM_SPAWN_RETRY_ATTEMPTS ? attempt : CBM_SPAWN_RETRY_ATTEMPTS;
957-
long ms = 10L << shift;
970+
int shift = attempt < CBM_SPAWN_BACKOFF_MAX_SHIFT ? attempt : CBM_SPAWN_BACKOFF_MAX_SHIFT;
971+
long ms = (long)CBM_SPAWN_BACKOFF_BASE_MS << shift;
958972
struct timespec delay = {ms / 1000L, (ms % 1000L) * 1000L * 1000L};
959973
(void)cbm_nanosleep(&delay, NULL);
960974
}

0 commit comments

Comments
 (0)