reapChildren() // optional: auto-reap forked children * ->register($webSocketServer) // any Contracts\Server * ->register($webRtcSignaling) * ->onBoot(fn (Kernel $k) => ...) * ->run(); // boots servers, installs signals, runs the loop */ final class Kernel { private LoopInterface $loop; /** @var Server[] */ private array $servers = []; /** @var array */ private array $onBoot = []; /** @var array */ private array $onShutdown = []; private bool $running = false; private ?ChildReaper $reaper = null; public function __construct(?LoopInterface $loop = null) { // Default to the global loop so co-located libraries share one reactor. $this->loop = $loop ?? Loop::get(); } public function loop(): LoopInterface { return $this->loop; } /** Attach a protocol server (WS, WebRTC, ...) to the shared loop. */ public function register(Server $server): self { $this->servers[] = $server; return $this; } public function onBoot(callable $callback): self { $this->onBoot[] = $callback; return $this; } public function onShutdown(callable $callback): self { $this->onShutdown[] = $callback; return $this; } /** * Automatically reap exited forked children so they never accumulate as * zombies (the concern behind laravel-websockets #982). Opt-in because not * every deployment forks. */ public function reapChildren(float $interval = 1.0): self { $this->reaper = new ChildReaper($this->loop); $this->reaper->install($interval); return $this; } /** Boot every registered server, install signal handlers, run the loop (blocks). */ public function run(): void { if ($this->running) { return; } $this->running = true; foreach ($this->servers as $server) { $server->boot($this->loop); } foreach ($this->onBoot as $callback) { $callback($this); } SignalHandler::install($this->loop, fn () => $this->stop()); $this->loop->run(); } /** Graceful shutdown: tear down servers, run shutdown hooks, stop the loop. */ public function stop(): void { if (! $this->running) { return; } foreach ($this->servers as $server) { try { $server->shutdown(); } catch (\Throwable) { // A failing teardown must not block the others. } } foreach ($this->onShutdown as $callback) { try { $callback($this); } catch (\Throwable) { // ignore } } $this->reaper?->reapAll(); $this->reaper?->uninstall(); $this->running = false; $this->loop->stop(); } public function isRunning(): bool { return $this->running; } }