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:
parent
ad6ce7eb1e
commit
19052674bd
8 changed files with 222 additions and 60 deletions
|
|
@ -91,7 +91,7 @@ Early return on every error. Log with zap fields before returning.
|
|||
- **Threaded processes**: the controller, the scheduler, the `xcecd` daemon (`xcecd`, `xcecd_virtual`: thread pool, network, resource-sync and connector threads), and any process that starts libxce's delaymgr/netio/vtaskmgr threads (`xce_ctx_start()`).
|
||||
- **How to start a child instead**: `posix_spawn()`/`posix_spawnp()` (glibc runs it with `CLONE_VM|CLONE_VFORK`, no copied user code), or a systemd unit over D-Bus as `xcecd` does for `xcecd_worker`. Setup that `posix_spawn()` cannot do (setuid, rlimits, cgroup join) belongs in a single-threaded process such as `xcecd_worker`, never in the threaded daemon.
|
||||
- **Allowed**: `exec*()` without a fork (process replacement, e.g. the controller's HA self-restart); and a fork-family call in a process proven single-threaded, marked on the call line or the line above with `/* xce-fork-ok: <why> */`. `xcecd_worker` (the per-job process) is single-threaded: its call graph from `main()`, callbacks included, reaches no `pthread_create()` and no libuv thread-pool call (`uv_queue_work`, `uv_fs_*`, `uv_getaddrinfo`); re-prove it before adding a thread there.
|
||||
- **Scope**: C code under `src/controller`, `src/scheduler`, `src/lib`, `src/worker`. Excluded (decision 2026-10-10): test code (`testsuite/`, `test*` files; forking to isolate a test case is fine), the harness (`src/harness`, `xce_test`), the autoscaler (not finalized; its `plug_mgr.c` fork is tracked as a bug and must be fixed before it ships), and the Go frontend (`os/exec` is safe).
|
||||
- **Scope**: C code under `src/controller`, `src/scheduler`, `src/lib`, `src/worker`. Excluded (decision 2026-10-10): test code (`testsuite/`, `test*` files; forking to isolate a test case is fine), the harness (`src/harness`, `xce_test`), the autoscaler (not finalized; its provider calls use `posix_spawn()` since bug-1969, and its suite counts `fork()` calls through `-Wl,--wrap=fork`), and the Go frontend (`os/exec` is safe).
|
||||
- **Enforcement**: `scripts/check-no-fork.py` runs as the meson test `no-fork rule` (suites lib, controller, scheduler, worker, so `/xce-build` runs it) and in the pre-commit hook on staged C files.
|
||||
|
||||
### Auth and RBAC
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue