? sites/default/files ? sites/default/settings.php Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.820 diff -u -p -r1.820 common.inc --- includes/common.inc 8 Nov 2008 20:05:00 -0000 1.820 +++ includes/common.inc 8 Nov 2008 20:45:50 -0000 @@ -2741,44 +2741,195 @@ function drupal_cron_run() { // Allow execution to continue even if the request gets canceled. @ignore_user_abort(TRUE); + $cron_run_time = variable_get('cron_run_time', 240); + $advanced_cron = variable_get('cron_run_advanced', FALSE); + $cron_info = array(); + $cron_run_max_threads = variable_get('cron_run_max_threads', 4); + // Increase the maximum execution time. - @set_time_limit(240); + if (!ini_get('safe_mode')) { + @set_time_limit($cron_run_time); + } - // Fetch the cron semaphore + // Fetch the cron semaphore. $semaphore = variable_get('cron_semaphore', FALSE); + $cron_wait_semaphore = variable_get('cron_wait_semaphore', FALSE); - if ($semaphore) { - if (REQUEST_TIME - $semaphore > 3600) { - // Either cron has been running for more than an hour or the semaphore - // was not reset due to a database error. + // If we enter the cron run w/o a rid but the semephore is set, that means we + // are in a 'cron session'. Thus we need to check to see if we need to time + // out this cron session. + if (($semaphore || $cron_wait_semaphore) && !$_REQUEST['rid']) { + if($advanced_cron) { + // See if we have been trying to start the crons for an hour. + if($cron_wait_semaphore) { + if(REQUEST_TIME - (int)$cron_wait_semaphore > 60) { + watchdog('cron', t('Cron has been waiting for a previous cron to shutdown for more than an hour and is most likely stuck.'), WATCHDOG_ERROR); + variable_del('cron_wait_semaphore'); + } + else { + watchdog('cron', t('Attempting to re-run cron while another cron is waiting for cron shutdown.'), WATCHDOG_WARNING); + return; + } + } + if($semaphore) { + if(REQUEST_TIME - (int)$semaphore > 3600) { + watchdog('cron', t('Cron has been trying to start multiple jobs running for more than an hour and is most likely stuck.'), WATCHDOG_ERROR); + variable_del('cron_semaphore'); + } + else { + // Cron is still running normally. + watchdog('cron', t('Attempting to re-run cron while it is already trying to start multiple jobs.'), WATCHDOG_WARNING); + return; + } + } + } + else if(REQUEST_TIME - $semaphore > 3600) { + // Either cron has been running for more than an hour or the semaphore was + // not reset due to a database error. watchdog('cron', 'Cron has been running for more than an hour and is most likely stuck.', array(), WATCHDOG_ERROR); - // Release cron semaphore + // Release cron semaphore. variable_del('cron_semaphore'); } else { // Cron is still running normally. watchdog('cron', 'Attempting to re-run cron while it is already running.', array(), WATCHDOG_WARNING); + return; } } + elseif ($_REQUEST['rid']) { + $rid = $_REQUEST['rid']; + $riid = $_REQUEST['riid']; + $run_module = $_REQUEST['module']; + + db_query("UPDATE {cron_run_info} SET run_status = %d, run_date = %d WHERE riid = %d", -1, time(), $riid); + watchdog('cron', t('Start running cron thread for %module', array('%module' => $run_module))); + + module_invoke($run_module, 'cron'); + watchdog('cron', t('Finished running cron thread for %module', array('%module' => $run_module))); + + db_query("UPDATE {cron_run_info} SET run_status = %d, run_stop = %d WHERE riid = %d", WATCHDOG_NOTICE, time(), $riid); + db_query("UPDATE {cron_runs} SET run_ticks = run_ticks + 1 WHERE rid = %d", $rid); + } else { - // Register shutdown callback - register_shutdown_function('drupal_cron_cleanup'); + // First truncate any old crons. + $max_crons = variable_get('cron_run_history', 50); + $truncate = db_result(db_query("SELECT COUNT(*) FROM {cron_runs}")); + + if($truncate > $max_crons) { + $rs = db_query_range("SELECT rid FROM {cron_runs} ORDER BY rid DESC", $max_crons, $truncate); + while($row = db_fetch_object($rs)) { + db_query("DELETE FROM {cron_run_info} WHERE rid = %d", $row->rid); + db_query("DELETE FROM {cron_runs} WHERE rid = %d", $row->rid); + } + } - // Lock cron semaphore - variable_set('cron_semaphore', REQUEST_TIME); + if($advanced_cron) { + // First, we need to wait until the other sessions have finished. We will + // also time out as needed any cron sessions that a running to long. This + // is so we don't over load the system with some errand cron run and we + // keep calling a thread for it, thus getting a race condition. + + // So we know we are waiting on a run to finish and we don't stack up + //runs. + $cron_wait_semaphore = time(); + variable_set('cron_wait_semaphore', $cron_wait_semaphore); + $max_threads = variable_get('cron_max_threads', 4); + $module_count = db_result(db_query("SELECT COUNT(*) FROM {cron_run_info} WHERE run_stop = ''")); + while($module_count > $max_threads) { + watchdog('cron', t('Cron is attempting to wait for a previous cron run to finish. Currently %module_count modules running', array('%module_count' => $module_count)), NULL, 'admin/logs/cron/' . $rid); + + // Ping every 10 seconds. + sleep(10); + variable_set('cron_wait_semaphore', $cron_wait_semaphore); + + // Check for stuck modules and release them. + $stuck_rs = db_query("SELECT * FROM {cron_run_info} WHERE (%d - run_date) > 1000 AND run_stop = ''", time()); + while($row = db_fetch_object($stuck_rs)) { + watchdog('cron', t('%module was not stopped in over an hour had has been marked as completed with errors.', array('%module' => $row->module_name)), WATCHDOG_ERROR, 'admin/logs/cron/' . $row->rid); + db_query("UPDATE {cron_run_info} SET run_stop = %d, run_status = %d WHERE riid = %d", time(), WATCHDOG_ERROR, $row->riid); + } + // Ping that this session is still running and update the module count. + $module_count = db_result(db_query("SELECT COUNT(*) FROM {cron_run_info} WHERE run_stop = ''")); + } + } + // Register shutdown callback. + register_shutdown_function('drupal_cron_cleanup'); - // Iterate through the modules calling their cron handlers (if any): - module_invoke_all('cron'); + // Lock cron semaphore. + $semaphore = time(); + variable_set('cron_semaphore', $semaphore); + if($advanced_cron) { + // Release wait lock. Important to to this after locking the session in + // general with the semaphore lock. + variable_del('cron_wait_semaphore'); + $winos = eregi("windows", strtolower(php_uname())); + $cmd = variable_get('cron_run_command', ''); + if($cmd == '') { + watchdog('cron', t('Cron can not run because you have setup advanced cron without a command'), WATCHDOG_ERROR); + return; + } + global $base_url; + $base_path = base_path(); + $rid = db_next_id("{cron_runs}_rid"); + db_query("INSERT INTO {cron_runs}(rid, run_date, run_status) VALUES(%d, %d, %d)", $rid, time(), WATCHDOG_WARNING); + // Start firing the modules. + $modules = module_list(); + $module_count = 0; + foreach($modules as $module) { + while($module_count > $cron_run_max_threads) { + sleep(5); + // Check for stuck modules. + $stuck_rs = db_query("SELECT * FROM {cron_run_info} WHERE (%d - run_date) > 3600", time()); + while($row = db_fetch_object($stuck_rs)) { + watchdog('cron', t('%module was started over an hour had has been marked as completed with errors.', array('%module' => $row->module_name)), WATCHDOG_ERROR); + db_query("UPDATE {cron_run_info} SET run_stop = %d, run_status = %d WHERE riid = %d", time(), WATCHDOG_ERROR, $row->riid); + } + // Ping that this session is still running and update the module count. + $module_count = db_result(db_query("SELECT COUNT(*) FROM {cron_run_info} WHERE run_stop = '' AND rid = %d", $rid)); + } + // Put some time between firing requests. + sleep(1); + if(module_hook($module, 'cron')) { + $riid = db_next_id("{cron_run_info}_riid"); + db_query("INSERT INTO {cron_run_info}(riid, rid, module_name, run_status) VALUES(%d, %d, '%s', %d)", $riid, $rid, $module, WATCHDOG_WARNING); + $run_cmd = "$cmd \"$base_url/cron.php?rid=$rid&riid=$riid&module=$module\""; + if(!$winos) { + pclose(popen("$run_cmd & ", "r")); + } + else { + $WshShell = new COM("WScript.Shell"); + $oExec = $WshShell->Run("cmd /C $run_cmd", 0, false); + } + $module_count++; + } + } + db_query("UPDATE {cron_runs} SET run_status = %d, run_stop = %d, run_ticks = %d", WATCHDOG_INFO, time(), (-1 * $module_count)); + } + else { + // Iterate through the modules calling their cron handlers (if any). + db_query("INSERT INTO {cron_runs}(run_date, run_status) VALUES(%d, %d)", time(), WATCHDOG_WARNING); + $rid = db_result(db_query("SELECT MAX(rid) FROM {cron_runs}")); + db_query("INSERT INTO {cron_run_info}(rid, module_name, run_status, run_date) VALUES(%d, '%s', %d, %d)", $rid, 'system-all-modules', WATCHDOG_WARNING, time()); + $riid = db_result(db_query("SELECT MAX(riid) FROM {cron_run_info}")); + module_invoke_all('cron'); + db_query("UPDATE {cron_run_info} SET run_status = %d, run_stop = %d WHERE riid = %d", WATCHDOG_NOTICE, time(), $riid); + db_query("UPDATE {cron_runs} SET run_status = %d, run_ticks = %d, run_stop = %d WHERE rid = %d",WATCHDOG_INFO, 0, time(), $rid); + } - // Record cron time - variable_set('cron_last', REQUEST_TIME); - watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE); + // Record cron time. + variable_set('cron_last', time()); + if($advanced_cron) { + watchdog('cron', t('Cron run started %module_count runs.', array('%module_count' => $module_count)), WATCHDOG_NOTICE); + } + else { + watchdog('cron', t('Cron run completed.'), WATCHDOG_NOTICE); + } - // Release cron semaphore + // Release cron semaphore. variable_del('cron_semaphore'); - // Return TRUE so other functions can check if it did run successfully + // Return TRUE so other functions can check if it did run successfully. return TRUE; } } Index: modules/system/system.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v retrieving revision 1.102 diff -u -p -r1.102 system.admin.inc --- modules/system/system.admin.inc 16 Oct 2008 20:23:08 -0000 1.102 +++ modules/system/system.admin.inc 8 Nov 2008 20:45:50 -0000 @@ -2244,3 +2244,178 @@ function theme_system_themes_form($form) $output .= drupal_render($form); return $output; } + +/** + * Menu callback: provides the cron basic/advanced settings interface. + * + * A cron run can be setup to run in a "multi-threaded" enviroment. + * Each module will have it's own "report" of how it ran, etc. + * + * @return + * The form array. + */ +function system_cron_settings($form_values = NULL) { + global $base_url; + $form['basic_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Basic cron settings'), + '#collapsible' => FALSE, + ); + $form['basic_settings']['cron_run_time'] = array( + '#type' => 'textfield', + '#size' => 5, + '#title' => t('Cron run time'), + '#description' => t('This is the time in seconds that a cron run is alloted to complete a run. Try increasing this value if you notice that you have a lot of errors of your cron run not finishing.'), + '#default_value' => variable_get('cron_run_time', 240), + ); + $history_options = drupal_map_assoc(range(50, 1000, 50)); + $form['basic_settings']['cron_run_history'] = array( + '#type' => 'select', + '#title' => t('Cron history'), + '#options' => $history_options, + '#default_value' => variable_get('cron_run_history', 50), + '#description' => t('The number of cron runs you would like to keep. It will delete starting with the oldest cron run.'), + ); + if($base_url == '') { + drupal_set_message('warning', t('In order to use advanced cron settings, you must set the $base_url variable in your settings.php file.')); + } else { + $form['advanced_cron_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced cron settings'), + '#collapsible' => FALSE, + ); + $form['advanced_cron_settings']['cron_max_threads'] = array( + '#type' => 'textfield', + '#title' => t('Max threads'), + '#size' => 4, + '#description' => t('The maximum number of threads that you would like the run at one time.'), + '#default_value' => variable_get('cron_max_threads', 4), + ); + $form['advanced_cron_settings']['cron_run_advanced'] = array( + '#type' => 'checkbox', + '#title' => t('Enable advanced cron runs'), + '#description' => t('Check this box to have your cron runs run in a multi-threaded environment. This will also give you more information about each individual modules cron run.'), + '#default_value' => variable_get('cron_run_advanced', FALSE), + ); + $form['advanced_cron_settings']['cron_run_command'] = array( + '#type' => 'textfield', + '#title' => t('Cron run command'), + '#description' => t('This is the command you use to run your cron. Use ONLY the command. Example: /usr/bin/lynx -source'), + '#default_value' => variable_get('cron_run_command', ""), + ); + } + return system_settings_form($form); +} + +/** + * Menu callback: cron logs. + */ +function system_cron_logs($form_values = NULL) { + if(is_numeric(arg(3))) { + $rid = arg(3); + $cron_run = db_fetch_object(db_query("SELECT cr.rid, cr.run_date, cr.run_status, cr.run_ticks, cr.run_stop, COUNT(riid) modules_ran + FROM {cron_runs} cr INNER JOIN {cron_run_info} ci ON cr.rid = ci.rid + WHERE cr.rid = %d + GROUP BY cr.rid, cr.run_date, cr.run_status, cr.run_ticks, cr.run_stop + ", $rid)); + if(!$cron_run) { + drupal_set_message(t('Invalid cron run given'), 'error'); + drupal_goto('admin/logs/cron'); + } + + // Icons setup. + $icons = array( + CRON_RUNNING => theme('image', 'misc/progress.gif', t('running'), t('running')), + WATCHDOG_NOTICE => theme('image', 'misc/watchdog-ok.png', t('ok'), t('ok')), + WATCHDOG_WARNING => theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning')), + WATCHDOG_ERROR => theme('image', 'misc/watchdog-error.png', t('error'), t('error')), + ); + + // General run information. + $form['cron_run_info'] = array('#value' => + t('Run ID') . ': ' . $rid . '
' . + t('Run date') . ': ' . format_date($cron_run->run_date, 'large') . '
' . + t('Modules ran') . ': ' . $cron_run->modules_ran . '
' . + t('Run status') . ': ' . $icons[$cron_run->run_status] . '

'); + + // Get the modules that ran. + $module_runs = db_query("SELECT * FROM {cron_run_info} WHERE rid = %d", $rid); + $rows = $module_run = $row = $headers = array(); + $time_format = 'h:i:s a'; + while($module_run = db_fetch_object($module_runs)) { + $row = array(); + $row[] = $module_run->module_name; + $row[] = format_date($module_run->run_date, 'custom', $time_format); + $row[] = $module_run->run_stop != '' ? format_date($module_run->run_stop, 'custom', $time_format) : t('(running)'); + if($module_run->run_stop) { + $row[] = ($module_run->run_stop - $module_run->run_date) . ' seconds'; + } + else { + $row[] = $module_run->run_date ? (time() - $module_run->run_date) . ' seconds' : 'run not started)'; + } + $row[] = $icons[$module_run->run_status]; + $rows[] = $row; + } + $headers = array(t('Module'), t('Start'), t('Finish'), t('Run time'), t('Status')); + $form['run_table'] = array('#value' => + theme('table', $headers, $rows)); + + } + else { + $rs = pager_query ("SELECT rid, run_date, run_stop, run_ticks, run_status FROM {cron_runs} ORDER BY run_date DESC", 20); + while($row = db_fetch_object($rs)) { + $cron_runs[$row->rid] = $row; + } + + $form['cron_runs'] = array('#value' => $cron_runs); + $form['pager_info'] = array('#value' => theme('pager', 20, NULL, 0)); + $form['#theme'] = 'system_cron_runs_logs'; + } + + return $form; + +} + +/** + * Theme function for the cron runs logs form. + * + * @param $form + * An associative array containing the structure of the form. + * @ingroup themeable + */ +function theme_system_cron_runs_logs($form) { + + $icons = array( + CRON_RUNNING => theme('image', 'misc/progress.gif', t('running'), t('running'), 'align=center'), + WATCHDOG_NOTICE => theme('image', 'misc/watchdog-ok.png', t('ok'), t('ok')), + WATCHDOG_WARNING => theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning')), + WATCHDOG_ERROR => theme('image', 'misc/watchdog-error.png', t('error'), t('error')), + ); + $headers = array( + array('field' => 'run_date', 'data' => t('Cron run date')), + array('field' => 'rid', 'data' => t('Run ID')), + array('field' => 'run_information', 'data' => t('Run status')) + ); + $cron_runs = $form['cron_runs']['#value']; + unset($form['cron_runs']); + $row = $rows = array(); + if(!is_array($cron_runs)) { + $cron_runs = array(); + } + foreach($cron_runs as $rid => $info) { + $link = 'admin/logs/cron/' . $rid; + if(db_result(db_query('SELECT COUNT(*) FROM {cron_run_info} WHERE run_stop = \'\' AND rid = ' . $rid))) { + $info->run_status = CRON_RUNNING; + } + $row['data'] = array(l(format_date($info->run_date,'long'), $link), l($rid, $link), $icons[$info->run_status]); + $row['class'] = 'ok'; + $row['align'] = 'center'; + $rows[] = $row; + $row = array(); + } + + $output .= drupal_render($form); + $output .= theme('table', $headers, $rows, array('class' => 'system-status-report')); + return $output; + +} \ No newline at end of file Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.278 diff -u -p -r1.278 system.install --- modules/system/system.install 5 Nov 2008 14:28:04 -0000 1.278 +++ modules/system/system.install 8 Nov 2008 20:45:50 -0000 @@ -610,6 +610,79 @@ function system_schema() { $schema['cache_registry'] = $schema['cache']; $schema['cache_registry']['description'] = t('Cache table for the code registry system to remember what code files need to be loaded on any given page.'); + $schema['cron_run_info'] = array( + 'description' => t(''), + 'fields' => array( + 'riid' => array( + 'description' => t(''), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'rid' => array( + 'description' => t(''), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'module_name' => array( + 'description' => t('The cron module.'), + 'type' => 'varchar', + 'length' => '50', + 'not null' => FALSE, + ), + 'run_date' => array( + 'description' => t('A Unix timestamp indicating when the last module\'s cron ran.'), + 'type' => 'int', + 'not null' => FALSE, + ), + 'run_stop' => array( + 'description' => t('A Unix timestamp indicating when the last module\'s cron stopped.'), + 'type' => 'int', + 'not null' => FALSE, + ), + 'run_status' => array( + 'description' => t('The last moule\'s cron status.'), + 'type' => 'int', + 'not null' => FALSE, + ), + ), + 'primary key' => array('riid'), + ); + + $schema['cron_runs'] = array( + 'description' => t(''), + 'fields' => array( + 'rid' => array( + 'description' => t(''), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'run_date' => array( + 'description' => t('A Unix timestamp indicating when the last cron ran.'), + 'type' => 'int', + 'not null' => FALSE, + ), + 'run_stop' => array( + 'description' => t('A Unix timestamp indicating when the last cron stopped.'), + 'type' => 'int', + 'not null' => FALSE, + ), + 'run_ticks' => array( + 'description' => t(''), + 'type' => 'int', + 'not null' => FALSE, + ), + 'run_status' => array( + 'description' => t('The last cron status.'), + 'type' => 'int', + 'not null' => FALSE, + ), + ), + 'primary key' => array('rid'), + ); + $schema['files'] = array( 'description' => t('Stores information for uploaded files.'), 'fields' => array( Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.635 diff -u -p -r1.635 system.module --- modules/system/system.module 31 Oct 2008 02:18:22 -0000 1.635 +++ modules/system/system.module 8 Nov 2008 20:45:50 -0000 @@ -156,6 +156,10 @@ function system_theme() { 'meta_generator_header' => array( 'arguments' => array('version' => NULL), ), + 'system_cron_runs_logs' => array( + 'arguments' => array('form' => NULL), + 'file' => 'system.admin.inc', + ), 'system_compact_link' => array(), )); } @@ -519,6 +523,14 @@ function system_menu() { 'type' => MENU_CALLBACK, ); + $items['admin/settings/cron'] = array( + 'title' => t('Cron'), + 'description' => t('Cron Settings.'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_cron_settings'), + 'access arguments' => array('administer site configuration'), + ); + // IP address blocking. $items['admin/settings/ip-blocking'] = array( 'title' => 'IP address blocking', @@ -668,6 +680,15 @@ function system_menu() { 'access arguments' => array('administer site configuration'), 'type' => MENU_CALLBACK, ); + + $items['admin/reports/cron'] = array( + 'title' => t('Cron runs'), + 'description' => t('Information about past cron runs.'), + 'callback' => 'drupal_get_form', + 'callback arguments' => array('system_cron_logs'), + 'access arguments' => array('administer site configuration'), + ); + // Default page for batch operations $items['batch'] = array( 'page callback' => 'system_batch_page', @@ -2150,4 +2171,4 @@ function theme_meta_generator_header($ve */ function system_image_toolkits() { return array('gd'); -} +} \ No newline at end of file