diff --git a/src/Console/Commands/StartServer.php b/src/Console/Commands/StartServer.php index 9ec36e2..6301548 100644 --- a/src/Console/Commands/StartServer.php +++ b/src/Console/Commands/StartServer.php @@ -74,6 +74,13 @@ class StartServer extends Command */ protected $restartSoftShutdown = false; + /** + * The last steer signal timestamp. + * + * @var int|null + */ + protected $lastSteer = null; + /** * Initialize the command. * @@ -148,6 +155,10 @@ class StartServer extends Command $this->configureRestartTimer(); \Log::channel('websocket')->debug('Restart timer configured'); + \Log::channel('websocket')->debug('Configuring steer timer...'); + $this->configureSteerTimer(); + \Log::channel('websocket')->debug('Steer timer configured'); + \Log::channel('websocket')->debug('Configuring routes...'); $this->configureRoutes(); \Log::channel('websocket')->debug('Routes configured'); @@ -290,6 +301,84 @@ class StartServer extends Command \Log::channel('websocket')->debug('WebSocket routes registered'); } + /** + * Configure the timer that polls for steer signals (cache:clear, etc.). + * + * @return void + */ + public function configureSteerTimer(): void + { + $steerData = Cache::store('file')->get('blax:websockets:steer'); + $this->lastSteer = $steerData['time'] ?? null; + + \Log::channel('websocket')->debug('Steer timer configured', [ + 'initial_steer_time' => $this->lastSteer, + ]); + + $this->loop->addPeriodicTimer(5, function () { + $steerData = Cache::store('file')->get('blax:websockets:steer'); + $currentSteer = $steerData['time'] ?? null; + + if ($currentSteer !== null && $currentSteer !== $this->lastSteer) { + $action = $steerData['action'] ?? null; + $this->lastSteer = $currentSteer; + + \Log::channel('websocket')->info("Steer signal received: {$action}"); + $this->handleSteerAction($action); + } + }); + } + + /** + * Execute a steer action received via the cache signal. + */ + protected function handleSteerAction(?string $action): void + { + switch ($action) { + case 'cache:clear': + $this->steerCacheClear(); + break; + + case 'restart': + $this->restartSoftShutdown = false; + $this->triggerShutdown(true); + break; + + case 'restart:soft': + $this->restartSoftShutdown = true; + $this->triggerShutdown(true); + break; + + default: + \Log::channel('websocket')->warning("Unknown steer action: {$action}"); + } + } + + /** + * Clear OPcache and the controller resolution cache so the running + * server picks up new code from disk without a full restart. + */ + protected function steerCacheClear(): void + { + $cleared = []; + + // 1. Reset OPcache — forces PHP to recompile files from disk + if (function_exists('opcache_reset')) { + opcache_reset(); + $cleared[] = 'opcache'; + } + + // 2. Clear controller resolver cache so new/changed controllers are discovered + if (class_exists(\BlaxSoftware\LaravelWebSockets\Websocket\ControllerResolver::class)) { + \BlaxSoftware\LaravelWebSockets\Websocket\ControllerResolver::clearCache(); + \BlaxSoftware\LaravelWebSockets\Websocket\ControllerResolver::preload(); + $cleared[] = 'controllers'; + } + + \Log::channel('websocket')->info('Steer cache:clear executed', ['cleared' => $cleared]); + $this->line('Cache cleared: ' . implode(', ', $cleared)); + } + /** * Configure the PCNTL signals for soft shutdown. * diff --git a/src/Console/Commands/SteerServer.php b/src/Console/Commands/SteerServer.php new file mode 100644 index 0000000..63c8162 --- /dev/null +++ b/src/Console/Commands/SteerServer.php @@ -0,0 +1,53 @@ + */ + protected array $actions = [ + 'cache:clear' => 'Clear OPcache and controller resolution cache (picks up new code without full restart)', + 'restart' => 'Hard-restart the server (stops loop, supervisord restarts the process)', + 'restart:soft' => 'Soft-restart the server (gracefully close connections first)', + ]; + + public function handle(): int + { + $action = $this->argument('action'); + + if (! array_key_exists($action, $this->actions)) { + $this->error("Unknown action: {$action}"); + $this->line(''); + $this->info('Available actions:'); + foreach ($this->actions as $name => $desc) { + $this->line(" {$name} {$desc}"); + } + return self::FAILURE; + } + + $store = $this->option('cache-driver') ?: 'file'; + + Cache::store($store)->forever('blax:websockets:steer', [ + 'action' => $action, + 'time' => $this->currentTime(), + ]); + + \Log::channel('websocket')->info("WebSocket steer signal sent: {$action}"); + $this->info("Sent '{$action}' signal to the WebSocket server."); + $this->line("{$this->actions[$action]}"); + + return self::SUCCESS; + } +} diff --git a/src/WebSocketsServiceProvider.php b/src/WebSocketsServiceProvider.php index 599375e..04acbc0 100644 --- a/src/WebSocketsServiceProvider.php +++ b/src/WebSocketsServiceProvider.php @@ -202,6 +202,7 @@ class WebSocketsServiceProvider extends ServiceProvider $this->commands([ Console\Commands\StartServer::class, Console\Commands\RestartServer::class, + Console\Commands\SteerServer::class, Console\Commands\CleanStatistics::class, Console\Commands\FlushCollectedStatistics::class, ]);