Files
spine/tests/unit/php_test_server.c
Thomas VincentandTheWitness 90da94b94f fix(process): unify subprocess ownership and child lifecycle safety (#614)
## Summary

- make subprocess-registry detach atomic so exactly one closer owns each
entry
- make cancellation cleanup release only the detached node it owns
- replace `vfork`/`execve` child setup with `posix_spawn` and explicit
file actions
- mark every pipe and stdio-collision duplicate close-on-exec
- restore the child's default SIGPIPE disposition while preserving the
parent's handling
- bound TERM/KILL reaping and park unreaped children in a fixed-capacity
sweep list
- harden PHP script-server slot ownership, startup, restart, signal,
descriptor, and cleanup paths
- retire and restart failed or oversized reads so stale response tails
cannot poison later data sources
- revalidate every command retry and cap recovery handshakes at two
seconds

This folds the child-process lifecycle portion of #597 into the narrower
ownership PR. It does not absorb unrelated poll-host, SQL, SNMP, ICMP,
or build-infrastructure work.

## Review-driven corrections

- close-on-exec is explicitly restored after `dup()` in both read and
write stdio-collision branches
- the source invariant check now uses portable POSIX character classes
- direct tests cover duplicate flags, both collision branches, failure
after pipe creation, abandoned-child sweeping, and capacity overflow
- collision cleanup preserves current-child redirects even when an older
registry entry occupies fd 0 or 1
- script children receive SIGTERM before SIGKILL and unreaped PHP
children transfer to the bounded sweep registry
- the Docker integration suite now starts a real PHP script server,
verifies its startup protocol and database result, and checks that
shutdown leaves no zombie PHP process

## Validation

- Ubuntu 24.04 container with GCC and GNU ld: `make check`, 6/6 targets
passed with no skips, including `test_php_runtime`
- real PHP script-server Docker integration: 6/6 checks passed; exact
value `42` reached `poller_output` and no zombie remained
- macOS Homebrew dependency build passed
- macOS `make check`: 5/5 available binaries passed; GNU `--wrap`
runtime suite is Linux-only
- PHP fixture syntax checked through mise PHP 8.3
- actionlint, shellcheck, and `git diff --check` passed

Closes #610.

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-09-22 17:24:04 -04:00

33 lines
815 B
C

#include <stdio.h>
#include <signal.h>
#include <string.h>
int main(int argc, char **argv) {
char command[4096];
const char *mode = argc > 2 ? argv[2] : "";
if (strstr(mode, "exit-before-start") != NULL) return 0;
if (strstr(mode, "silent") == NULL) {
struct sigaction action;
int bad_start = strstr(mode, "bad-start") != NULL;
if (strstr(mode, "check-sigpipe") != NULL &&
(sigaction(SIGPIPE, NULL, &action) != 0 || action.sa_handler != SIG_DFL)) {
bad_start = 1;
}
puts(bad_start ? "Not ready" : "Started");
fflush(stdout);
}
while (fgets(command, sizeof(command), stdin) != NULL) {
if (strcmp(command, "quit\r\n") == 0 || strcmp(command, "quit\n") == 0) {
break;
}
puts(strcmp(command, "poll 7\r\n") == 0 ? "42" : "unexpected command");
fflush(stdout);
}
return 0;
}