Go to file
Fabian @ Blax Software 35ed320202 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 10:38:11 +02:00
.github docs: add copilot instructions 2026-04-16 08:27:28 +02:00
config fix(fork): children never outlive the server or hold its port (#2465) 2026-10-14 10:38:11 +02:00
database/migrations Renovation 2025-01-16 08:54:02 +01:00
docs docs(agents): add AGENTS.md source-verified reference + fix stale wsSession/install docs 2026-06-30 10:56:25 +02:00
resources/views Merge 2.x changes (#1043) 2022-10-06 13:46:54 +02:00
routes A default broadcasting route, I ip on close 2025-12-05 19:56:44 +01:00
src fix(fork): children never outlive the server or hold its port (#2465) 2026-10-14 10:38:11 +02:00
tests fix(fork): children never outlive the server or hold its port (#2465) 2026-10-14 10:38:11 +02:00
.codecov.yml Addded .codecov.yml 2020-08-23 13:54:22 +03:00
.editorconfig wip 2020-09-10 22:59:26 +03:00
.gitattributes Update .gitattributes 2020-06-02 11:26:55 +02:00
.gitignore wip 2020-09-10 22:59:26 +03:00
.scrutinizer.yml wip 2020-09-10 22:59:26 +03:00
.styleci.yml wip 2020-09-10 22:59:26 +03:00
AGENTS.md fix(fork): children never outlive the server or hold its port (#2465) 2026-10-14 10:38:11 +02:00
CONTRIBUTING.md initial commit 2018-11-20 09:54:06 +01:00
LICENSE wip 2020-09-10 22:59:26 +03:00
README.md docs(agents): add AGENTS.md source-verified reference + fix stale wsSession/install docs 2026-06-30 10:56:25 +02:00
composer.json IA helpers 2026-02-03 15:03:50 +01:00
phpunit.xml I performance, A testing & event-driven 2026-01-24 13:34:29 +01:00

README.md

Blax Software OSS

Laravel WebSockets

PHP Version Laravel

Plug-and-play WebSockets for Laravel with a Pusher-compatible protocol, async fork-based handling, attribute-driven routing, and live operational tooling.

[!NOTE] This package is actively maintained as a fork of beyondcode/laravel-websockets.

Features

  • #[Websocket] attribute on regular HTTP controllers — turn any controller method into a WebSocket-callable endpoint with one annotation, no second class to maintain

  • Async processing — incoming messages are handled in pcntl_fork child processes, so a slow handler never blocks the event loop

  • Broadcast from anywhere — ws_broadcast(), ws_whisper(), ws_broadcast_except() helpers and a static WebsocketService API let any controller, job, or service push events to any channel

  • Multiple channel types — public, private-*, presence-*, and open-presence-* channels with the standard auth handshake

  • Live ops tooling — php artisan websockets:watch renders connection counts, authenticated users, and per-channel connections, refreshing every second:

    WebSocket Server — Live Stats
    2026-05-05 14:33:35 — refreshing every 1s (Ctrl+C to exit)
    
      Live Stats .......................................................................................................................................
      Total connections ............................................................................................................................. 12
      Authenticated users ............................................................................................................................ 3
      Active channels ................................................................................................................................ 1
    
    +-----------+-------------+
    | Channel   | Connections |
    +-----------+-------------+
    | websocket | 12          |
    +-----------+-------------+
    
  • Automatic route recognition — controller class and method names map to event names automatically (FlightschoolController::index → flightschool.index); override per method or per class via #[Websocket(event: ..., prefix: ..., suffix: ..., needAuth: true)]

  • Hot code reload in dev, OPcache in prod — websocket:steer cache:clear clears OPcache and the controller resolver cache without restarting the running server, so iteration is instant in development while production runs with fully warmed caches

  • Pusher-compatible protocol — supports both modern websocket.* and legacy pusher:* action formats, drop-in for Echo and pusher-js clients

  • Test helpers — newConnection(), newActiveConnection(), newPrivateConnection(), newPresenceConnection(), plus assertSentEvent() keep WebSocket tests short

Requirements

  • PHP 8.1+
  • Laravel 9, 10, 11 or 12
  • ext-pcntl (for async fork-based handling)

Installation

composer require blax-software/laravel-websockets

Publish the config:

php artisan vendor:publish --provider="BlaxSoftware\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"

Start the server:

php artisan websockets:serve

Default URL is ws://127.0.0.1:6001.

Quick Start

1. Mark a regular controller method as WebSocket-reachable

use BlaxSoftware\LaravelWebSockets\Attributes\Websocket;

class FlightschoolController extends Controller
{
    #[Websocket]                      // event: "flightschool.index"
    public function index() { ... }

    #[Websocket(event: 'flightschools.list')]   // explicit override
    public function list() { ... }

    #[Websocket(needAuth: true)]      // requires authenticated socket
    public function update() { ... }
}

2. Broadcast from anywhere

// Helpers
ws_broadcast('chat.message', ['text' => 'Hello'], 'chat');
ws_whisper('chat.typing', ['typing' => true], ['1234.1', '1234.2'], 'chat');
ws_broadcast_except('chat.message', ['text' => 'Server msg'], ['1234.1'], 'chat');

// Service API
use BlaxSoftware\LaravelWebSockets\Services\WebsocketService;

WebsocketService::send('metrics.tick', ['count' => 1], 'websocket');
WebsocketService::broadcastExcept('chat.message', ['text' => 'Hi'], ['1234.1'], 'chat');

3. Per-connection session store

Inside a WebSocket handler, wsSession() returns a Redis-backed key/value store scoped to the current connection that persists across messages on the same socket. It takes no arguments.

wsSession()->increment('transmit_count');
$count = wsSession()->get('transmit_count', 0);
wsSession()->put('last_action', 'transmitted');

4. Watch live stats

php artisan websockets:watch          # connection counts and channels
php artisan websockets:watch -v       # expanded per-connection rows
php artisan websockets:info           # one-shot snapshot

5. Iterate without restarting

php artisan websocket:steer cache:clear   # clear OPcache + resolver cache
php artisan websockets:restart            # graceful restart
php artisan websocket:restart-hard        # signal-based force restart

Channel Types

Type Prefix Description
Public (none) Anyone can subscribe
Private private- Server-signed auth required
Presence presence- Auth required, tracks user list, broadcasts join/leave
Open Presence open-presence- Presence semantics without the auth signature — useful for guests

Testing

$connection = $this->newActiveConnection(['chat']);

$this->wsHandler->onMessage($connection, new Message([
    'event' => 'websocket.ping',
    'data'  => new stdClass(),
]));

$connection->assertSentEvent('websocket.pong');
vendor/bin/phpunit --exclude-group=stability,stress,integration,requires-server

Documentation

Changelog

See CHANGELOG.

Security

Please report vulnerabilities via the issue tracker or by email: office@blax.at.

Credits

License

MIT. See LICENSE.md.

Star History

Star History Chart