Skip to content

Swoole: Follow symlinks to enable zero-downtime deployment #1009

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: 2.x
Choose a base branch
from
30 changes: 18 additions & 12 deletions bin/swoole-server
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ use Swoole\Timer;

ini_set('display_errors', 'stderr');

require_once __DIR__.'/../src/Stream.php';
$basePath = 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';

/*
|--------------------------------------------------------------------------
Expand All @@ -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';

/*
|--------------------------------------------------------------------------
Expand All @@ -49,6 +51,10 @@ $timerTable = require __DIR__.'/createSwooleTimerTable.php';
|
*/

$server->on('beforereload', function (Server $server) {
clearstatcache(true);
});

$server->on('start', fn (Server $server) => $bootstrap($serverState) && (new OnServerStart(
new ServerStateFile($serverStateFile),
new SwooleExtension,
Expand All @@ -58,13 +64,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']
))();
});

Expand All @@ -83,13 +89,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(
Expand Down
92 changes: 91 additions & 1 deletion src/Commands/StartSwooleCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,47 @@ class StartSwooleCommand extends Command implements SignalableCommandInterface
*/
protected $hidden = true;

/**
* Indicates whether the command runs in a symlinked directory path.
*/
protected bool $isSymlinked = false;

/**
* The reliable directory path to use, relative to the Octane path.
*/
protected string $dir;

/**
* The actual path, rather than the symlinked path.
*/
protected string $real_cwd;

/**
* The reliable working directory path of the process.
* Respects the symlink path and does not convert to the real path.
*/
protected string $pwd;

public function __construct()
{
$this->dir = __DIR__;
$this->real_cwd = base_path();
$this->setPwd();

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.
*
Expand Down Expand Up @@ -83,9 +124,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();

Expand Down Expand Up @@ -138,6 +180,54 @@ protected function defaultServerOptions(SwooleExtension $extension)
];
}

/**
* Returns the real path of a file. If the working directory path is symlinked,
* it returns the file location relative to the reliable working directory path of the process,
* instead of the real path.
*/
protected function realpath(string $file): string
{
$realPath = realpath($file);

if (! $this->isSymlinked) {
return $realPath;
}

return str_replace($this->real_cwd, $this->pwd, $realPath);
}

/**
* Determines and sets the reliable working directory path of the process.
* Respects the symlink path and does not convert to the real path.
*/
protected function setPwd(): void
{
$working_dir = dirname(request()->server('SCRIPT_NAME'));
if (($starts_as_curr = str_starts_with($working_dir, './')) || str_starts_with($working_dir, '/')) {
if ($starts_as_curr) {
$working_dir = substr($working_dir, 2);
}

$cwd = getcwd();
if (! str_starts_with($cwd, $working_dir)) {
$this->pwd = "$cwd/$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.
// 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
}
} else {
$this->pwd = $this->real_cwd;
}
}

/**
* Get the number of workers that should be started.
*
Expand Down
Loading