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,
]);