From 77d7238eef84f498fc024fa8b9e06f187dd0f2a6 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 14 Apr 2010 16:14:32 -0400 Subject: [PATCH] [feature/system-cron] WIP on making cron tasks runnable via system cron PHPBB3-9596 --- phpBB/common.php | 6 + phpBB/cron.php | 293 +++++-------------------------- phpBB/includes/cron.php | 135 ++++++++++++++ phpBB/includes/cron/standard.php | 163 +++++++++++++++++ phpBB/includes/cron_lock.php | 71 ++++++++ phpBB/includes/functions.php | 36 +--- phpBB/viewforum.php | 4 +- 7 files changed, 421 insertions(+), 287 deletions(-) create mode 100644 phpBB/includes/cron.php create mode 100644 phpBB/includes/cron/standard.php create mode 100644 phpBB/includes/cron_lock.php diff --git a/phpBB/common.php b/phpBB/common.php index 0ac7cbbd86..3586031c36 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -239,3 +239,9 @@ foreach ($cache->obtain_hooks() as $hook) { @include($phpbb_root_path . 'includes/hooks/' . $hook . '.' . $phpEx); } + +if (!$config['use_system_cron']) +{ + require($phpbb_root_path . 'includes/cron.' . $phpEx); + $cron = new cron(); +} diff --git a/phpBB/cron.php b/phpBB/cron.php index 4462f52e93..1dbe1768c1 100644 --- a/phpBB/cron.php +++ b/phpBB/cron.php @@ -15,271 +15,58 @@ define('IN_CRON', true); $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; $phpEx = substr(strrchr(__FILE__, '.'), 1); include($phpbb_root_path . 'common.' . $phpEx); +include($phpbb_root_path . 'includes/cron_lock.' . $phpEx); // Do not update users last page entry $user->session_begin(false); $auth->acl($user->data); -$cron_type = request_var('cron_type', ''); -$use_shutdown_function = (@function_exists('register_shutdown_function')) ? true : false; +function output_image() { + // Output transparent gif + header('Cache-Control: no-cache'); + header('Content-type: image/gif'); + header('Content-length: 43'); -// Output transparent gif -header('Cache-Control: no-cache'); -header('Content-type: image/gif'); -header('Content-length: 43'); + echo base64_decode('R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='); -echo base64_decode('R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='); - -// test without flush ;) -// flush(); - -// -if (!isset($config['cron_lock'])) -{ - set_config('cron_lock', '0', true); + // test without flush ;) + // flush(); } -// make sure cron doesn't run multiple times in parallel -if ($config['cron_lock']) -{ - // if the other process is running more than an hour already we have to assume it - // aborted without cleaning the lock - $time = explode(' ', $config['cron_lock']); - $time = $time[0]; - - if ($time + 3600 >= time()) - { - exit; +function do_cron($run_tasks) { + global $cron_lock; + + foreach ($run_tasks as $cron_type) { + $cron->run_task($cron_type); } -} - -define('CRON_ID', time() . ' ' . unique_id()); - -$sql = 'UPDATE ' . CONFIG_TABLE . " - SET config_value = '" . $db->sql_escape(CRON_ID) . "' - WHERE config_name = 'cron_lock' AND config_value = '" . $db->sql_escape($config['cron_lock']) . "'"; -$db->sql_query($sql); - -// another cron process altered the table between script start and UPDATE query so exit -if ($db->sql_affectedrows() != 1) -{ - exit; -} - -/** -* Run cron-like action -* Real cron-based layer will be introduced in 3.2 -*/ -switch ($cron_type) -{ - case 'queue': - - if (time() - $config['queue_interval'] <= $config['last_queue_run'] || !file_exists($phpbb_root_path . 'cache/queue.' . $phpEx)) - { - break; - } - - // A user reported using the mail() function while using shutdown does not work. We do not want to risk that. - if ($use_shutdown_function && !$config['smtp_delivery']) - { - $use_shutdown_function = false; - } - - include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); - $queue = new queue(); - - if ($use_shutdown_function) - { - register_shutdown_function(array(&$queue, 'process')); - } - else - { - $queue->process(); - } - - break; - - case 'tidy_cache': - - if (time() - $config['cache_gc'] <= $config['cache_last_gc'] || !method_exists($cache, 'tidy')) - { - break; - } - - if ($use_shutdown_function) - { - register_shutdown_function(array(&$cache, 'tidy')); - } - else - { - $cache->tidy(); - } - - break; - - case 'tidy_search': - - // Select the search method - $search_type = basename($config['search_type']); - - if (time() - $config['search_gc'] <= $config['search_last_gc'] || !file_exists($phpbb_root_path . 'includes/search/' . $search_type . '.' . $phpEx)) - { - break; - } - - include_once("{$phpbb_root_path}includes/search/$search_type.$phpEx"); - - // We do some additional checks in the module to ensure it can actually be utilised - $error = false; - $search = new $search_type($error); - - if ($error) - { - break; - } - - if ($use_shutdown_function) - { - register_shutdown_function(array(&$search, 'tidy')); - } - else - { - $search->tidy(); - } - - break; - - case 'tidy_warnings': - - if (time() - $config['warnings_gc'] <= $config['warnings_last_gc']) - { - break; - } - - include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx); - - if ($use_shutdown_function) - { - register_shutdown_function('tidy_warnings'); - } - else - { - tidy_warnings(); - } - - break; - - case 'tidy_database': - - if (time() - $config['database_gc'] <= $config['database_last_gc']) - { - break; - } - - include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx); - - if ($use_shutdown_function) - { - register_shutdown_function('tidy_database'); - } - else - { - tidy_database(); - } - - break; - - case 'tidy_sessions': - - if (time() - $config['session_gc'] <= $config['session_last_gc']) - { - break; - } - - if ($use_shutdown_function) - { - register_shutdown_function(array(&$user, 'session_gc')); - } - else - { - $user->session_gc(); - } - - break; - - case 'prune_forum': - - $forum_id = request_var('f', 0); - - $sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, forum_flags, prune_freq - FROM ' . FORUMS_TABLE . " - WHERE forum_id = $forum_id"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if (!$row) - { - break; - } - - // Do the forum Prune thang - if ($row['prune_next'] < time() && $row['enable_prune']) - { - include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx); - - if ($row['prune_days']) - { - if ($use_shutdown_function) - { - register_shutdown_function('auto_prune', $row['forum_id'], 'posted', $row['forum_flags'], $row['prune_days'], $row['prune_freq']); - } - else - { - auto_prune($row['forum_id'], 'posted', $row['forum_flags'], $row['prune_days'], $row['prune_freq']); - } - } - - if ($row['prune_viewed']) - { - if ($use_shutdown_function) - { - register_shutdown_function('auto_prune', $row['forum_id'], 'viewed', $row['forum_flags'], $row['prune_viewed'], $row['prune_freq']); - } - else - { - auto_prune($row['forum_id'], 'viewed', $row['forum_flags'], $row['prune_viewed'], $row['prune_freq']); - } - } - } - - break; -} - -// Unloading cache and closing db after having done the dirty work. -if ($use_shutdown_function) -{ - register_shutdown_function('unlock_cron'); - register_shutdown_function('garbage_collection'); -} -else -{ - unlock_cron(); + + // Unloading cache and closing db after having done the dirty work. + $cron_lock->unlock(); garbage_collection(); } -exit; +if ($cron_lock->lock()) { + if ($config['use_system_cron']) { + $use_shutdown_function = false; + + $run_tasks = $cron->find_all_runnable_tasks(); + } else { + $cron_type = request_var('cron_type', ''); + $use_shutdown_function = (@function_exists('register_shutdown_function')) ? true : false; + + output_image(); - -/** -* Unlock cron script -*/ -function unlock_cron() -{ - global $db; - - $sql = 'UPDATE ' . CONFIG_TABLE . " - SET config_value = '0' - WHERE config_name = 'cron_lock' AND config_value = '" . $db->sql_escape(CRON_ID) . "'"; - $db->sql_query($sql); + if ($cron->is_valid_task($cron_type) && $cron->is_task_runnable($cron_type)) + { + if ($use_shutdown_function && !$cron->is_task_shutdown_function_compatible($cron_type)) { + $use_shutdown_function = false; + } + $run_tasks = array($cron_type); + } + } + if ($use_shutdown_function) { + register_shutdown_function('do_cron', $run_tasks); + } else { + do_cron($run_tasks); + } } diff --git a/phpBB/includes/cron.php b/phpBB/includes/cron.php new file mode 100644 index 0000000000..2aa22858b7 --- /dev/null +++ b/phpBB/includes/cron.php @@ -0,0 +1,135 @@ +tasks as $cron_type => $params) { + $params['object'] = $object; + $this->tasks[$cron_type] = $params; + } + } + } + } + + function is_valid_task($cron_type) { + return isset($this->tasks[$cron_type]); + } + + function is_task_runnable($cron_type, $args=null) { + global $config; + $time_now = time(); + $cron_params = $this->tasks[$cron_type]; + if ($cron_params['enable_config'] && !$config[$cron_params['enable_config']]) { + return false; + } + if ($cron_param['custom_condition']) { + $callable = array($cron_params['object'], $cron_type . '_condition'); + if ($args) { + $answer = call_user_func_array($callable, $args); + } else { + $answer = call_user_func($callable); + } + if (!$answer) { + return false; + } + } + if ($time_now - $config[$cron_params['interval_config']] > $config[$cron_params['last_run_config']]) { + return true; + } + return false; + } + + function is_task_shutdown_function_compatible($cron_type) { + $cron_params = $this->tasks[$cron_type]; + if (isset($cron_params['shutdown_function_condition'])) { + return call_user_func(array($cron_params->object, $cron_type . '_shutdown_function_condition')); + } else { + return true; + } + } + + function determine_cron_mode_param() { + global $config; + if ($config['use_system_cron']) { + $mode = 'run_from_system'; + } else { + $mode_param = 'run_from_phpbb'; + } + return $mode_param; + } + + function find_one_runnable_task() { + $mode_param = $this->determine_cron_mode_param(); + foreach ($this->tasks as $cron_type => $cron_params) { + if ($cron_params[$mode_param] && $this->is_task_runnable($cron_type)) { + return $cron_type; + } + } + return null; + } + + function find_all_runnable_tasks() { + $mode_param = $this->determine_cron_mode_param(); + $tasks = array(); + foreach ($this->tasks as $cron_type => $cron_params) { + if ($cron_params[$mode_param] && $this->is_task_runnable($cron_type)) { + $tasks[] = $cron_type; + } + } + return $tasks; + } + + function generate_task_code($cron_type, $args=array()) { + $cron_params = $this->tasks[$cron_type]; + if ($cron_params['custom_code']) { + $code = call_user_func_array(array($cron_params['object'], $cron_type . '_code'), $args); + } else { + $code = $this->generate_generic_task_code($cron_type); + } + return $code; + } + + function generate_generic_task_code($cron_type) { + global $phpbb_root_path, $phpEx; + return 'cron'; + } + + function run_task($cron_type) { + call_user_func(array($this->tasks[$cron_type]['object'], 'run_' . $cron_type)); + } +} diff --git a/phpBB/includes/cron/standard.php b/phpBB/includes/cron/standard.php new file mode 100644 index 0000000000..1cb8738f17 --- /dev/null +++ b/phpBB/includes/cron/standard.php @@ -0,0 +1,163 @@ + array( + 'custom_condition' => true, + 'run_from_system' => true, + ), + 'prune_forum' => array( + 'custom_condition' => true, + 'custom_code' => true, + ), + 'queue' => array( + 'custom_condition' => true, + 'interval_config' => 'queue_interval_config', + 'last_run_config' => 'last_queue_run', + 'run_from_phpbb' => true, + 'run_from_system' => true, + 'shutdown_function_condition' => true, + ), + 'tidy_cache' => array( + 'custom_condition' => true, + 'interval_config' => 'cache_gc', + 'last_run_config' => 'cache_last_gc', + 'run_from_phpbb' => true, + 'run_from_system' => true, + ), + 'tidy_database' => array( + 'interval_config' => 'database_gc', + 'last_run_config' => 'database_last_gc', + 'run_from_phpbb' => true, + 'run_from_system' => true, + ), + 'tidy_search' => array( + 'interval_config' => 'search_gc', + 'last_run_config' => 'search_last_gc', + 'run_from_phpbb' => true, + 'run_from_system' => true, + ), + 'tidy_sessions' => array( + 'interval_config' => 'session_gc', + 'last_run_config' => 'session_last_gc', + 'run_from_phpbb' => true, + 'run_from_system' => true, + ), + 'tidy_warnings' => array( + 'enable_config' => 'warnings_expire_days', + 'interval_config' => 'warnings_gc', + 'last_run_config' => 'warnings_last_gc', + 'run_from_phpbb' => true, + 'run_from_system' => true, + ), + ); + + function prune_forum_condition($forum_data) { + return $forum_data['enable_prune'] && $forum_data['prune_next'] < time(); + } + + function prune_forum_code($forum_id) { + global $phpbb_root_path, $phpEx; + return 'cron'; + } + + function run_prune_forum() { + } + + function queue_condition() { + global $phpbb_root_path, $phpEx; + return file_exists($phpbb_root_path . 'cache/queue.' . $phpEx); + } + + function queue_shutdown_function_condition() { + global $config; + return !$config['smtp_delivery']; + } + + function run_queue() { + global $phpbb_root_path, $phpEx; + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + $queue = new queue(); + $queue->process(); + } + + function tidy_cache_condition() { + global $cache; + return method_exists($cache, 'tidy'); + } + + function run_tidy_cache() { + global $cache; + $cache->tidy(); + } + + function run_tidy_database() { + include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + tidy_database(); + } + + function tidy_search_condition() { + global $phpbb_root_path, $phpEx, $config; + + // Select the search method + $search_type = basename($config['search_type']); + + return file_exists($phpbb_root_path . 'includes/search/' . $search_type . '.' . $phpEx); + } + + function run_tidy_search() { + global $phpbb_root_path, $phpEx, $config, $error; + + // Select the search method + $search_type = basename($config['search_type']); + + include_once("{$phpbb_root_path}includes/search/$search_type.$phpEx"); + + // We do some additional checks in the module to ensure it can actually be utilised + $error = false; + $search = new $search_type($error); + + if (!$error) { + $search->tidy(); + } + } + + function run_tidy_sessions() { + global $user; + $user->session_gc(); + } + + function run_tidy_warnings() { + include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + tidy_warnings(); + } +} diff --git a/phpBB/includes/cron_lock.php b/phpBB/includes/cron_lock.php new file mode 100644 index 0000000000..2a09590772 --- /dev/null +++ b/phpBB/includes/cron_lock.php @@ -0,0 +1,71 @@ += time()) + { + return false; + } + } + + define('CRON_ID', time() . ' ' . unique_id()); + + $sql = 'UPDATE ' . CONFIG_TABLE . " + SET config_value = '" . $db->sql_escape(CRON_ID) . "' + WHERE config_name = 'cron_lock' AND config_value = '" . $db->sql_escape($config['cron_lock']) . "'"; + $db->sql_query($sql); + + // another cron process altered the table between script start and UPDATE query so exit + if ($db->sql_affectedrows() != 1) + { + return false; + } + + return true; + } + + function unlock() { + global $db; + + $sql = 'UPDATE ' . CONFIG_TABLE . " + SET config_value = '0' + WHERE config_name = 'cron_lock' AND config_value = '" . $db->sql_escape(CRON_ID) . "'"; + $db->sql_query($sql); + } +} diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 056d578e75..eb787bfc62 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -4595,7 +4595,7 @@ function page_footer($run_cron = true) // Call cron-type script $call_cron = false; - if (!defined('IN_CRON') && $run_cron && !$config['board_disable']) + if (!defined('IN_CRON') && !$config['use_system_cron'] && $run_cron && !$config['board_disable']) { $call_cron = true; $time_now = (!empty($user->time_now) && is_int($user->time_now)) ? $user->time_now : time(); @@ -4616,40 +4616,12 @@ function page_footer($run_cron = true) // Call cron job? if ($call_cron) { - $cron_type = ''; - - if ($time_now - $config['queue_interval'] > $config['last_queue_run'] && !defined('IN_ADMIN') && file_exists($phpbb_root_path . 'cache/queue.' . $phpEx)) - { - // Process email queue - $cron_type = 'queue'; - } - else if (method_exists($cache, 'tidy') && $time_now - $config['cache_gc'] > $config['cache_last_gc']) - { - // Tidy the cache - $cron_type = 'tidy_cache'; - } - else if ($config['warnings_expire_days'] && ($time_now - $config['warnings_gc'] > $config['warnings_last_gc'])) - { - $cron_type = 'tidy_warnings'; - } - else if ($time_now - $config['database_gc'] > $config['database_last_gc']) - { - // Tidy the database - $cron_type = 'tidy_database'; - } - else if ($time_now - $config['search_gc'] > $config['search_last_gc']) - { - // Tidy the search - $cron_type = 'tidy_search'; - } - else if ($time_now - $config['session_gc'] > $config['session_last_gc']) - { - $cron_type = 'tidy_sessions'; - } + global $cron; + $cron_type = $cron->find_one_runnable_task(); if ($cron_type) { - $template->assign_var('RUN_CRON_TASK', 'cron'); + $template->assign_var('RUN_CRON_TASK', $cron->generate_task_code($cron_type)); } } diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 47d71849cb..8a06e28394 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -193,9 +193,9 @@ if ($forum_data['forum_topics_per_page']) } // Do the forum Prune thang - cron type job ... -if ($forum_data['prune_next'] < time() && $forum_data['enable_prune']) +if (!$config['use_system_cron'] && $cron->is_task_runnable('prune_forum', array($forum_data))) { - $template->assign_var('RUN_CRON_TASK', 'cron'); + $template->assign_var('RUN_CRON_TASK', $cron->generate_task_code('prune_forum', array($forum_id))); } // Forum rules and subscription info