Skip to content

Commit

Permalink
add restart command
Browse files Browse the repository at this point in the history
  • Loading branch information
joedixon committed Jan 4, 2024
1 parent 8704fc0 commit 8a4cecd
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 5 deletions.
54 changes: 54 additions & 0 deletions src/Concerns/InteractsWithServerState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Laravel\Reverb\Concerns;

use Illuminate\Support\Facades\Cache;

trait InteractsWithServerState
{
/**
* The server state cache key.
*
* @var string
*/
protected $key = 'reverb:server';

/**
* Deteremine whether the server is running.
*/
protected function serverIsRunning(): bool
{
return Cache::has($this->key);
}

/**
* Get the current state of the running server.
*
* @return null|array{HOST: string, PORT: int, DEBUG: bool, RESTART: bool}
*/
protected function getState(): ?array
{
return Cache::get($this->key);
}

/**
* Set the state of the running server.
*/
protected function setState(string $host, int $port, bool $debug, bool $restart = false): void
{
Cache::forever($this->key, [
'HOST' => $host,
'PORT' => $port,
'DEBUG' => $debug ??= false,
'RESTART' => $restart,
]);
}

/**
* Destroy the server state.
*/
protected function destroyState(): void
{
Cache::forget($this->key);
}
}
69 changes: 69 additions & 0 deletions src/Servers/Reverb/Console/Commands/RestartServer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Laravel\Reverb\Servers\Reverb\Console\Commands;

use Illuminate\Console\Command;
use Laravel\Reverb\Concerns\InteractsWithServerState;

class RestartServer extends Command
{
use InteractsWithServerState;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'reverb:restart';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Signal Reverb to restart the server';

/**
* Execute the console command.
*/
public function handle(): void
{
if (! $state = $this->getState()) {
$this->error('No Reverb server running.');

return;
}

$this->sendStopSignal($state);

$this->waitForServerToStop(fn () => $this->call('reverb:start', [
'--host' => $state['HOST'],
'--port' => $state['PORT'],
'--debug' => $state['DEBUG'],
]));
}

/**
* Send the stop signal to the running server.
*
* @param array{HOST: string, PORT: int, DEBUG: bool, RESTART: bool} $state
*/
protected function sendStopSignal(array $state): void
{
$this->components->info('Sending stop signal to Reverb server.');

$this->setState($state['HOST'], $state['PORT'], $state['DEBUG'], true);
}

/**
* Run the callback when the server has stopped.
*/
protected function waitForServerToStop(callable $callback): void
{
while ($this->serverIsRunning()) {
usleep(1000);
}

$callback();
}
}
74 changes: 69 additions & 5 deletions src/Servers/Reverb/Console/Commands/StartServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@
namespace Laravel\Reverb\Servers\Reverb\Console\Commands;

use Illuminate\Console\Command;
use Laravel\Reverb\Application;
use Laravel\Reverb\Concerns\InteractsWithAsyncRedis;
use Laravel\Reverb\Concerns\InteractsWithServerState;
use Laravel\Reverb\Contracts\ApplicationProvider;
use Laravel\Reverb\Contracts\Logger;
use Laravel\Reverb\Jobs\PingInactiveConnections;
use Laravel\Reverb\Jobs\PruneStaleConnections;
use Laravel\Reverb\Loggers\CliLogger;
use Laravel\Reverb\Protocols\Pusher\Channels\ChannelConnection;
use Laravel\Reverb\Protocols\Pusher\Contracts\ChannelManager;
use Laravel\Reverb\Servers\Reverb\Factory as ServerFactory;
use Laravel\Reverb\Servers\Reverb\Http\Server;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use Symfony\Component\Console\Command\SignalableCommandInterface;

class StartServer extends Command
class StartServer extends Command implements SignalableCommandInterface
{
use InteractsWithAsyncRedis;
use InteractsWithAsyncRedis, InteractsWithServerState;

/**
* The name and signature of the console command.
Expand All @@ -38,7 +45,7 @@ class StartServer extends Command
*/
public function handle(): void
{
if ($this->option('debug')) {
if ($debug = $this->option('debug')) {
$this->laravel->instance(Logger::class, new CliLogger($this->output));
}

Expand All @@ -48,17 +55,36 @@ public function handle(): void

$loop = Loop::get();

$server = ServerFactory::make($host, $port, loop: $loop);

$this->bindRedis($loop);
$this->subscribeToRedis($loop);
$this->scheduleCleanup($loop);

$server = ServerFactory::make($host, $port, loop: $loop);
$this->checkForRestartSignal($server, $loop);
$this->setState($host, $port, $debug ??= false);

$this->components->info("Starting server on {$host}:{$port}");

$server->start();
}

/**
* Get the list of signals handled by the command.
*/
public function getSubscribedSignals(): array
{
return [SIGINT, SIGTERM];
}

/**
* Handle the signals sent to the server.
*/
public function handleSignal(int $signal): void
{
$this->gracefullyDisconnect();
$this->destroyState();
}

/**
* Use the event loop to schedule periodic cleanup of connections.
*/
Expand All @@ -70,4 +96,42 @@ protected function scheduleCleanup(LoopInterface $loop): void
PingInactiveConnections::dispatch();
});
}

/**
* Check to see whether the restart signal has been sent.
*/
protected function checkForRestartSignal(Server $server, LoopInterface $loop): void
{
$loop->addPeriodicTimer(5, function () use ($server) {
$state = $this->getState();

if (! $state['RESTART']) {
return;
}

$this->components->info('Stopping Reverb server.');

$this->gracefullyDisconnect();

$server->stop();

$this->destroyState();
});
}

/**
* Gracefully disconnect all connections.
*/
protected function gracefullyDisconnect(): void
{
$this->laravel->make(ApplicationProvider::class)
->all()
->each(function (Application $application) {
collect(
$this->laravel->make(ChannelManager::class)
->for($application)
->connections()
)->each(fn (ChannelConnection $connection) => $connection->disconnect());
});
}
}
2 changes: 2 additions & 0 deletions src/Servers/Reverb/ReverbProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Laravel\Reverb\Concerns\InteractsWithAsyncRedis;
use Laravel\Reverb\Contracts\ServerProvider;
use Laravel\Reverb\Protocols\Pusher\EventDispatcher;
use Laravel\Reverb\Servers\Reverb\Console\Commands\RestartServer;
use Laravel\Reverb\Servers\Reverb\Console\Commands\StartServer;
use React\EventLoop\LoopInterface;

Expand Down Expand Up @@ -40,6 +41,7 @@ public function boot(): void
Artisan::starting(function ($artisan) {
$artisan->resolveCommands([
StartServer::class,
RestartServer::class,
]);
});
}
Expand Down

0 comments on commit 8a4cecd

Please sign in to comment.