fix(autoscaler): run provider scripts with posix_spawn, not fork (bug-1969)

The scaler is multi-threaded (monitor and demand threads); invoke() forked
and, in the child, logged, snprintf'd and setenv()'d before execl(), which
can deadlock on a lock another thread held at fork time and block the
caller in waitpid() forever.

run_script() now posix_spawn()s `sh <script> -f <in.json>`: stdout and
stderr go to the output file through file actions, the provider variables
(PRO_CONF_DIR, PRO_DATA_DIR, PRO_LOG_DIR, PROVIDER_NAME) are passed as the
child's envp (provider_env()), the child starts with an empty signal mask
and default dispositions, and waitpid() retries on EINTR.

The scaler unit suite (scaler/testsuite) was never built; it is now the
meson test "autoscaler test", linked with -Wl,--wrap=fork so it counts
fork() calls during a provider call.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-10 14:41:02 +00:00
parent ad6ce7eb1e
commit 19052674bd
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
8 changed files with 222 additions and 60 deletions

View file

@ -9,7 +9,8 @@ started with posix_spawn(), or by systemd over D-Bus.
Scanned: C sources of the components the rule covers (COVERED below), minus
test code. Excluded by decision (2026-10-10): tests, the harness (xce_test),
the autoscaler (not finalized; tracked as a bug) and the Go frontend.
the autoscaler (not finalized; provider calls use posix_spawn since bug-1969)
and the Go frontend.
A call is allowed only in a process proven single-threaded, marked on the call
line or the line above with: /* xce-fork-ok: <why it is single-threaded> */