Jump to content

Simple Service Alternative To Cron


Serendesk

Recommended Posts

Here's a very simple script to run the Blesta cron tasks as a constantly running background service and to do away with cron altogether. This is useful if you need to run the tasks more frequently than 5 minutes, or, get locking and overlap with cron. I have been running this now for about 24 hours and no problems so far.

This script can be improved on greatly, but it does have a simple feature to ensure that you don't have more than one service running at the same time. With a bit more work you could have a very useful script that behaves like a system service with cli args, better error checking and signal handling for graceful shutdowns etc. (I have added basic signal handling but I haven't tested it. It requires the posix extension which I do not have).

You can either start/stop the service from the command line or within a script, or use a process monitor like supervisord or daemontools to handle it (they have better logging and protect you from starting more than one).

Two files are used; service.php and config.json.

Start the service: redirect output to a log file and place the process in the background (append & to the end of the command). Choose your own php path and log file path.

#run as current user
/usr/local/bin/php service.php > BlestaService.log &
Check the service is running: you should see the service name (set in config) and the pid of the process.
ps -aux | grep BlestaService
Stop the service:

If usePIDFile in the config is set to true, the service will stop if the pid file is deleted. This provides a very simple way of gracefully stopping the service (the while loop will stop if the file cannot be found). Otherwise, you can use kill to stop it immediately, but ideally try to avoid this. DB transactions may be in use, but there could be issues when exiting during imap connections etc. Alternatively and more cleanly, send it signals if you have the posix extension.

#kill
kill 47854

#send quit signal, better
kill -s QUIT 47854

service.php

<?php

require('/usr/local/www/blesta/blesta/lib/init.php');

set_time_limit(0);

class BlestaService
{
	private $config;
	private $pidFile;
	private $running = true;
	
	public function run()
	{
		//these are required steps so dont catch errors thrown from this function
		$this->prep();
		
		while($this->keepRunning())
		{
			try
			{
				$start = time();
				
				//do the cron work
				Dispatcher::dispatch('/cron/', true);
				
				//clean up. might not be absolutely required
				gc_collect_cycles();
				
				sleep(max(0, $this->config->runInterval - (time() - $start)));
			}
			catch(Exception $e)
			{
				$this->logWrite($e);
				sleep(60);
			}
		}
		$this->cleanup();
	}
	
	private function prep()
	{
		gc_enable();
		
		$this->config = json_decode(file_get_contents('config.json'));
		
		if(function_exists('cli_set_process_title') && function_exists('cli_get_process_title'))
		{
			$currentTitle = cli_get_process_title();
			cli_set_process_title($this->config->serviceName);
		}
		
		if($this->config->usePIDFile)
		{
			$this->pidFile = $this->config->pidFileDir. DIRECTORY_SEPARATOR .$this->config->serviceName.'.pid';
			$this->exitIfPIDFileExists();
			$this->setPIDFile();
		}
		
		if(function_exists('pcntl_signal'))
		{
			//signal handling
			declare(ticks = 1);
			pcntl_signal(SIGQUIT, 'sigHandler');
			pcntl_signal(SIGTERM, 'sigHandler');
			pcntl_signal(SIGKILL, 'sigHandler');
			pcntl_signal(SIGABRT, 'sigHandler');
			pcntl_signal(SIGHUP,  'sigHandler');
			pcntl_signal(SIGINT,  'sigHandler');
		}
	}
	
	private function exitIfPIDFileExists()
	{
		if(file_exists($this->pidFile) === true)
		{
			$pid = file_get_contents($this->pidFile);
			$this->logWrite("Unable to start. Is there a service already running with pid $pid? Otherwise remove the pid file located at $this->pidFile before starting");
			exit(1);
		}
	}
	
	private function setPIDFile()
	{
		file_put_contents($this->pidFile, getmypid());
		chown($this->pidFile, $this->config->pidFileOwner);
		chmod($this->pidFile, 0600);
	}
	
	private function removePIDFile()
	{
		if($this->config->usePIDFile && file_exists($this->pidFile) === true)
		{
			unlink($this->pidFile);
		}
	}
	
	private function keepRunning()
	{
		if($this->config->usePIDFile)
		{
			//removing pid file will shutdown the service
			$this->running = file_exists($this->pidFile);
		}
		//alternatively use pcntl_signal to stop the service cleanly by setting $this->running = false in the SIGTERM, SIGKILL, SIGINT handler
		return $this->running;
	}
	
	private function logWrite($string)
	{
		//echo to console as output will be redirected to a log file
		echo "$string\n";
	}
	
	public function sigHandler($signo)
	{
		switch($signo)
		{
			case SIGQUIT:
			case SIGTERM:
			case SIGHUP:
			case SIGABRT:
					$this->keepRunning = false;
				break;
			case SIGKILL: //probably cant catch this one but try anyway
					$this->cleanup();
				break;
				
			case SIGINT:
				break;
				
			default:
		}
	}
	
	private function cleanup()
	{
		$this->logWrite('Exiting gracefully');
		$this->removePIDFile();
		exit;
	}
	
	/*public function __destruct()
	{
		//removed this as it will be called when exiting via exitIfPIDFileExists() when a service exists already, causing that services pid file to be deleted (and eventually stop running)
		$this->cleanup();
	}*/
}

//run
$service = new BlestaService();
$service->run();
?>
config.json
{
	"serviceName":"BlestaService",
	"runInterval":60,
	"usePIDFile":true,
	"pidFileDir":"/tmp",
	"pidFileOwner":"www"
}
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...