Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions dev/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ an item marked "reproduce" gets its failing run before any code.
9. `test_static_decoders` back in the unit suite (issue to open).
10. #314 — no action until it recurs.
11. HTTP/3 `chunk_queue` primer: with the next change to `h3_stream_append_chunk`.
12. `http_server_pause_listeners` and the accept it may not stop: reproduce.

- [ ] **Drop the streaming exemption in laravel-spawn.** `TrueAsyncServer::streamContent`
calls `setNoCompression()` on every `StreamedResponse` as the workaround for #170
Expand Down Expand Up @@ -1673,6 +1674,36 @@ a release yet except the first two, which are fixed.
still has none, so its summation is proved only on a run where the six bad
requests did land on more than one worker. The file's header comment says so.

## The room retry tests on the shared listen fd (#329)

- [x] **`064`–`066` skip on the shared listen fd.** They need a subscriber on each
of two workers, which the server does not promise (#240), and on the shared fd
it measurably does not happen: `065`/`066` failed there three times in two runs
of PR #328, and locally `065` read `no subscriber on a remote worker` on 3 of
10, 5 of 20 and 2 of 20 runs against 0 of 16 on SO_REUSEPORT. The instrumented
helper showed the failing runs at `[0,4]` after round 0 and `[0,32]` after
round 7; in passing ones the first worker held 1 to 3. The retry queue they
test (`room_hub_post_locked`) reads nothing about the listener and stays
covered by the first CI pass. `tests/phpt/websocket/_ws_spread_skipif.inc`
mirrors `core/076`'s condition. Evidence: with
`TRUE_ASYNC_SERVER_SHARED_LISTEN_FD=1` the three tests skip with the reason;
without it each passes 10 of 10. Decided with Edmond on 2026-09-29.

Left open: the cause of the skew is not traced — every shared-fd run gave the
majority to the second worker, which the idea of a busy worker winning does
not explain; the measurements and the tracing step are in #329, together with
the question whether #240's answer still holds. `telemetry/012` rests on the
same premise in the second pass and has not failed yet; the same predicate
applies if it does. Opening connections in a burst, so both workers drain one
backlog, is an untried alternative to skipping.

- [ ] **`http_server_pause_listeners` may not stop accepting on the socket.** It
calls the listen event's `stop`, and `libuv_listen_stop` in `ext/async`'s
reactor only drops a reference count ("uv_listen doesn't have a stop
function"); the pause tests check counters, not the fd (`h1/011`: "Don't
check listeners_paused"). Found while reading for #329, not run: the first step
is a test that stays connected-and-accepted while the listeners are paused.

## A WebSocket close is lost when the peer is still sending (#303 fallout)

- [x] **The 1013 close never reaches a client that overflowed the inbound
Expand Down
2 changes: 2 additions & 0 deletions tests/phpt/websocket/064-room-retry-delivered.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Rooms reliable-send: a full target parks then a retry lands it (retry_queued ->
if (!function_exists('TrueAsync\\__test_force_topic_post_full')) {
echo "skip requires --enable-tas-test-hooks (fault-injection hook absent)";
}
require __DIR__ . '/_ws_spread_skipif.inc';
ws_spread_skipif();
?>
--EXTENSIONS--
true_async_server
Expand Down
2 changes: 2 additions & 0 deletions tests/phpt/websocket/065-room-retry-expired.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Rooms reliable-send: a target that stays full past the deadline expires and send
if (!function_exists('TrueAsync\\__test_force_topic_post_full')) {
echo "skip requires --enable-tas-test-hooks (fault-injection hook absent)";
}
require __DIR__ . '/_ws_spread_skipif.inc';
ws_spread_skipif();
?>
--EXTENSIONS--
true_async_server
Expand Down
2 changes: 2 additions & 0 deletions tests/phpt/websocket/066-room-retry-rejected.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Rooms reliable-send: enqueue past the outbound-queue cap is refused (retry_rejec
if (!function_exists('TrueAsync\\__test_force_topic_post_full')) {
echo "skip requires --enable-tas-test-hooks (fault-injection hook absent)";
}
require __DIR__ . '/_ws_spread_skipif.inc';
ws_spread_skipif();
?>
--EXTENSIONS--
true_async_server
Expand Down
26 changes: 26 additions & 0 deletions tests/phpt/websocket/_ws_spread_skipif.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/* SKIPIF for a test that needs a subscriber on every worker.
*
* Which worker accepts a connection is not something the server promises
* (true-async/server#240). Where the kernel load-balances SO_REUSEPORT, the
* rounds of ws_open_spread_subscribers reach every worker in practice. On the
* shared listen fd — the default outside Linux and FreeBSD, and the second
* Linux CI run through the env — they measurably do not: on 2026-09-29, 32
* WebSocket connections to 2 workers split 2:30 to 11:21 there against 10:22
* to 22:10 on SO_REUSEPORT, and 065 ended at 0:32 on 2 to 5 runs of 20. The
* condition mirrors core/076's, which selects the same camp.
*
* Usage:
* <?php
* require __DIR__ . '/_ws_spread_skipif.inc';
* ws_spread_skipif();
* ?>
*/

function ws_spread_skipif(): void
{
if (!((PHP_OS_FAMILY === 'Linux' || PHP_OS_FAMILY === 'BSD')
&& getenv('TRUE_ASYNC_SERVER_SHARED_LISTEN_FD') !== '1')) {
die('skip shared listen fd: no subscriber on every worker is promised (#240)');
}
}
Loading