123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- <?php
- /**
- * @file
- *
- * @todo Implement admin interface.
- * @todo Fix runtime check of running process.
- */
- /**
- * Default max age before unlock process.
- */
- define('BACKGROUND_PROCESS_ASS_MAX_AGE', 30);
- /**
- * Implements hook_menu().
- */
- function background_process_ass_menu() {
- $items = array();
- $items['admin/config/system/background-process/ass'] = array(
- 'type' => 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("/<table.*?>.*?<\/[\s]*table>/s", $html, $table_htmls);
- $tables = array();
- foreach ($table_htmls[0] as $table_html) {
- // Get title for each row
- preg_match_all("/<th.*?>(.*?)<\/[\s]*th>/s", $table_html, $matches);
- $row_headers = $matches[1];
- // Iterate each row
- preg_match_all("/<tr.*?>(.*?)<\/[\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("/<td.*?>(.*?)<\/[\s]*td>/", $row_html, $td_matches);
- $row = array();
- for ($i=0; $i<count($td_matches[1]); $i++) {
- $td = strip_tags(html_entity_decode($td_matches[1][$i]));
- $i2 = isset($row_headers[$i]) ? $row_headers[$i] : $i;
- $row[$i2] = $td;
- }
- if (count($row) > 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("/<dl.*?>.*?<\/[\s]*dl>/s", $html, $dl_htmls);
- $dls = array();
- foreach ($dl_htmls[0] as $dl_html) {
- // Get title for each row
- preg_match_all("/<dl.*?>(.*?)<\/[\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("/<dt.*?>(.*?)<\/[\s]*dt>/", $row_html, $dt_matches);
- $row = array();
- for ($i=0; $i<count($dt_matches[1]); $i++) {
- $dt = strip_tags(html_entity_decode($dt_matches[1][$i]));
- if (strpos($dt, ':') !== FALSE) {
- list($key, $value) = explode(': ', $dt, 2);
- $dl[$key] = $value;
- }
- }
- }
- $dls[] = $dl;
- }
- return $dls;
- }
|