laravel-websockets/tests/Websocket/Fixtures/ChildCrash/ChildCrashController.php

44 lines
908 B
PHP
Raw Normal View History

fix(fork): children never outlive the server or hold its port (#2465) A fork-per-message child inherits the server's listening socket. A child that outlived the server (a long-running handler such as an engine loop) kept the port bound, so the restarted server failed with EADDRINUSE until that child died. - Track every forked child (Server\ChildProcesses). On hard and soft shutdown, send SIGTERM to the ones still running, SIGKILL them after websockets.shutdown.child_grace_seconds (default 2) and reap them. A PID is signalled only while waitpid() confirms it is still an unreaped child, with SIGCHLD blocked, so a recycled PID is never hit. Opt out with websockets.shutdown.terminate_children = false. - Right after fork the child closes its copy of the listening socket, so even a stray child cannot hold the port. Only the fd is closed: the React server is not closed, because removing it from an ext-ev/ext-event loop would edit the epoll set the child shares with the parent. - The child restores default SIGTERM/SIGINT/SIGCHLD handling. Under ext-ev the inherited libev handlers swallowed SIGTERM in the child. - A child that dies of a fatal error (out of memory, E_ERROR) now sends <event>:error from a shutdown function (Server\ChildFatalErrorGuard, #1462). A child that dies without any reply (killed by a signal, or the shutdown function never ran) gets <event>:error from the parent, decided once both its stream and its exit status are in (Websocket\ChildMessageOutcome). - Tests: real forks, signals and sockets for the shutdown, the port release and the fatal reply; end to end through Handler::forkWithSocketPair(). The test TestCase now stops a leftover broadcast socket, which made ws_available() true for later tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 08:38:11 +00:00
<?php
declare(strict_types=1);
namespace BlaxSoftware\LaravelWebSockets\Test\Websocket\Fixtures\ChildCrash;
use BlaxSoftware\LaravelWebSockets\Attributes\Websocket;
/**
* Handlers for HandlerChildLifecycleTest. They run in real forked message
* children. Events: child-crash.ok, child-crash.killed, child-crash.hold.
*/
class ChildCrashController
{
#[Websocket]
public function ok(): array
{
return ['ok' => true];
}
/**
* Dies without replying, like a child taken by the kernel OOM killer.
*/
#[Websocket]
public function killed(): array
{
posix_kill(getmypid(), SIGKILL);
sleep(5);
return ['unreachable' => true];
}
/**
* Blocks like a long-running engine until the server stops it.
*/
#[Websocket]
public function hold(): array
{
sleep(10);
return ['held' => true];
}
}