Skip to content

Instantly share code, notes, and snippets.

@spelcaster
Created September 29, 2017 03:19
Show Gist options
  • Save spelcaster/cf846dfdce07fa0476ebc0cd21ab6311 to your computer and use it in GitHub Desktop.
Save spelcaster/cf846dfdce07fa0476ebc0cd21ab6311 to your computer and use it in GitHub Desktop.
Simple PHP daemon using double fork
#!/usr/bin/php
<?php
// ##### definitions #####
define("DAEMON_NAME", "daemon-sample");
define("DAEMON_ROOT", "/tmp/daemon-sample");
define("DAEMON_PID", "/run/daemon-sample.pid");
define("DAEMON_LOG", "/tmp/daemon-sample/logfile");
// uid and gid from http|www-data user
define("DAEMON_UID", 33);
define("DAEMON_GID", 33);
// ##### helper functions #####
function isCli()
{
return PHP_SAPI == 'cli';
}
function logger($code, $msg, $var = null)
{
$codeMap = [
E_ERROR => "Error",
E_WARNING => "Warning",
E_NOTICE => "Notice"
];
$now = new DateTime("now");
$msg = $now->format('c') . " {$codeMap[$code]} : $msg";
if (!is_null($var)) {
$msg .= "\n";
$msg .= var_export($var, true);
$msg .= "\n";
$msg .= "\n";
}
file_put_contents(
DAEMON_LOG,
$msg . "\n",
FILE_APPEND
);
}
function handleError($errno, $errstr, $errfile, $errline, $errctx)
{
if (error_reporting() == 0) {
return;
}
$msg = "$errstr ($errfile:$errline) -> " . var_export($errctx, true);
logger($errno, $msg);
return true;
}
function stopDaemon()
{
if (is_callable('stop')) {
call_user_func('stop');
}
}
function handleSignal($signo)
{
switch ($signo) {
case SIGTERM:
logger(E_NOTICE, 'SIGTERM received, dying gracefully!');
stopDaemon();
return;
case SIGINT:
logger(E_NOTICE, 'SIGINT received, dying gracefully!');
stopDaemon();
return;
case SIGHUP:
logger(E_NOTICE, 'SIGHUP received, dying gracefully!');
stopDaemon();
return;
default:
logger(E_WARNING, "Unknown Signal ($signo) received, dying gracefully");
stopDaemon();
return;
}
}
function registerSignalHandler()
{
pcntl_signal(SIGTERM, 'handleSignal');
pcntl_signal(SIGHUP, 'handleSignal');
pcntl_signal(SIGINT, 'handleSignal');
}
function registerEnv()
{
file_put_contents(DAEMON_PID, getmypid());
posix_setuid(DAEMON_UID);
posix_setgid(DAEMON_GID);
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
}
// ##### daemon #####
ini_set("display_errors", 0);
set_error_handler("handleError");
if (!isCli()) {
logger(E_NOTICE, "This script must be run in CLI");
exit;
}
umask(0);
$pid = pcntl_fork();
if ($pid == -1) {
logger(E_WARNING, "Failed to fork");
exit(1);
} else if ($pid) {
exit(0);
}
if (posix_setsid() < 0) {
logger(E_WARNING, "Failed to make the process a session leader");
exit(1);
}
// fix ticks for signal handling
declare(ticks = 1);
registerSignalHandler();
$pid = pcntl_fork();
if ($pid == -1) {
logger(E_WARNING, "Failed to double fork");
exit(1);
} else if ($pid) {
exit(0);
}
chdir(DAEMON_ROOT);
registerEnv();
if (!cli_set_process_title(DAEMON_NAME)) {
logger(E_WARNING, "Failed to set process title for $pid");
exit(1);
}
// I'm thinking in a better approach for this
$run = true;
function stop()
{
global $run;
$run = false;
}
function loop()
{
global $run;
$sleepTime = 250000;
$i = 0;
while ($run) {
logger(E_NOTICE, "($i) Running...");
if ($i > 99) {
break;
}
$i++;
usleep($sleepTime);
}
}
loop();
@spelcaster
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment