Skip to content

Instantly share code, notes, and snippets.

@amad
Created January 15, 2013 21:52
Show Gist options
  • Save amad/4542455 to your computer and use it in GitHub Desktop.
Save amad/4542455 to your computer and use it in GitHub Desktop.
PHP Daemon
#!/usr/bin/php -q
<?php
/**
* System_Daemon turns PHP-CLI scripts into daemons.
*
* PHP version 5
*
* @category System
* @package System_Daemon
* @author Kevin <kevin@vanzonneveld.net>
* @copyright 2008 Kevin van Zonneveld
* @license http://www.opensource.org/licenses/bsd-license.php
* @link http://github.com/kvz/system_daemon
*/
/**
* System_Daemon Example Code
*
* If you run this code successfully, a daemon will be spawned
* but unless have already generated the init.d script, you have
* no real way of killing it yet.
*
* In this case wait 3 runs, which is the maximum for this example.
*
*
* In panic situations, you can always kill you daemon by typing
*
* killall -9 logparser.php
* OR:
* killall -9 php
*
*/
// Allowed arguments & their defaults
$runmode = array(
'no-daemon' => false,
'help' => false,
'write-initd' => false,
);
// Scan command line attributes for allowed arguments
foreach ($argv as $k=>$arg) {
if (substr($arg, 0, 2) == '--' && isset($runmode[substr($arg, 2)])) {
$runmode[substr($arg, 2)] = true;
}
}
// Help mode. Shows allowed argumentents and quit directly
if ($runmode['help'] == true) {
echo 'Usage: '.$argv[0].' [runmode]' . "\n";
echo 'Available runmodes:' . "\n";
foreach ($runmode as $runmod=>$val) {
echo ' --'.$runmod . "\n";
}
die();
}
// Make it possible to test in source directory
// This is for PEAR developers only
ini_set('include_path', ini_get('include_path').':..');
// Include Class
error_reporting(E_ALL);
require_once 'System/Daemon.php';
// Setup
$options = array(
'appName' => 'logparser',
'appDir' => dirname(__FILE__),
'appDescription' => 'Parses vsftpd logfiles and stores them in MySQL',
'authorName' => 'Kevin van Zonneveld',
'authorEmail' => 'kevin@vanzonneveld.net',
'sysMaxExecutionTime' => '0',
'sysMaxInputTime' => '0',
'sysMemoryLimit' => '1024M',
'appRunAsGID' => 1000,
'appRunAsUID' => 1000,
);
System_Daemon::setOptions($options);
// This program can also be run in the forground with runmode --no-daemon
if (!$runmode['no-daemon']) {
// Spawn Daemon
System_Daemon::start();
}
// With the runmode --write-initd, this program can automatically write a
// system startup file called: 'init.d'
// This will make sure your daemon will be started on reboot
if (!$runmode['write-initd']) {
System_Daemon::info('not writing an init.d script this time');
} else {
if (($initd_location = System_Daemon::writeAutoRun()) === false) {
System_Daemon::notice('unable to write init.d script');
} else {
System_Daemon::info(
'sucessfully written startup script: %s',
$initd_location
);
}
}
// Run your code
// Here comes your own actual code
// This variable gives your own code the ability to breakdown the daemon:
$runningOkay = true;
// This variable keeps track of how many 'runs' or 'loops' your daemon has
// done so far. For example purposes, we're quitting on 3.
$cnt = 1;
// While checks on 3 things in this case:
// - That the Daemon Class hasn't reported it's dying
// - That your own code has been running Okay
// - That we're not executing more than 3 runs
while (!System_Daemon::isDying() && $runningOkay && $cnt <=3) {
// What mode are we in?
$mode = '"'.(System_Daemon::isInBackground() ? '' : 'non-' ).
'daemon" mode';
// Log something using the Daemon class's logging facility
// Depending on runmode it will either end up:
// - In the /var/log/logparser.log
// - On screen (in case we're not a daemon yet)
System_Daemon::info('{appName} running in %s %s/3',
$mode,
$cnt
);
// In the actuall logparser program, You could replace 'true'
// With e.g. a parseLog('vsftpd') function, and have it return
// either true on success, or false on failure.
$runningOkay = true;
//$runningOkay = parseLog('vsftpd');
// Should your parseLog('vsftpd') return false, then
// the daemon is automatically shut down.
// An extra log entry would be nice, we're using level 3,
// which is critical.
// Level 4 would be fatal and shuts down the daemon immediately,
// which in this case is handled by the while condition.
if (!$runningOkay) {
System_Daemon::err('parseLog() produced an error, '.
'so this will be my last run');
}
// Relax the system by sleeping for a little bit
// iterate also clears statcache
System_Daemon::iterate(2);
$cnt++;
}
// Shut down the daemon nicely
// This is ignored if the class is actually running in the foreground
System_Daemon::stop();
http://kvz.io/blog/2009/01/09/create-daemons-in-php/
pear install -f System_Daemon
--no-daemon # just run the program in the console this time
--write-initd # writes a startup file
tail /var/log/logparser.log
ps uf -C logparser.php
killall -9 logparser.php
/etc/init.d/logparser stop
/etc/init.d/logparser start
$path = System_Daemon::writeAutoRun();
update-rc.d logparser defaults
update-rc.d -f logparser remove
/etc/logrotate.d/
>>
/var/log/mydaemon.log {
rotate 15
compress
missingok
notifempty
sharedscripts
size 5M
create 644 mydaemon_user mydaemon_group
postrotate
/bin/kill -HUP `cat /var/run/mydaemon/mydaemond.pid 2>/dev/null` 2> /dev/null || true
endscript
}
Don’t use echo() Echo writes to the STDOUT of your current session. If you logout, that will cause fatal errors and the daemon to die. Use System_Daemon::log() instead.
Connect to MySQL after you start() the daemon. Otherwise only the parent process will have a MySQL connection, and since that dies.. It’s lost and you will get a ‘MySQL has gone away’ error.
Error handling Good error handling is imperative. Daemons are often mission critical applications and you don’t want an uncatched error to bring it to it’s knees.
Reconnect to MySQL A connection may be interrupted. Think about network downtime or lock-ups when your database server makes backups. Whatever the cause: You don’t want your daemon to die for this, let it try again later.
PHP error handler As of 0.6.3, System_Daemon forwards all PHP errors to the log() method, so keep an eye on your logfile. This behavior can be controlled using the logPhpErrors (true||false) option.
Monit Monit is a standalone program that can kickstart any daemon, based on your parameters. Should your daemon fail, monit will mail you and try to restart it.
Watch that memory Some classes keep a history of executed commands, sent mails, queries, whatever. They were designed without knowing they would ever be used in a daemonized environment. Cause daemons run indefinitely this ‘history’ will expand indefinitely. Since unfortunately your server’s RAM is not infinite, you will run into problems at some point. This makes it’s very important to address these memory ‘leaks’ when building daemons.
Statcache will corrupt your data If you do a file_exists(), PHP remembers the results to ease on your disk until the process end. That’s ok but since the Daemon process does not end, PHP will not be able to give you up to date information. As of 0.8.0 you should call System_Daemon::iterate(2) instead of e.g. sleep(2), this will sleep & clear the cache and give you fresh & valid data.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment