MENU_LOCAL_TASK, 'title' => 'Apache Server Status', 'description' => 'Administer background process apache server status', 'page callback' => 'drupal_get_form', 'page arguments' => array('background_process_ass_settings_form'), 'access arguments' => array('administer background process'), 'file' => 'background_process_ass.admin.inc', 'weight' => 3, ); return $items; } /** * Implements hook_cron(). */ function background_process_ass_cron() { // Don't use more than 30 seconds to unlock @set_time_limit(30); background_process_ass_auto_unlock(); } /** * Implements hook_cronapi(). */ function background_process_ass_cronapi($op, $job = NULL) { switch ($op) { case 'list': return array('background_process_ass_cron' => t('Unlock dead processes')); case 'rule': return '* * * * *'; case 'configure': return 'admin/config/system/background-process/ass'; } } /** * Implements hook_cron_alter(). */ function background_process_ass_cron_alter(&$items) { $items['background_process_ass_cron']['override_congestion_protection'] = TRUE; // Unlock background if too old. // @todo Move to some access handler or pre-execute? if ($process = background_process_get_process('uc:background_process_ass_cron')) { if ($process->start + 30 < time()) { background_process_unlock($process->handle, t('Self unlocking stale lock')); } } } /** * Implements hook_service_group(). */ function background_process_ass_service_group() { $info = array(); $info['methods']['background_process_ass_service_group_idle'] = t('Idle workers'); return $info; } /** * Determine host with most idle workers and claim it. * * @param $service_group * Service group to check * @return * Claimed service host on success, NULL if none found */ function background_process_ass_service_group_idle($service_group, $reload = FALSE) { $result = NULL; $max = 0; $msg = ""; $workers = &drupal_static('background_process_ass_idle_workers', array()); // Load idle worker status for all hosts foreach ($service_group['hosts'] as $idx => $host) { $name = $host . '_ass'; if ($reload || !isset($workers[$name])) { $workers[$name] = background_process_ass_get_server_status($name, TRUE, $reload); } // Reload apache server status for all hosts, if any is fully loaded if ($workers[$name] <= 0 && !$reload) { return background_process_ass_service_group_idle($service_group, TRUE); } if ($max < $workers[$name]) { $result = $host; $max = $workers[$name]; } } if (isset($result)) { // Claim host and tell caller $workers[$result . '_ass']--; return $result; } else { // Could not determine most idle host, fallback to pseudo round robin return background_process_service_group_round_robin($service_group); } } /** * Unlock locked processes that aren't really running. */ function background_process_ass_auto_unlock() { $processes = background_process_get_processes(); $service_hosts = background_process_get_service_hosts(); foreach ($processes as $process) { // Don't even dare try determining state, if not properly configured. if (!$process->service_host) { continue; } // Naming convention suffix "_ass" for a given service hosts defines the // host to use for server-status. if (!isset($service_hosts[$process->service_host . '_ass'])) { continue; } if (!isset($service_hosts[$process->service_host])) { continue; } list($url, $headers) = background_process_build_request('bgp-start/' . rawurlencode($process->handle), $process->service_host); $process->http_host = $headers['Host']; // Locate our connection $url = parse_url($url); $path = $url['path'] . (isset($url['query']) ? '?' . $url['query'] : ''); if (strlen("POST $path") > 64) { // Request is larger than 64 characters, which is the max length of // requests in the extended Apache Server Status. We cannot determine // if it's running or not ... skip this process! continue; } if ($process->status != BACKGROUND_PROCESS_STATUS_RUNNING) { // Not ready for unlock yet continue; } if ($process->start > time() - variable_get('background_process_ass_max_age', BACKGROUND_PROCESS_ASS_MAX_AGE)) { // Not ready for unlock yet continue; } $server_status = background_process_ass_get_server_status($process->service_host . '_ass'); if ($server_status) { if (!background_process_ass_check_process($process, $server_status, $path)) { _background_process_ass_unlock($process); } } } } /** * Check if process is really running. * * @param $process * Process object * @param $server_status * Server status data * @return boolean * TRUE if running, FALSE if not. */ function background_process_ass_check_process($process, $server_status, $path) { $active = TRUE; // Is status reliable? if ($server_status && $server_status['status']['Current Timestamp'] > $process->start) { // Check if process is in the server status if (!empty($server_status['connections'])) { $active = FALSE; foreach ($server_status['connections'] as $conn) { if ($conn['M'] == 'R') { // We cannot rely on the server status, assume connection is still // active, and bail out. watchdog('bg_process', 'Found reading state ...', array(), WATCHDOG_WARNING); $active = TRUE; break; } // Empty connections, skip them if ($conn['M'] == '.' || $conn['M'] == '_') { continue; } if ( $conn['VHost'] == $process->http_host && strpos($conn['Request'], 'POST ' . $path) === 0 ) { $active = TRUE; break; } } } } return $active; } function _background_process_ass_unlock($process) { watchdog('bg_process', 'Unlocking: ' . $process->handle); if ($process->status == BACKGROUND_PROCESS_STATUS_RUNNING) { $msg = t('Died unexpectedly (auto unlock due to missing connection)'); // Unlock the process if (background_process_unlock($process->handle, $msg, $process->start)) { drupal_set_message(t("%handle unlocked: !msg", array('%handle' => $process->handle, '!msg' => $msg))); } } } /** * Get apache extended server status. * * @staticvar $server_status * Cached statically to avoid multiple requests to server-status. * @param $name * Name of service host for server-status. * @param $auto * Load only idle workers, not entire server status. * @param $reload * Don't load from cache. * @return array * Server status data. */ function background_process_ass_get_server_status($name, $auto = FALSE, $reload = FALSE) { // Sanity check ... if (!$name) { return; } $service_hosts = variable_get('background_process_service_hosts', array()); if (empty($service_hosts[$name])) { return; } $service_host = $service_hosts[$name]; // Static caching. $cache = &drupal_static('background_process_ass_server_status', array()); if (!$reload && isset($cache[$name][$auto])) { return $cache[$name][$auto]; } $server_status = array(); $options = array(); if ($auto) { $options['query']['auto'] = 1; } list($url, $headers) = background_process_build_request('', $name, $options); $timestamp = time(); $response = drupal_http_request($url, array('headers' => $headers)); if ($response->code != 200) { watchdog('bg_process', 'Could not acquire server status from %url - error: %error', array('%url' => $url, '%error' => $response->error), WATCHDOG_ERROR); return NULL; } // If "auto" only collect idle workers if ($auto) { preg_match('/IdleWorkers:\s+(\d+)/', $response->data, $matches); $server_status = $matches[1]; } else { $tables = _background_process_ass_parse_table($response->data); $dls = _background_process_ass_parse_definition_list($response->data); $server_status = array( 'response' => $response, 'connections' => $tables[0], 'status' => $dls[1], ); preg_match('/.*?,\s+(\d+-.*?-\d+\s+\d+:\d+:\d+)/', $server_status['status']['Restart Time'], $matches); // @hack Convert monthly names from Danish to English for strtotime() to work str_replace('Maj', 'May', $matches[1]); str_replace('May', 'Oct', $matches[1]); $server_status['status']['Restart Timestamp'] = strtotime($matches[1]); $server_status['status']['Current Timestamp'] = $timestamp; } $cache[$name][$auto] = $server_status; return $server_status; } /** * Converts an HTML table into an associative array. * * @param $html * HTML containing table. * @return array * Table data. */ function _background_process_ass_parse_table($html) { // Find the table preg_match_all("/.*?<\/[\s]*table>/s", $html, $table_htmls); $tables = array(); foreach ($table_htmls[0] as $table_html) { // Get title for each row preg_match_all("/(.*?)<\/[\s]*th>/s", $table_html, $matches); $row_headers = $matches[1]; // Iterate each row preg_match_all("/(.*?)<\/[\s]*tr>/s", $table_html, $matches); $table = array(); foreach ($matches[1] as $row_html) { $row_html = preg_replace("/\r|\n/", '', $row_html); preg_match_all("/(.*?)<\/[\s]*td>/", $row_html, $td_matches); $row = array(); for ($i=0; $i 0) { $table[] = $row; } } $tables[] = $table; } return $tables; } /** * Converts an HTML table into an associative array. * * @param $html * HTML containing table. * @return array * Table data. */ function _background_process_ass_parse_definition_list($html) { // Find the table preg_match_all("/.*?<\/[\s]*dl>/s", $html, $dl_htmls); $dls = array(); foreach ($dl_htmls[0] as $dl_html) { // Get title for each row preg_match_all("/(.*?)<\/[\s]*dl>/s", $dl_html, $matches); $dl = array(); foreach ($matches[1] as $row_html) { $row_html = preg_replace("/\r|\n/", '', $row_html); preg_match_all("/(.*?)<\/[\s]*dt>/", $row_html, $dt_matches); $row = array(); for ($i=0; $i