flarum/framework · info

Halt signal received, killing to restart.

Error message

Halt signal received, killing to restart.

What it means

The realtime websocket server (ServeCommand) polls the cache every 10 seconds for the HaltCommand KEY; when present, it warns 'Halt signal received, killing to restart.', clears the key, and stops the ReactPHP loop so the process exits (typically for a supervisor/systemd to restart it with fresh code/config).

Solutions

  1. No action needed — this is expected restart behavior
  2. Ensure a process supervisor (systemd, pm2, Docker restart policy) restarts the server after exit
  3. If it loops unexpectedly, inspect who is running `php flarum halt` or what writes HaltCommand::KEY to the cache
Defensive patterns

Strategy: fallback

Validate before calling

# ensure a supervisor restarts the websocket server
# systemd: Restart=always under [Service]

Prevention

When it happens

Trigger: Running `php flarum websocket:serve` (or the serve command) while `php flarum halt` has been executed elsewhere, setting HaltCommand::KEY in the cache.

Common situations: Deployments or config changes that call `flarum halt` to signal the long-running websocket process to recycle; admin actions that require a restart.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of flarum/framework@4b939f6853 (2026-09-15). Data as JSON: /api/errors/a06cefba6bcf5f35. Report an issue: GitHub.

Appendix: source

Thrown at extensions/realtime/src/Websocket/Console/ServeCommand.php:180

                'data' => ['revision' => $token],
            ];

            $manager->getChannels()->then(function (array $channels) use ($payload) {
                foreach ($channels as $name => $channel) {
                    if ($name === 'public' || str_starts_with($name, 'private-user=')) {
                        $payload->channel = $name;
                        $channel->broadcast($payload);
                    }
                }
            });
        });
    }

    protected function restartOnCachedSignal(LoopInterface $loop, Repository $cache): void
    {
        $loop->addPeriodicTimer(10, function (TimerInterface $timer) use ($loop, $cache) {
            if ($cache->has($key = HaltCommand::KEY)) {
                $this->warn('Halt signal received, killing to restart.');

                $cache->forget($key);

                $loop->stop();
            }
        });
    }

    protected function restartOnExtensionChanges(LoopInterface $loop): void
    {
        $enabled = null;

        $loop->addPeriodicTimer(10, function (TimerInterface $timer) use ($loop, &$enabled) {
            $app = $this->getLaravel();

            // If a Redis-backed settings cache is bound (e.g. fof/redis), use the settings
            // repository which will read from Redis. Otherwise fall back to a direct DB query,
            // because the default MemoryCacheSettingsRepository is a per-process in-memory cache

View on GitHub (pinned to 4b939f6853)