2010-07-19 03:49:54 +00:00
< ? php
2015-02-19 09:45:46 +00:00
if ( ! file_exists ( " boot.php " ) AND ( sizeof ( $_SERVER [ " argv " ]) != 0 )) {
$directory = dirname ( $_SERVER [ " argv " ][ 0 ]);
2015-02-19 09:26:49 +00:00
2015-02-19 09:45:46 +00:00
if ( substr ( $directory , 0 , 1 ) != " / " )
$directory = $_SERVER [ " PWD " ] . " / " . $directory ;
2015-02-19 09:26:49 +00:00
2015-02-19 09:45:46 +00:00
$directory = realpath ( $directory . " /.. " );
2015-02-19 09:26:49 +00:00
2015-02-19 09:45:46 +00:00
chdir ( $directory );
}
2011-04-16 06:40:43 +00:00
2016-07-13 17:43:16 +00:00
use \Friendica\Core\Config ;
use \Friendica\Core\PConfig ;
2011-01-28 13:04:18 +00:00
require_once ( " boot.php " );
2012-11-05 08:28:54 +00:00
function poller_run ( & $argv , & $argc ){
2011-03-05 04:55:32 +00:00
global $a , $db ;
2011-01-28 13:04:18 +00:00
2011-03-05 04:55:32 +00:00
if ( is_null ( $a )) {
$a = new App ;
}
2013-01-20 13:08:28 +00:00
2011-03-05 04:55:32 +00:00
if ( is_null ( $db )) {
2015-09-10 21:32:56 +00:00
@ include ( " .htconfig.php " );
require_once ( " include/dba.php " );
$db = new dba ( $db_host , $db_user , $db_pass , $db_data );
unset ( $db_host , $db_user , $db_pass , $db_data );
};
2013-01-20 13:08:28 +00:00
2016-10-02 13:52:52 +00:00
// Quit when in maintenance
if ( get_config ( 'system' , 'maintenance' , true ))
return ;
2016-09-09 20:33:54 +00:00
$a -> start_process ();
$mypid = getmypid ();
2016-07-02 07:31:28 +00:00
if ( $a -> max_processes_reached ())
return ;
2016-06-01 19:54:02 +00:00
2016-02-09 05:42:00 +00:00
if ( poller_max_connections_reached ())
return ;
2016-03-08 19:28:09 +00:00
if ( App :: maxload_reached ())
return ;
2015-09-23 06:56:48 +00:00
2015-12-06 15:40:31 +00:00
// Checking the number of workers
2016-07-24 18:25:11 +00:00
if ( poller_too_much_workers ()) {
2016-01-24 01:53:21 +00:00
poller_kill_stale_workers ();
2015-12-06 15:40:31 +00:00
return ;
2016-01-24 01:53:21 +00:00
}
2015-12-06 15:40:31 +00:00
2015-09-12 15:51:27 +00:00
if (( $argc <= 1 ) OR ( $argv [ 1 ] != " no_cron " )) {
// Run the cron job that calls all other jobs
2016-08-02 04:28:34 +00:00
proc_run ( PRIORITY_MEDIUM , " include/cron.php " );
2015-09-12 15:51:27 +00:00
2015-09-12 18:22:58 +00:00
// Run the cronhooks job separately from cron for being able to use a different timing
2016-08-02 04:28:34 +00:00
proc_run ( PRIORITY_MEDIUM , " include/cronhooks.php " );
2015-09-12 18:22:58 +00:00
2015-09-12 15:51:27 +00:00
// Cleaning dead processes
2016-01-24 01:53:21 +00:00
poller_kill_stale_workers ();
2015-12-06 15:40:31 +00:00
} else
2015-12-06 15:28:28 +00:00
// Sleep four seconds before checking for running processes again to avoid having too many workers
2015-09-12 16:08:03 +00:00
sleep ( 4 );
2011-03-16 00:31:49 +00:00
2015-09-10 21:32:56 +00:00
// Checking number of workers
2016-07-24 18:25:11 +00:00
if ( poller_too_much_workers ())
2011-01-28 13:04:18 +00:00
return ;
2011-07-01 05:00:08 +00:00
2016-07-13 17:43:16 +00:00
$cooldown = Config :: get ( " system " , " worker_cooldown " , 0 );
2015-09-25 15:38:56 +00:00
$starttime = time ();
2016-10-10 21:01:36 +00:00
while ( $r = poller_worker_process ()) {
2015-09-23 06:56:48 +00:00
2016-10-02 13:52:52 +00:00
// Quit when in maintenance
if ( get_config ( 'system' , 'maintenance' , true ))
return ;
2016-07-02 07:31:28 +00:00
// Constantly check the number of parallel database processes
if ( $a -> max_processes_reached ())
return ;
2016-06-01 19:54:02 +00:00
2016-02-11 20:39:34 +00:00
// Constantly check the number of available database connections to let the frontend be accessible at any time
if ( poller_max_connections_reached ())
return ;
2015-12-15 23:14:53 +00:00
// Count active workers and compare them with a maximum value that depends on the load
2016-07-24 18:25:11 +00:00
if ( poller_too_much_workers ())
2015-12-15 23:14:53 +00:00
return ;
2015-09-28 19:58:58 +00:00
q ( " UPDATE `workerqueue` SET `executed` = '%s', `pid` = %d WHERE `id` = %d AND `executed` = '0000-00-00 00:00:00' " ,
2015-09-10 21:32:56 +00:00
dbesc ( datetime_convert ()),
2016-09-09 20:33:54 +00:00
intval ( $mypid ),
2015-09-10 21:32:56 +00:00
intval ( $r [ 0 ][ " id " ]));
2011-04-16 06:40:43 +00:00
2015-09-28 19:58:58 +00:00
// Assure that there are no tasks executed twice
2016-09-09 20:33:54 +00:00
$id = q ( " SELECT `pid`, `executed` FROM `workerqueue` WHERE `id` = %d " , intval ( $r [ 0 ][ " id " ]));
2015-09-28 20:32:56 +00:00
if ( ! $id ) {
2016-09-09 20:33:54 +00:00
logger ( " Queue item " . $r [ 0 ][ " id " ] . " vanished - skip this execution " , LOGGER_DEBUG );
continue ;
} elseif (( strtotime ( $id [ 0 ][ " executed " ]) <= 0 ) OR ( $id [ 0 ][ " pid " ] == 0 )) {
logger ( " Entry for queue item " . $r [ 0 ][ " id " ] . " wasn't stored - we better stop here " , LOGGER_DEBUG );
return ;
} elseif ( $id [ 0 ][ " pid " ] != $mypid ) {
logger ( " Queue item " . $r [ 0 ][ " id " ] . " is to be executed by process " . $id [ 0 ][ " pid " ] . " and not by me ( " . $mypid . " ) - skip this execution " , LOGGER_DEBUG );
2015-09-28 19:58:58 +00:00
continue ;
2015-09-28 20:32:56 +00:00
}
2015-09-28 19:58:58 +00:00
2016-07-24 18:25:11 +00:00
$argv = json_decode ( $r [ 0 ][ " parameter " ]);
$argc = count ( $argv );
2015-09-13 16:47:10 +00:00
// Check for existance and validity of the include file
$include = $argv [ 0 ];
if ( ! validate_include ( $include )) {
logger ( " Include file " . $argv [ 0 ] . " is not valid! " );
q ( " DELETE FROM `workerqueue` WHERE `id` = %d " , intval ( $r [ 0 ][ " id " ]));
continue ;
}
require_once ( $include );
2010-12-21 22:51:26 +00:00
2016-07-24 18:25:11 +00:00
$funcname = str_replace ( " .php " , " " , basename ( $argv [ 0 ])) . " _run " ;
2015-09-10 21:32:56 +00:00
if ( function_exists ( $funcname )) {
2016-09-09 20:33:54 +00:00
logger ( " Process " . $mypid . " - Prio " . $r [ 0 ][ " priority " ] . " - ID " . $r [ 0 ][ " id " ] . " : " . $funcname . " " . $r [ 0 ][ " parameter " ]);
2016-10-07 06:05:43 +00:00
// For better logging create a new process id for every worker call
// But preserve the old one for the worker
$old_process_id = $a -> process_id ;
$a -> process_id = uniqid ( " wrk " , true );
2015-09-10 21:32:56 +00:00
$funcname ( $argv , $argc );
2015-09-11 19:35:58 +00:00
2016-10-07 06:05:43 +00:00
$a -> process_id = $old_process_id ;
2016-07-13 17:43:16 +00:00
if ( $cooldown > 0 ) {
2016-09-09 20:33:54 +00:00
logger ( " Process " . $mypid . " - Prio " . $r [ 0 ][ " priority " ] . " - ID " . $r [ 0 ][ " id " ] . " : " . $funcname . " - in cooldown for " . $cooldown . " seconds " );
2016-07-13 17:43:16 +00:00
sleep ( $cooldown );
}
2016-09-09 20:33:54 +00:00
logger ( " Process " . $mypid . " - Prio " . $r [ 0 ][ " priority " ] . " - ID " . $r [ 0 ][ " id " ] . " : " . $funcname . " - done " );
2014-12-21 01:03:06 +00:00
2015-09-10 21:32:56 +00:00
q ( " DELETE FROM `workerqueue` WHERE `id` = %d " , intval ( $r [ 0 ][ " id " ]));
2015-09-13 16:47:10 +00:00
} else
logger ( " Function " . $funcname . " does not exist " );
2015-09-29 04:15:26 +00:00
// Quit the poller once every hour
if ( time () > ( $starttime + 3600 ))
return ;
2011-03-05 04:55:32 +00:00
}
2011-04-16 15:45:08 +00:00
2011-01-28 13:04:18 +00:00
}
2010-07-19 03:49:54 +00:00
2016-02-09 05:42:00 +00:00
/**
* @ brief Checks if the number of database connections has reached a critical limit .
*
* @ return bool Are more than 3 / 4 of the maximum connections used ?
*/
function poller_max_connections_reached () {
2016-02-09 22:28:33 +00:00
// Fetch the max value from the config. This is needed when the system cannot detect the correct value by itself.
$max = get_config ( " system " , " max_connections " );
2016-04-23 08:11:09 +00:00
// Fetch the percentage level where the poller will get active
$maxlevel = get_config ( " system " , " max_connections_level " );
if ( $maxlevel == 0 )
$maxlevel = 75 ;
2016-02-09 22:28:33 +00:00
if ( $max == 0 ) {
2016-02-11 20:39:34 +00:00
// the maximum number of possible user connections can be a system variable
$r = q ( " SHOW VARIABLES WHERE `variable_name` = 'max_user_connections' " );
if ( $r )
$max = $r [ 0 ][ " Value " ];
// Or it can be granted. This overrides the system variable
$r = q ( " SHOW GRANTS " );
if ( $r )
foreach ( $r AS $grants ) {
$grant = array_pop ( $grants );
if ( stristr ( $grant , " GRANT USAGE ON " ))
if ( preg_match ( " /WITH MAX_USER_CONNECTIONS ( \ d*)/ " , $grant , $match ))
$max = $match [ 1 ];
}
}
// If $max is set we will use the processlist to determine the current number of connections
// The processlist only shows entries of the current user
if ( $max != 0 ) {
$r = q ( " SHOW PROCESSLIST " );
if ( ! $r )
return false ;
$used = count ( $r );
logger ( " Connection usage (user values): " . $used . " / " . $max , LOGGER_DEBUG );
2016-04-23 08:11:09 +00:00
$level = ( $used / $max ) * 100 ;
2016-02-09 05:42:00 +00:00
2016-04-23 08:11:09 +00:00
if ( $level >= $maxlevel ) {
logger ( " Maximum level ( " . $maxlevel . " %) of user connections reached: " . $used . " / " . $max );
2016-02-12 10:04:25 +00:00
return true ;
}
}
2016-02-09 05:42:00 +00:00
2016-02-12 10:04:25 +00:00
// We will now check for the system values.
// This limit could be reached although the user limits are fine.
$r = q ( " SHOW VARIABLES WHERE `variable_name` = 'max_connections' " );
if ( ! $r )
return false ;
2016-02-11 10:33:45 +00:00
2016-02-12 10:04:25 +00:00
$max = intval ( $r [ 0 ][ " Value " ]);
if ( $max == 0 )
return false ;
$r = q ( " SHOW STATUS WHERE `variable_name` = 'Threads_connected' " );
if ( ! $r )
return false ;
$used = intval ( $r [ 0 ][ " Value " ]);
if ( $used == 0 )
return false ;
logger ( " Connection usage (system values): " . $used . " / " . $max , LOGGER_DEBUG );
2016-02-09 05:42:00 +00:00
2016-04-23 08:11:09 +00:00
$level = $used / $max * 100 ;
2016-02-09 05:42:00 +00:00
2016-04-23 08:11:09 +00:00
if ( $level < $maxlevel )
2016-02-09 05:42:00 +00:00
return false ;
2016-04-23 08:11:09 +00:00
logger ( " Maximum level ( " . $level . " %) of system connections reached: " . $used . " / " . $max );
2016-02-09 05:42:00 +00:00
return true ;
}
2016-01-24 01:53:21 +00:00
/**
* @ brief fix the queue entry if the worker process died
*
*/
function poller_kill_stale_workers () {
2016-08-08 19:28:44 +00:00
$r = q ( " SELECT `pid`, `executed`, `priority`, `parameter` FROM `workerqueue` WHERE `executed` != '0000-00-00 00:00:00' " );
2016-03-04 21:38:18 +00:00
2016-07-02 12:00:42 +00:00
if ( ! dbm :: is_result ( $r )) {
2016-03-04 21:38:18 +00:00
// No processing here needed
return ;
}
2016-01-24 01:53:21 +00:00
foreach ( $r AS $pid )
if ( ! posix_kill ( $pid [ " pid " ], 0 ))
q ( " UPDATE `workerqueue` SET `executed` = '0000-00-00 00:00:00', `pid` = 0 WHERE `pid` = %d " ,
intval ( $pid [ " pid " ]));
else {
// Kill long running processes
2016-08-08 17:20:40 +00:00
// Check if the priority is in a valid range
2016-08-18 10:33:17 +00:00
if ( ! in_array ( $pid [ " priority " ], array ( PRIORITY_CRITICAL , PRIORITY_HIGH , PRIORITY_MEDIUM , PRIORITY_LOW , PRIORITY_NEGLIGIBLE )))
2016-08-08 17:20:40 +00:00
$pid [ " priority " ] = PRIORITY_MEDIUM ;
// Define the maximum durations
2016-08-18 10:33:17 +00:00
$max_duration_defaults = array ( PRIORITY_CRITICAL => 360 , PRIORITY_HIGH => 10 , PRIORITY_MEDIUM => 60 , PRIORITY_LOW => 180 , PRIORITY_NEGLIGIBLE => 360 );
2016-08-08 17:20:40 +00:00
$max_duration = $max_duration_defaults [ $pid [ " priority " ]];
2016-08-14 19:02:29 +00:00
$argv = json_decode ( $pid [ " parameter " ]);
$argv [ 0 ] = basename ( $argv [ 0 ]);
2016-08-08 17:20:40 +00:00
// How long is the process already running?
2016-01-24 01:53:21 +00:00
$duration = ( time () - strtotime ( $pid [ " executed " ])) / 60 ;
2016-08-08 17:20:40 +00:00
if ( $duration > $max_duration ) {
2016-08-14 19:02:29 +00:00
logger ( " Worker process " . $pid [ " pid " ] . " ( " . implode ( " " , $argv ) . " ) took more than " . $max_duration . " minutes. It will be killed now. " );
2016-01-24 01:53:21 +00:00
posix_kill ( $pid [ " pid " ], SIGTERM );
2016-08-04 13:33:15 +00:00
// We killed the stale process.
// To avoid a blocking situation we reschedule the process at the beginning of the queue.
2016-08-04 13:41:32 +00:00
// Additionally we are lowering the priority.
q ( " UPDATE `workerqueue` SET `executed` = '0000-00-00 00:00:00', `created` = '%s',
`priority` = % d , `pid` = 0 WHERE `pid` = % d " ,
2016-08-04 13:33:15 +00:00
dbesc ( datetime_convert ()),
2016-08-18 10:33:17 +00:00
intval ( PRIORITY_NEGLIGIBLE ),
2016-01-24 01:53:21 +00:00
intval ( $pid [ " pid " ]));
} else
2016-08-14 19:02:29 +00:00
logger ( " Worker process " . $pid [ " pid " ] . " ( " . implode ( " " , $argv ) . " ) now runs for " . round ( $duration ) . " of " . $max_duration . " allowed minutes. That's okay. " , LOGGER_DEBUG );
2016-01-24 01:53:21 +00:00
}
}
2016-07-24 18:25:11 +00:00
function poller_too_much_workers () {
2016-07-23 20:57:22 +00:00
2015-09-27 11:56:20 +00:00
2015-09-28 05:54:28 +00:00
$queues = get_config ( " system " , " worker_queues " );
if ( $queues == 0 )
$queues = 4 ;
2016-07-23 20:57:22 +00:00
$maxqueues = $queues ;
2015-09-28 05:54:28 +00:00
$active = poller_active_workers ();
// Decrease the number of workers at higher load
2015-12-15 22:26:58 +00:00
$load = current_load ();
if ( $load ) {
2015-09-28 05:54:28 +00:00
$maxsysload = intval ( get_config ( 'system' , 'maxloadavg' ));
if ( $maxsysload < 1 )
$maxsysload = 50 ;
2015-09-28 17:14:07 +00:00
$maxworkers = $queues ;
// Some magical mathemathics to reduce the workers
$exponent = 3 ;
$slope = $maxworkers / pow ( $maxsysload , $exponent );
$queues = ceil ( $slope * pow ( max ( 0 , $maxsysload - $load ), $exponent ));
2015-09-28 05:54:28 +00:00
2016-08-03 13:59:25 +00:00
$s = q ( " SELECT COUNT(*) AS `total` FROM `workerqueue` WHERE `executed` = '0000-00-00 00:00:00' " );
$entries = $s [ 0 ][ " total " ];
if ( Config :: get ( " system " , " worker_fastlane " , false ) AND ( $queues > 0 ) AND ( $entries > 0 ) AND ( $active >= $queues )) {
$s = q ( " SELECT `priority` FROM `workerqueue` WHERE `executed` = '0000-00-00 00:00:00' ORDER BY `priority` LIMIT 1 " );
$top_priority = $s [ 0 ][ " priority " ];
2016-08-03 08:03:05 +00:00
2016-08-18 10:33:17 +00:00
$s = q ( " SELECT `id` FROM `workerqueue` WHERE `priority` <= %d AND `executed` != '0000-00-00 00:00:00' LIMIT 1 " ,
2016-08-03 13:59:25 +00:00
intval ( $top_priority ));
2016-08-18 10:33:17 +00:00
$high_running = dbm :: is_result ( $s );
2016-08-03 08:03:05 +00:00
2016-08-18 10:33:17 +00:00
if ( ! $high_running AND ( $top_priority > PRIORITY_UNDEFINED ) AND ( $top_priority < PRIORITY_NEGLIGIBLE )) {
2016-08-03 13:59:25 +00:00
logger ( " There are jobs with priority " . $top_priority . " waiting but none is executed. Open a fastlane. " , LOGGER_DEBUG );
2016-08-03 08:19:46 +00:00
$queues = $active + 1 ;
2016-08-03 08:03:05 +00:00
}
}
2016-10-11 06:30:53 +00:00
// Create a list of queue entries grouped by their priority
$running = array ( PRIORITY_CRITICAL => 0 ,
PRIORITY_HIGH => 0 ,
PRIORITY_MEDIUM => 0 ,
PRIORITY_LOW => 0 ,
PRIORITY_NEGLIGIBLE => 0 );
$r = q ( " SELECT COUNT(*) AS `running`, `priority` FROM `process` INNER JOIN `workerqueue` ON `workerqueue`.`pid` = `process`.`pid` GROUP BY `priority` " );
if ( dbm :: is_result ( $r ))
foreach ( $r AS $process )
$running [ $process [ " priority " ]] = $process [ " running " ];
$processlist = " " ;
$r = q ( " SELECT COUNT(*) AS `entries`, `priority` FROM `workerqueue` GROUP BY `priority` " );
if ( dbm :: is_result ( $r ))
foreach ( $r as $entry ) {
if ( $processlist != " " )
$processlist .= " , " ;
$processlist .= $entry [ " priority " ] . " : " . $running [ $entry [ " priority " ]] . " / " . $entry [ " entries " ];
}
logger ( " Load: " . $load . " / " . $maxsysload . " - processes: " . $active . " / " . $entries . " ( " . $processlist . " ) - maximum: " . $queues . " / " . $maxqueues , LOGGER_DEBUG );
2015-09-27 11:56:20 +00:00
2016-08-01 05:48:43 +00:00
// Are there fewer workers running as possible? Then fork a new one.
if ( ! get_config ( " system " , " worker_dont_fork " ) AND ( $queues > ( $active + 1 )) AND ( $entries > 1 )) {
logger ( " Active workers: " . $active . " / " . $queues . " Fork a new worker. " , LOGGER_DEBUG );
$args = array ( " php " , " include/poller.php " , " no_cron " );
$a = get_app ();
$a -> proc_run ( $args );
}
2015-09-27 11:56:20 +00:00
}
2015-09-28 05:54:28 +00:00
return ( $active >= $queues );
2015-09-27 11:56:20 +00:00
}
function poller_active_workers () {
2016-09-09 20:33:54 +00:00
$workers = q ( " SELECT COUNT(*) AS `processes` FROM `process` WHERE `command` = 'poller.php' " );
2015-09-27 11:56:20 +00:00
2016-09-09 20:33:54 +00:00
return ( $workers [ 0 ][ " processes " ]);
2015-09-27 11:56:20 +00:00
}
2016-10-10 21:01:36 +00:00
/**
* @ brief Check if we should pass some slow processes
*
* When the active processes of the highest priority are using more than 2 / 3
* of all processes , we let pass slower processes .
*
* @ param string $highest_priority Returns the currently highest priority
* @ return bool We let pass a slower process than $highest_priority
*/
function poller_passing_slow ( & $highest_priority ) {
$highest_priority = 0 ;
$r = q ( " SELECT `priority`
FROM `process`
INNER JOIN `workerqueue` ON `workerqueue` . `pid` = `process` . `pid`
WHERE `process` . `command` = 'poller.php' " );
// No active processes at all? Fine
if ( ! dbm :: is_result ( $r ))
return ( false );
$priorities = array ();
foreach ( $r AS $line )
$priorities [] = $line [ " priority " ];
// Should not happen
if ( count ( $priorities ) == 0 )
return ( false );
$highest_priority = min ( $priorities );
// The highest process is already the slowest one?
// Then we quit
if ( $highest_priority == PRIORITY_NEGLIGIBLE )
return ( false );
$high = 0 ;
foreach ( $priorities AS $priority )
if ( $priority == $highest_priority )
++ $high ;
logger ( " Highest priority: " . $highest_priority . " Total processes: " . count ( $priorities ) . " Count high priority processes: " . $high , LOGGER_DEBUG );
$passing_slow = (( $high / count ( $priorities )) > ( 2 / 3 ));
if ( $passing_slow )
logger ( " Passing slower processes than priority " . $highest_priority , LOGGER_DEBUG );
return ( $passing_slow );
}
/**
* @ brief Returns the next worker process
*
* @ return string SQL statement
*/
function poller_worker_process () {
// Check if we should pass some low priority process
$highest_priority = 0 ;
if ( poller_passing_slow ( $highest_priority )) {
// Are there waiting processes with a higher priority than the currently highest?
$r = q ( " SELECT * FROM `workerqueue`
WHERE `executed` = '0000-00-00 00:00:00' AND `priority` < % d
ORDER BY `priority` , `created` LIMIT 1 " , dbesc( $highest_priority ));
if ( dbm :: is_result ( $r ))
return $r ;
// Give slower processes some processing time
$r = q ( " SELECT * FROM `workerqueue`
WHERE `executed` = '0000-00-00 00:00:00' AND `priority` > % d
ORDER BY `priority` , `created` LIMIT 1 " , dbesc( $highest_priority ));
}
// If there is no result (or we shouldn't pass lower processes) we check without priority limit
if (( $highest_priority == 0 ) OR ! dbm :: is_result ( $r ))
$r = q ( " SELECT * FROM `workerqueue` WHERE `executed` = '0000-00-00 00:00:00' ORDER BY `priority`, `created` LIMIT 1 " );
return $r ;
}
2011-01-28 13:04:18 +00:00
if ( array_search ( __file__ , get_included_files ()) === 0 ){
2016-09-09 20:55:49 +00:00
poller_run ( $_SERVER [ " argv " ], $_SERVER [ " argc " ]);
get_app () -> end_process ();
killme ();
2011-01-28 13:04:18 +00:00
}
2015-09-10 21:32:56 +00:00
?>