From 9c00423d69af65e42a3ef84915320e1bc6af2235 Mon Sep 17 00:00:00 2001 From: Mikk Pokk Date: Wed, 12 Mar 2025 04:21:21 +0200 Subject: [PATCH 1/6] fix: Follow symlinks to enable zero-downtime deployment #1004 --- bin/swoole-server | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/bin/swoole-server b/bin/swoole-server index 3fa19c249..e0e94184b 100755 --- a/bin/swoole-server +++ b/bin/swoole-server @@ -13,11 +13,13 @@ use Swoole\Timer; ini_set('display_errors', 'stderr'); -require_once __DIR__.'/../src/Stream.php'; +$basePath = realpath(getenv('APP_RELEASE_BIN_DIR')); -require __DIR__.'/../fixes/fix-symfony-dd.php'; +require_once $basePath . '/../src/Stream.php'; -$bootstrap = fn ($serverState) => require __DIR__.'/bootstrap.php'; +require $basePath . '/../fixes/fix-symfony-dd.php'; + +$bootstrap = fn($serverState) => require $basePath . '/bootstrap.php'; /* |-------------------------------------------------------------------------- @@ -34,9 +36,9 @@ $serverState = json_decode(file_get_contents( $serverStateFile = $_SERVER['argv'][1] ), true)['state']; -$server = require __DIR__.'/createSwooleServer.php'; +$server = require $basePath . '/createSwooleServer.php'; -$timerTable = require __DIR__.'/createSwooleTimerTable.php'; +$timerTable = require $basePath . '/createSwooleTimerTable.php'; /* |-------------------------------------------------------------------------- @@ -49,6 +51,11 @@ $timerTable = require __DIR__.'/createSwooleTimerTable.php'; | */ +$server->on('beforereload', function (Server $server) use (&$basePath) { + clearstatcache(true); + $basePath = realpath(getenv('APP_RELEASE_BIN_DIR')); +}); + $server->on('start', fn (Server $server) => $bootstrap($serverState) && (new OnServerStart( new ServerStateFile($serverStateFile), new SwooleExtension, @@ -58,13 +65,13 @@ $server->on('start', fn (Server $server) => $bootstrap($serverState) && (new OnS $serverState['octaneConfig']['tick'] ?? true ))($server)); -$server->on('managerstart', function () use ($serverState) { +$server->on('managerstart', function () use (&$serverState, $basePath) { // Don't bootstrap entire application before server / worker start. Otherwise, files can't be gracefully reloaded... #632 - require_once __DIR__.'/../src/Swoole/Handlers/OnManagerStart.php'; - require_once __DIR__.'/../src/Swoole/SwooleExtension.php'; + require_once $basePath . '/../src/Swoole/Handlers/OnManagerStart.php'; + require_once $basePath . '/../src/Swoole/SwooleExtension.php'; (new OnManagerStart( - new SwooleExtension, $serverState['appName'] + new SwooleExtension, $serverState['appName'] ))(); }); @@ -83,13 +90,13 @@ $server->on('managerstart', function () use ($serverState) { | */ -require_once __DIR__.'/WorkerState.php'; +require_once $basePath . '/WorkerState.php'; $workerState = new WorkerState; -$workerState->cacheTable = require __DIR__.'/createSwooleCacheTable.php'; +$workerState->cacheTable = require $basePath . '/createSwooleCacheTable.php'; $workerState->timerTable = $timerTable; -$workerState->tables = require __DIR__.'/createSwooleTables.php'; +$workerState->tables = require $basePath . '/createSwooleTables.php'; $server->on('workerstart', fn (Server $server, $workerId) => (fn ($basePath) => (new OnWorkerStart( From 69eee9d76351b607393939698a2a5ffc84cdd4d9 Mon Sep 17 00:00:00 2001 From: Mikk Pokk Date: Wed, 12 Mar 2025 04:24:28 +0200 Subject: [PATCH 2/6] fix: Follow symlinks to enable zero-downtime deployment #1004 --- src/Commands/StartSwooleCommand.php | 57 ++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/Commands/StartSwooleCommand.php b/src/Commands/StartSwooleCommand.php index 4d31dbc3a..232288b4c 100644 --- a/src/Commands/StartSwooleCommand.php +++ b/src/Commands/StartSwooleCommand.php @@ -43,6 +43,30 @@ class StartSwooleCommand extends Command implements SignalableCommandInterface * @var bool */ protected $hidden = true; + protected bool $isSymlinked = false; + protected string $dir; + protected string $real_cwd; + protected string $pwd; + + public function __construct() + { + $this->dir = __DIR__; + $this->real_cwd = base_path(); + $this->getPwd(); + + if ($this->pwd !== $this->real_cwd) { + $this->isSymlinked = true; + $this->dir = str_replace($this->real_cwd, $this->pwd, $this->dir); + app()->setBasePath($this->pwd); + + $log_file = config('octane.swoole.options.log_file'); + if ($log_file) { + config(['octane.swoole.options.log_file' => $this->realpath($log_file)]); + } + } + + parent::__construct(); + } /** * Handle the command. @@ -83,9 +107,10 @@ public function handle( ...config('octane.swoole.php_options', []), config('octane.swoole.command', 'swoole-server'), $serverStateFile->path(), - ], realpath(__DIR__.'/../../bin'), [ + ], $this->realpath($this->dir.'/../../bin'), [ 'APP_ENV' => app()->environment(), 'APP_BASE_PATH' => base_path(), + 'APP_RELEASE_BIN_DIR' => $this->realpath($this->dir.'/../../bin'), 'LARAVEL_OCTANE' => 1, ]))->start(); @@ -138,6 +163,36 @@ protected function defaultServerOptions(SwooleExtension $extension) ]; } + protected function realpath(string $file): string + { + $realPath = realpath($file); + + if (!$this->isSymlinked) { + return $realPath; + } + + return str_replace($this->real_cwd, $this->pwd, $realPath); + } + + protected function getPwd(): void + { + $working_dir = dirname(request()->server('SCRIPT_NAME')); + if (str_starts_with($working_dir, './')) { + $working_dir = substr($working_dir, 2); + + $this->pwd = getcwd() . '/' . $working_dir; + } elseif ($working_dir === '.') { + // This part comes into action only if the server changes to + // the base path directory before running the Artisan command. + // eg. cd /www/current && php artisan octane:start + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $this->pwd = trim(shell_exec('cd')); // For Windows + } else { + $this->pwd = trim(shell_exec('pwd')); // For Unix-like systems + } + } + } + /** * Get the number of workers that should be started. * From 636e2e615646ef5cec74e7e1160e36cfc02a7c9e Mon Sep 17 00:00:00 2001 From: Mikk Pokk Date: Wed, 12 Mar 2025 04:36:19 +0200 Subject: [PATCH 3/6] StyleCI --- src/Commands/StartSwooleCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Commands/StartSwooleCommand.php b/src/Commands/StartSwooleCommand.php index 232288b4c..0cfff7946 100644 --- a/src/Commands/StartSwooleCommand.php +++ b/src/Commands/StartSwooleCommand.php @@ -167,7 +167,7 @@ protected function realpath(string $file): string { $realPath = realpath($file); - if (!$this->isSymlinked) { + if (! $this->isSymlinked) { return $realPath; } @@ -180,7 +180,7 @@ protected function getPwd(): void if (str_starts_with($working_dir, './')) { $working_dir = substr($working_dir, 2); - $this->pwd = getcwd() . '/' . $working_dir; + $this->pwd = getcwd().'/'.$working_dir; } elseif ($working_dir === '.') { // This part comes into action only if the server changes to // the base path directory before running the Artisan command. From e795fc98fb35141901783463e9aa91a5075708f2 Mon Sep 17 00:00:00 2001 From: Mikk Pokk Date: Wed, 12 Mar 2025 04:55:05 +0200 Subject: [PATCH 4/6] fix: don't convert paths with `realpath()` --- bin/swoole-server | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/swoole-server b/bin/swoole-server index e0e94184b..98e57cf61 100755 --- a/bin/swoole-server +++ b/bin/swoole-server @@ -13,7 +13,7 @@ use Swoole\Timer; ini_set('display_errors', 'stderr'); -$basePath = realpath(getenv('APP_RELEASE_BIN_DIR')); +$basePath = getenv('APP_RELEASE_BIN_DIR'); require_once $basePath . '/../src/Stream.php'; @@ -51,9 +51,8 @@ $timerTable = require $basePath . '/createSwooleTimerTable.php'; | */ -$server->on('beforereload', function (Server $server) use (&$basePath) { +$server->on('beforereload', function (Server $server) { clearstatcache(true); - $basePath = realpath(getenv('APP_RELEASE_BIN_DIR')); }); $server->on('start', fn (Server $server) => $bootstrap($serverState) && (new OnServerStart( From 0141ad0126968df9d5e10fd754d64eae21e34a96 Mon Sep 17 00:00:00 2001 From: Mikk Pokk Date: Wed, 12 Mar 2025 15:53:38 +0200 Subject: [PATCH 5/6] fix: `getPwd()` method for Ubuntu edge cases --- src/Commands/StartSwooleCommand.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Commands/StartSwooleCommand.php b/src/Commands/StartSwooleCommand.php index 0cfff7946..340a59c56 100644 --- a/src/Commands/StartSwooleCommand.php +++ b/src/Commands/StartSwooleCommand.php @@ -177,10 +177,17 @@ protected function realpath(string $file): string protected function getPwd(): void { $working_dir = dirname(request()->server('SCRIPT_NAME')); - if (str_starts_with($working_dir, './')) { - $working_dir = substr($working_dir, 2); + if (($starts_as_curr = str_starts_with($working_dir, './')) || str_starts_with($working_dir, '/')) { + if ($starts_as_curr) { + $working_dir = substr($working_dir, 2); + } - $this->pwd = getcwd().'/'.$working_dir; + $cwd = getcwd(); + if (! str_starts_with($cwd, $working_dir)) { + $this->pwd = getcwd() . '/' . $working_dir; + } else { + $this->pwd = $working_dir; + } } elseif ($working_dir === '.') { // This part comes into action only if the server changes to // the base path directory before running the Artisan command. @@ -190,6 +197,8 @@ protected function getPwd(): void } else { $this->pwd = trim(shell_exec('pwd')); // For Unix-like systems } + } else { + $this->pwd = $this->real_cwd; } } From 18096b156d4b0c074147788afefdab738b63a38e Mon Sep 17 00:00:00 2001 From: Mikk Pokk Date: Wed, 12 Mar 2025 15:55:08 +0200 Subject: [PATCH 6/6] StyleCI --- src/Commands/StartSwooleCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/StartSwooleCommand.php b/src/Commands/StartSwooleCommand.php index 340a59c56..d89f8558e 100644 --- a/src/Commands/StartSwooleCommand.php +++ b/src/Commands/StartSwooleCommand.php @@ -184,7 +184,7 @@ protected function getPwd(): void $cwd = getcwd(); if (! str_starts_with($cwd, $working_dir)) { - $this->pwd = getcwd() . '/' . $working_dir; + $this->pwd = "$cwd/$working_dir"; } else { $this->pwd = $working_dir; }