walkor/workerman · critical · RuntimeException

${errMsg}

Error message

${errMsg}

What it means

During Worker->run() the listening socket is created with stream_socket_server() over the configured listen address (tcp://host:port, unix://path). On failure Workerman throws the OS-provided $errMsg verbatim - typical texts are 'Address already in use', 'Permission denied', 'Cannot assign requested address'. This is the classic bind/listen failure and always occurs at worker startup before the event loop runs.

Source

Thrown at src/Worker.php:2462

        }

        if (!$this->mainSocket) {

            $localSocket = $this->parseSocketAddress();

            // Flag.
            $flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
            $errNo = 0;
            $errMsg = '';
            // SO_REUSEPORT.
            if ($this->reusePort && DIRECTORY_SEPARATOR !== '\\') {
                stream_context_set_option($this->socketContext, 'socket', 'so_reuseport', 1);
            }

            // Create an Internet or Unix domain server socket.
            $this->mainSocket = stream_socket_server($localSocket, $errNo, $errMsg, $flags, $this->socketContext);
            if (!$this->mainSocket) {
                throw new RuntimeException($errMsg);
            }

            if ($this->transport === 'ssl') {
                stream_socket_enable_crypto($this->mainSocket, false);
            } elseif ($this->transport === 'unix') {
                $socketFile = substr($localSocket, 7);
                if ($this->user) {
                    chown($socketFile, $this->user);
                }
                if ($this->group) {
                    chgrp($socketFile, $this->group);
                }
            }

            // Try to open keepalive for tcp and disable Nagle algorithm.
            if (function_exists('socket_import_stream') && self::BUILD_IN_TRANSPORTS[$this->transport] === 'tcp') {
                set_error_handler(static fn (): bool => true);
                $socket = socket_import_stream($this->mainSocket);

View on GitHub (pinned to 1391112a61)

Solutions

  1. Find and stop the occupant: 'ss -ltnp | grep :PORT' (or lsof -i :PORT), kill it or enable reusePort ('Worker->reusePort = true') when running multiple instances intentionally
  2. For ports below 1024 run with privileges, use setcap, or put a reverse proxy in front and listen on a high port
  3. Remove a stale unix socket before start (rm /path/app.sock) or add unlink logic in onWorkerStart; fix the listen string to a valid address your host actually owns

Example fix

# before
php start.php start
# RuntimeException: Address already in use

# after
ss -ltnp | grep :8080        # find old master
kill <old-master-pid>         # or: php start.php stop
php start.php start

# for intentional multi-instance listeners:
# $worker->reusePort = true;
Defensive patterns

Strategy: validation

Validate before calling

$port = 8080;
$probe = @fsockopen('127.0.0.1', $port, $errNo, $errStr, 1);
if ($probe !== false) {
    fclose($probe);
    throw new RuntimeException("port $port already in use - stop the old instance or enable reusePort");
}
if ($port < 1024 && (function_exists('posix_getuid') ? posix_getuid() : 0) !== 0) {
    throw new RuntimeException("port $port needs privileges or a reverse proxy");
}

Try / catch

try {
    $worker = new Worker('http://0.0.0.0:8080');
    $worker->reusePort = true; // allow parallel listeners
    Worker::runAll();
} catch (RuntimeException $e) {
    // message is the OS bind error, e.g. 'Address already in use'
    fwrite(STDERR, 'listen failed: ' . $e->getMessage() . PHP_EOL);
    exit(1);
}

Prevention

When it happens

Trigger: Another process holds the port (previous instance not stopped, dev copy running); binding a port < 1024 as non-root; listen address referencing an IPv6 address on a host without IPv6 or a non-local IP; unix socket file already exists (stale socket from a crashed run); SELinux/AppArmor denying the bind.

Common situations: Starting the service twice; crash left a stale unix socket file; deploying as non-root on port 80/443; misconfigured 0.0.0.0 vs specific interface addresses; SELinux-enabled hosts blocking non-standard ports for the service context.

Related errors


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