? 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 <em>%module_count</em> 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 <em>ONLY</em> the command. Example: <strong><em>/usr/bin/lynx -source</em></strong>'),
+      '#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') . ': <strong>' . $rid . '</strong><br/>' .
+      t('Run date') . ': <strong>' . format_date($cron_run->run_date, 'large') . '</strong><br/>' .
+      t('Modules ran') . ': <strong>' . $cron_run->modules_ran . '</strong><br/>' .
+      t('Run status') . ': ' . $icons[$cron_run->run_status] . '<br/><hr/>');
+    
+    // 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
