Skip to content

Possibility to run websocket server in background (demonizing) #8

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 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions Command/StartCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace Varspool\WebsocketBundle\Command;

use Wrench\Server;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;


class StartCommand extends WebsocketCommand
{

/**
* @see Symfony\Component\Console\Command.Command::configure()
*/
protected function configure()
{
$this
->setName('websocket:start')
->setDescription('Start websocket server daemon')
->addArgument(
'server_name',
InputArgument::OPTIONAL,
'The server name (from your varspool_websocket configuration)',
'default'
);
}

/**
* @see Symfony\Component\Console\Command.Command::execute()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{

$name = $input->getArgument('server_name');
parent::setConfName($name);

if (parent::checkPid()) {
$output->writeln('<error>PID file already exists. Maybe the server is running?</error>');
return;
}

// fork process for demonizing
$pid = pcntl_fork();

if ($pid < 0) {
$output->writeln('<error>Unable to start the server process</error>');
return 1;
}

if ($pid > 0) {
$output->writeln(sprintf('<info>%s WebSocket server is running</info>', $name));
// stop parent process
return;
}

if (posix_setsid() < 0) {
$output->writeln('<error>Unable to make a process as session leader</error>');
return 1;
}

$manager = $this->getContainer()->get('varspool_websocket.server_manager');

// use Symfony's Monolog for logging
$logger = $this->getContainer()->get('logger');
$manager->setLogger(function ($message, $level) use ($logger) {
$logger->log( $level, $message );
});

// create pid file and handle system signals
parent::createPid();
parent::handleSignal();

// use parent::listenPid() method as callable to shutdown the server
$server = $manager->getServer($name);
$server->run(array('Varspool\WebsocketBundle\Command\WebsocketCommand', 'listenPid'));
}
}
?>
53 changes: 53 additions & 0 deletions Command/StopCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Varspool\WebsocketBundle\Command;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;


class StopCommand extends WebsocketCommand
{
/**
* @see Symfony\Component\Console\Command.Command::configure()
*/
protected function configure()
{
$this
->setName('websocket:stop')
->setDescription('Stop websocket server')
->addArgument(
'server_name',
InputArgument::OPTIONAL,
'The server name (from your varspool_websocket configuration)',
'default'
);
}

/**
* @see Symfony\Component\Console\Command.Command::execute()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{

$name = $input->getArgument('server_name');
parent::setConfName($name);

if (!parent::checkPid()) {
$output->writeln("<error>PID file doesn't exist. Maybe the server isn't running?</error>");
return;
}

$pid = parent::getPid();

// send sigterm signal to shutdown server
posix_kill($pid, SIGTERM);
parent::deletePid();

$output->writeln(sprintf('<info>Stopped %s WebSocket server</info>', $name));

}
}

?>
139 changes: 139 additions & 0 deletions Command/WebsocketCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

namespace Varspool\WebsocketBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Varspool\WebsocketBundle\Exception\WebSocketException;
use Varspool\WebsocketBundle\Exception\WebSocketSignalException;


/**
* Usefull methods for demonizing wrench websocket bundle
*/
abstract class WebsocketCommand extends ContainerAwareCommand
{
/**
* Server name from varspool_websocket config file
*
* @var string
*/
private $name = 'default';


/**
* Set server name
*
* @param string $title
*/
public function setConfName($name)
{
$this->name = $name;
}

/**
* Get full path for pid file
*
* @return string
*/
private function getPidFileName()
{
return sys_get_temp_dir().'/websocket-'.$this->name.'.pid';
}

/**
* Check existence of pid file
*
* @return boolean
*/
protected function checkPid()
{
return file_exists($this->getPidFileName());
}

/**
* Delete pid file
*
* @throws WebSocketException
* @return boolean
*/
protected function deletePid()
{
if (!$this->checkPid()) {
throw new WebSocketException("Pid file doesn't exist", 1);
}
if (!unlink($this->getPidFileName())) {
throw new WebSocketException("Can't delete pid file.\n".error_get_last(), 4);
}
return true;
}

/**
* Get websocket daemon process number
*
* @throws WebSocketException
* @return integer
*/
protected function getPid()
{
if (!$handle = fopen($this->getPidFileName(), "r")){
throw new WebSocketException("Can't open pid file.\n".error_get_last(), 2);
}
if (!$pid = fread($handle, filesize($this->getPidFileName()))) {
throw new WebSocketException("Can't read pid file.\n".error_get_last(), 3);
}
return $pid;
}

/**
* Create pid file with process number inside
*
* @throws WebSocketException
* @return boolean
*/
protected function createPid()
{
if (!$handle = fopen($this->getPidFileName(), "w") ){
throw new WebSocketException("Can't open pid file.\n".error_get_last(), 2);
}
if (!$pid = getmypid()) {
throw new WebSocketException("Can't get current php process id.\n".error_get_last(), 6);
}
if (!fwrite($handle, $pid)) {
throw new WebSocketException("Can't write into pid file.\n".error_get_last(), 5);
}
return true;
}

/**
* Handle process's signal (sigterm) and throw exception for future catching
*
* @throws WebSocketSignalException
*/
static function handleSignal()
{
pcntl_signal(SIGTERM, function($signo) {
throw new WebSocketSignalException('Received SIGTERM signal', 12);
});
}

/**
* Check system signals and return false in time of sigterm recieving
*
* @throws WebSocketException
* @return boolean
*/
static function listenPid()
{
sleep(1);
try {
if (!pcntl_signal_dispatch()) {
throw new WebSocketException("Can't dispatch signals.\n".error_get_last(), 7);
}
} catch (WebSocketSignalException $e) {
return false;
}
return true;
}

}
?>
10 changes: 10 additions & 0 deletions Exception/WebSocketException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Varspool\WebsocketBundle\Exception;

class WebSocketException extends \Exception
{

}

?>
8 changes: 8 additions & 0 deletions Exception/WebSocketSignalException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
namespace Varspool\WebsocketBundle\Exception;

class WebSocketSignalException extends \Exception
{

}
?>
36 changes: 36 additions & 0 deletions Server/DaemonServer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
namespace Varspool\WebsocketBundle\Server;

use Wrench\Server;

class DaemonServer extends Server
{
/**
* Fork original Server class with possibility to shutdown it
*
* {@inheritDoc}
* @see \Wrench\Server::run()
*/
public function run( callable $finish = null )
{
$this->connectionManager->listen();

if (!is_callable($finish) ) {
$finish = function() { return true; };
}

while ( $finish() ) {
$this->connectionManager->selectAndProcess();

foreach($this->applications as $application) {
if(method_exists($application, 'onUpdate')) {
$application->onUpdate();
}
}
}
}



}
?>