walkor/workerman · critical · RuntimeException

${msg}

Error message

${msg}

What it means

Workerman runs Worker::checkPortAvailable() on Linux while the master process is in STATUS_STARTING: for every tcp (non-unix, non-udp) listener it does a probe stream_socket_server() bind and installs a set_error_handler that converts the PHP warning into this RuntimeException. So the message is the raw stream_socket_server warning, e.g. 'stream_socket_server(): unable to connect to tcp://0.0.0.0:8080 (Address already in use)'. The library throws it to fail fast, before forking worker processes, when the address cannot be bound.

Source

Thrown at src/Worker.php:2536

     *
     * @return void
     */
    protected static function checkPortAvailable(): void
    {
        foreach (static::$workers as $worker) {
            $socketName = $worker->getSocketName();
            if (DIRECTORY_SEPARATOR === '/'  // if linux
                && static::$status === static::STATUS_STARTING // only for starting status
                && $worker->transport === 'tcp' // if tcp socket
                && !str_starts_with($socketName, 'unix') // if not unix socket
                && !str_starts_with($socketName, 'udp')) { // if not udp socket

                $address = parse_url($socketName);
                if (isset($address['host']) && isset($address['port'])) {
                    $address = "tcp://{$address['host']}:{$address['port']}";
                    $server = null;
                    set_error_handler(function ($code, $msg) {
                        throw new RuntimeException($msg);
                    });
                    $server = stream_socket_server($address, $code, $msg);
                    if ($server) {
                        fclose($server);
                    }
                    restore_error_handler();
                }
            }
        }
    }

    /**
     * Parse local socket address.
     */
    protected function parseSocketAddress(): ?string
    {
        if (!$this->socketName) {
            return null;

View on GitHub (pinned to 1391112a61)

Solutions

  1. Find and stop the process holding the port: `ss -ltnp 'sport = :8080'` or `lsof -i :8080`, then `php start.php stop` (or kill the PID) and start again.
  2. If the other listener is intentional and you want several instances sharing the port, set `$worker->reusePort = true;` on Linux (SO_REUSEPORT).
  3. Change to a free port in the Worker constructor, e.g. `new Worker('http://0.0.0.0:8081')`.
  4. For ports below 1024, run with adequate privilege or grant the PHP binary the capability once: `sudo setcap 'cap_net_bind_service=+ep' $(which php)`.
  5. If the host part is wrong (typo like 0.0.0.1 or an IP not on the machine), correct it to a local address such as 127.0.0.1 or 0.0.0.0.

Example fix

// before: second instance while the first still holds the port
$worker = new Worker('http://0.0.0.0:8080');
Worker::runAll(); // RuntimeException: stream_socket_server(): unable to connect to tcp://0.0.0.0:8080 (Address already in use)

// after: stop the old instance first, or opt into port sharing
$worker = new Worker('http://0.0.0.0:8080');
$worker->reusePort = true; // SO_REUSEPORT, allows multiple listeners on Linux
Worker::runAll();
Defensive patterns

Strategy: validation

Validate before calling

// Probe the port before handing control to Workerman
$listen = '0.0.0.0:8080';
$probe = @stream_socket_server("tcp://$listen", $errno, $errstr);
if ($probe === false) {
    fwrite(STDERR, "Cannot bind $listen: $errstr\n");
    exit(1);
}
fclose($probe); // release it so Workerman can bind

$worker = new Worker("http://$listen");
Worker::runAll();

Try / catch

try {
    Worker::runAll();
} catch (RuntimeException $e) {
    if (str_contains($e->getMessage(), 'Address already in use')) {
        // port conflict: report PID hint and exit non-zero
        fwrite(STDERR, $e->getMessage() . "\nRun: php start.php stop\n");
        exit(1);
    }
    throw $e;
}

Prevention

When it happens

Trigger: Running `php start.php start` (the runAll path sets STATUS_STARTING) on Linux when: another process (a previous Workerman instance, nginx, apache, a dev server) already listens on the same host:port; the port is <1024 and the process is not root/cap_net_bind_service; the host part of listen resolves to an address not available on the machine; or the address is otherwise invalid. Only workers with transport==='tcp' and a socketName not starting with 'unix'/'udp' are probed.

Common situations: Starting a second copy of the server while the old one is still alive (kill -9 left the socket held, or the stop command was skipped); deploying to a container/port mapping that collides with another service; switching to port 80/443 without privileges; porting a config that worked on macOS/Windows (the probe only runs when DIRECTORY_SEPARATOR === '/').

Related errors


AI-assisted analysis of walkor/workerman@1391112a61 (2026-08-21). Data as JSON: /api/errors/f2e8f1f51cf1a3a2. Report an issue: GitHub.