Skip to content

#143 fuzzy-tests: chaos coverage gaps — proc_open, curl_multi, feof, flock, SSL accept, socket ext#147

Merged
EdmondDantes merged 6 commits into
mainfrom
143-proc_openexec-curl_multi-socket-ext-feof-flock-ssl-accept
May 25, 2026
Merged

#143 fuzzy-tests: chaos coverage gaps — proc_open, curl_multi, feof, flock, SSL accept, socket ext#147
EdmondDantes merged 6 commits into
mainfrom
143-proc_openexec-curl_multi-socket-ext-feof-flock-ssl-accept

Conversation

@EdmondDantes
Copy link
Copy Markdown
Contributor

Umbrella #143: closes the chaos-coverage white zones identified after #138 (Layer 1+2 IO) merged. Six new features under fuzzy-tests/, 21 .phpt total, green on fifo + 3–5 random seeds and ASAN-ZTS.

Coverage added

Feature Scenarios .phpt Status
exec/proc_open_chaos storm (4 coroutines × 5 cycles) 1
curl/curl_multi_chaos clean fetch, per-handle abort, two-coro multi 3
io/feof_chaos drain, close-timing sweep, poller, cancel 6
io/flock_chaos 4-way contention, holder + 2 waiters 2
io/ssl_accept_chaos 3-server, 4-client fan-in, cancel + sweep 7
io/socket_ext_chaos connect/recv/accept cancel + timing sweep 6

Findings filed while drafting

Three real bugs surfaced — all confirmed by minimal repros (≤50 lines) outside the harness; ASAN traces or deadlock-detector reports attached on each issue. The blocked scenarios are kept commented in the feature files under # Blocked: #N for one-line reinstatement after each fix.

Harness additions

  • Context::$processes, Context::$lockFiles registries + teardown
  • proc-open, sockets SKIPIF tags
  • ~10 new step verbs (proc_open lifecycle, feof drain/poll, flock acquire, curl_multi fetch, socket_* connect/recv/accept)
  • StandardSteps::curlMulti() helper mirroring curlGet()

Test plan

First Layer-3 (process) chaos: four coroutines cycling proc_open + proc_close
hammer the reactor's process_events hashtable under random scheduling.
Backstops tests/exec/011-proc_open_handle_reuse_uaf with concurrent open/close
instead of sequential.

The parked-fread-vs-close scenarios surfaced a real reactor bug while being
drafted: proc_terminate + proc_close does not wake a parked fread() on the
child's stdout pipe; the deadlock detector eventually aborts the request.
Reproduced outside the harness in ~25 lines, filed as #144. Those scenarios
are kept commented in the feature so they can be reinstated by uncomment
once the fix lands.

Context: new $processes registry + teardown.
Steps: long-lived child, reads stdout, closes (terminate+reap), SIGTERM,
storm (proc_open+proc_close N times).
generate.php: new SKIPIF tag `proc-open` (POSIX-only, requires
TEST_PHP_EXECUTABLE).

Storm scenario green under fifo + 4 random seeds, plus ASAN-ZTS. Full
fuzzy-tests suite (735 phpt) stays 100% pass.

Umbrella: #143
… cancel)

First chaos coverage of ext/curl/curl_async.c's curl_multi_select() reactor
integration (single-handle curl_exec already covered by #136). One coroutine
attaches N easy handles to one curl_multi_init() and drives the standard
exec/select loop; per-handle CURLMSG_DONE drains into outcome buckets.

Three scenarios shipped:
  - three peers, all return intact;
  - one peer aborts mid-body, others still complete;
  - two coroutines each owning their own multi handle.

The three planned cancel-mid-multi-select scenarios surfaced a real
heap-corruption bug: AsyncCancellation interrupting curl_multi_select()
corrupts the zend allocator free-list and the next coroutine SEGVs in
zend_mm_alloc_small. Reproduced in ~50 lines outside the harness on
ASAN-ZTS. Filed as #145; the scenarios stay commented in the feature
under `# Blocked: #145` for one-line reinstatement after the fix.

3 .phpt × 4 random seeds green, plus ASAN-ZTS. Full fuzzy suite stays at
100% pass.

Umbrella: #143
ext/async/tests/stream/038–044 pin the deterministic feof() return value at
specific points; this feature crosses them into chaos. A writer pushes N
bytes then closes the write end, a reader drains via canonical
`while (!feof) fread` loop, an optional third coroutine spams feof() from
a sibling context.

Invariants: drain loop receives every byte BEFORE feof becomes true
(no premature-EOF data loss), drain/cancel/fail outcomes sum to attempts.

Steps: `drains shared pipe ... with feof loop` (counter family
feof_drain_* incl. feof_drain_bytes) and `polls feof of shared pipe ...
N times` (feof_poll_*).

6 .phpt × 5 seeds green, plus ASAN-ZTS.

Umbrella: #143
…on cancel)

ext/async/tests/io/081-flock_non_blocking_event_loop.phpt pins one
deterministic shape; this feature fuzzes the contention surface — four
coroutines contend for one LOCK_EX, plus holder + two waiters all
eventually acquire.

Two cancel-mid-flock scenarios surfaced a real stack-use-after-return in
main/streams/plain_wrapper.c:1192: the flock task struct lives on the
caller's stack but the libuv worker keeps writing to it after the
coroutine unwinds on cancel. Confirmed by ASAN-ZTS trace pointing at
plain_wrapper.c:1097. Filed as #146; cancel scenarios stay commented
under `# Blocked: #146` for one-line reinstatement after fix.

Context: $lockFiles registry, unlink in __destruct.
Steps: shared lock file, acquire LOCK_EX + release after N ms.

2 .phpt × 3 random seeds green, plus ASAN-ZTS.

Umbrella: #143
tests/stream/027-ssl_concurrent_accept.phpt pins the deterministic
"three SSL servers each accept one client" shape; the existing
tls_connect.feature (#138) covers client-side cancellation. This feature
is the accept-side mirror:

  - three independent ssl:// servers each accept one client (027 under
    random scheduling);
  - one server fans in four concurrent TLS clients;
  - acceptor cancelled while parked in stream_socket_accept() (timing
    sweep 0 / 5 / 50 / 200 ms).

Reuses existing TLS server + TLS client steps.

7 .phpt × 5 random seeds green, plus ASAN-ZTS. No findings — the SSL
accept path is solid.

Umbrella: #143
First chaos coverage of the ext/sockets API path (xp_socket.c /
network_async.c), distinct from the streams layer covered by
cancel_during_connect / udp_chaos / cancel_during_io.

Three verbs on the cancel surface:
  - socket_connect to TCP blackhole (cancel-timing sweep 0/5/50 ms);
  - socket_recvfrom on fresh bound UDP socket;
  - socket_accept on fresh TCP listener.

Confirmed ext/sockets is reactor-integrated via separate probe (recvfrom
parks ~50 ms before cancellation lands).

6 .phpt × 4 random seeds green, plus ASAN-ZTS. No findings.

Umbrella: #143
@EdmondDantes EdmondDantes linked an issue May 24, 2026 that may be closed by this pull request
@EdmondDantes EdmondDantes merged commit ff5da2d into main May 25, 2026
7 checks passed
@EdmondDantes EdmondDantes deleted the 143-proc_openexec-curl_multi-socket-ext-feof-flock-ssl-accept branch May 25, 2026 11:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

proc_open/exec, curl_multi, socket ext, feof, flock, SSL accept

1 participant