| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "fcgid_pm.h" |
| #include "fcgid_pm_main.h" |
| #include "fcgid_conf.h" |
| #include "fcgid_proctbl.h" |
| #include "fcgid_proc.h" |
| #include "fcgid_spawn_ctl.h" |
| #define HAS_GRACEFUL_KILL "Gracefulkill" |
| |
| static int g_idle_timeout; |
| static int g_idle_scan_interval; |
| static int g_busy_timeout; |
| static int g_busy_scan_interval; |
| static int g_proc_lifetime; |
| static int g_error_scan_interval; |
| static int g_zombie_scan_interval; |
| |
| static void |
| link_node_to_list(server_rec * main_server, |
| fcgid_procnode * header, |
| fcgid_procnode * node, fcgid_procnode * table_array) |
| { |
| safe_lock(main_server); |
| node->next_index = header->next_index; |
| header->next_index = node - table_array; |
| safe_unlock(main_server); |
| } |
| |
| static apr_time_t lastidlescan = 0; |
| static void scan_idlelist(server_rec * main_server) |
| { |
| /* |
| Scan the idle list |
| 1. move all processes idle timeout to error list |
| 2. move all processes lifetime expired to error list |
| */ |
| fcgid_procnode *previous_node, *current_node, *next_node; |
| fcgid_procnode *error_list_header; |
| fcgid_procnode *proc_table; |
| apr_time_t last_active_time, start_time; |
| apr_time_t now = apr_time_now(); |
| |
| /* Should I check the idle list now? */ |
| if (procmgr_must_exit() |
| || apr_time_sec(now) - apr_time_sec(lastidlescan) <= |
| g_idle_scan_interval) |
| return; |
| lastidlescan = now; |
| |
| /* Check the list */ |
| proc_table = proctable_get_table_array(); |
| previous_node = proctable_get_idle_list(); |
| error_list_header = proctable_get_error_list(); |
| |
| safe_lock(main_server); |
| current_node = &proc_table[previous_node->next_index]; |
| while (current_node != proc_table) { |
| next_node = &proc_table[current_node->next_index]; |
| last_active_time = current_node->last_active_time; |
| start_time = current_node->start_time; |
| if ((apr_time_sec(now) - apr_time_sec(last_active_time) > |
| g_idle_timeout |
| || apr_time_sec(now) - apr_time_sec(start_time) > |
| g_proc_lifetime) |
| && is_kill_allowed(current_node)) { |
| /* Set die reason for log */ |
| if (apr_time_sec(now) - apr_time_sec(last_active_time) > |
| g_idle_timeout) |
| current_node->diewhy = FCGID_DIE_IDLE_TIMEOUT; |
| else if (apr_time_sec(now) - apr_time_sec(start_time) > |
| g_proc_lifetime) |
| current_node->diewhy = FCGID_DIE_LIFETIME_EXPIRED; |
| |
| /* Unlink from idle list */ |
| previous_node->next_index = current_node->next_index; |
| |
| /* Link to error list */ |
| current_node->next_index = error_list_header->next_index; |
| error_list_header->next_index = current_node - proc_table; |
| } else |
| previous_node = current_node; |
| |
| current_node = next_node; |
| } |
| safe_unlock(main_server); |
| } |
| |
| static apr_time_t lastbusyscan = 0; |
| static void scan_busylist(server_rec * main_server) |
| { |
| /* |
| Scan the busy list |
| 1. move all expired node to error list |
| */ |
| fcgid_procnode *previous_node, *current_node, *next_node; |
| fcgid_procnode *error_list_header; |
| fcgid_procnode *proc_table; |
| apr_time_t last_active_time; |
| apr_time_t now = apr_time_now(); |
| |
| /* Should I check the busy list? */ |
| if (procmgr_must_exit() |
| || apr_time_sec(now) - apr_time_sec(lastbusyscan) <= |
| g_busy_scan_interval) |
| return; |
| lastbusyscan = now; |
| |
| /* Check the list */ |
| proc_table = proctable_get_table_array(); |
| previous_node = proctable_get_busy_list(); |
| error_list_header = proctable_get_error_list(); |
| |
| safe_lock(main_server); |
| current_node = &proc_table[previous_node->next_index]; |
| while (current_node != proc_table) { |
| next_node = &proc_table[current_node->next_index]; |
| |
| last_active_time = current_node->last_active_time; |
| if (apr_time_sec(now) - apr_time_sec(last_active_time) > |
| g_busy_timeout) { |
| /* Set dir reason for log */ |
| current_node->diewhy = FCGID_DIE_BUSY_TIMEOUT; |
| |
| /* Unlink from busy list */ |
| previous_node->next_index = current_node->next_index; |
| |
| /* Link to error list */ |
| current_node->next_index = error_list_header->next_index; |
| error_list_header->next_index = current_node - proc_table; |
| } else |
| previous_node = current_node; |
| |
| current_node = next_node; |
| } |
| safe_unlock(main_server); |
| } |
| |
| static apr_time_t lastzombiescan = 0; |
| static void scan_idlelist_zombie(server_rec * main_server) |
| { |
| /* |
| Scan the idle list |
| 1. pick up the node for scan(now-last_activ>g_zombie_scan_interval) |
| 2. check if it's zombie process |
| 3. if it's zombie process, wait() and return to free list |
| 4. return to idle list if it's not zombie process |
| */ |
| pid_t thepid; |
| fcgid_procnode *previous_node, *current_node, *next_node; |
| fcgid_procnode *error_list_header, *check_list_header; |
| fcgid_procnode *proc_table; |
| apr_time_t last_active_time; |
| apr_time_t now = apr_time_now(); |
| fcgid_procnode temp_header; |
| |
| memset(&temp_header, 0, sizeof(temp_header)); |
| |
| /* Should I check zombie processes in idle list now? */ |
| if (procmgr_must_exit() |
| || apr_time_sec(now) - apr_time_sec(lastzombiescan) <= |
| g_zombie_scan_interval) |
| return; |
| lastzombiescan = now; |
| |
| /* |
| Check the list |
| */ |
| proc_table = proctable_get_table_array(); |
| previous_node = proctable_get_idle_list(); |
| error_list_header = proctable_get_error_list(); |
| check_list_header = &temp_header; |
| |
| safe_lock(main_server); |
| current_node = &proc_table[previous_node->next_index]; |
| while (current_node != proc_table) { |
| next_node = &proc_table[current_node->next_index]; |
| |
| /* Is it time for zombie check? */ |
| last_active_time = current_node->last_active_time; |
| if (apr_time_sec(now) - apr_time_sec(last_active_time) > |
| g_zombie_scan_interval) { |
| /* Unlink from idle list */ |
| previous_node->next_index = current_node->next_index; |
| |
| /* Link to check list */ |
| current_node->next_index = check_list_header->next_index; |
| check_list_header->next_index = current_node - proc_table; |
| } else |
| previous_node = current_node; |
| |
| current_node = next_node; |
| } |
| safe_unlock(main_server); |
| |
| /* |
| Now check every node in check list |
| 1) If it's zombie process, wait() and return to free list |
| 2) If it's not zombie process, link it to the tail of idle list |
| */ |
| previous_node = check_list_header; |
| current_node = &proc_table[previous_node->next_index]; |
| while (current_node != proc_table) { |
| next_node = &proc_table[current_node->next_index]; |
| |
| /* Is it zombie process? */ |
| thepid = current_node->proc_id->pid; |
| if (proc_wait_process(main_server, current_node) == APR_CHILD_DONE) { |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, |
| "mod_fcgid: cleanup zombie process %" |
| APR_PID_T_FMT, thepid); |
| |
| /* Unlink from check list */ |
| previous_node->next_index = current_node->next_index; |
| |
| /* Link to free list */ |
| link_node_to_list(main_server, proctable_get_free_list(), |
| current_node, proc_table); |
| } else |
| previous_node = current_node; |
| |
| current_node = next_node; |
| } |
| |
| /* |
| Now link the check list back to the tail of idle list |
| */ |
| if (check_list_header->next_index) { |
| safe_lock(main_server); |
| previous_node = proctable_get_idle_list(); |
| current_node = &proc_table[previous_node->next_index]; |
| |
| /* Find the tail of idle list */ |
| while (current_node != proc_table) { |
| previous_node = current_node; |
| current_node = &proc_table[current_node->next_index]; |
| } |
| |
| /* Link check list to the tail of idle list */ |
| previous_node->next_index = check_list_header->next_index; |
| safe_unlock(main_server); |
| } |
| } |
| |
| static apr_time_t lasterrorscan = 0; |
| static void scan_errorlist(server_rec * main_server) |
| { |
| /* |
| kill() and wait() every node in error list |
| put them back to free list after that |
| */ |
| void *dummy; |
| fcgid_procnode *previous_node, *current_node, *next_node; |
| apr_time_t now = apr_time_now(); |
| fcgid_procnode *error_list_header = proctable_get_error_list(); |
| fcgid_procnode *free_list_header = proctable_get_free_list(); |
| fcgid_procnode *proc_table = proctable_get_table_array(); |
| fcgid_procnode temp_error_header; |
| |
| /* Should I check the busy list? */ |
| if (procmgr_must_exit() |
| || apr_time_sec(now) - apr_time_sec(lasterrorscan) <= |
| g_error_scan_interval) |
| return; |
| lasterrorscan = now = apr_time_now(); |
| |
| /* Try wait dead processes, restore to free list */ |
| /* Note: I can't keep the lock during the scan */ |
| safe_lock(main_server); |
| temp_error_header.next_index = error_list_header->next_index; |
| error_list_header->next_index = 0; |
| safe_unlock(main_server); |
| |
| previous_node = &temp_error_header; |
| current_node = &proc_table[previous_node->next_index]; |
| while (current_node != proc_table) { |
| next_node = &proc_table[current_node->next_index]; |
| |
| if (proc_wait_process(main_server, current_node) != |
| APR_CHILD_NOTDONE) { |
| /* Unlink from error list */ |
| previous_node->next_index = current_node->next_index; |
| |
| /* Link to free list */ |
| current_node->next_index = free_list_header->next_index; |
| free_list_header->next_index = current_node - proc_table; |
| } else |
| previous_node = current_node; |
| |
| current_node = next_node; |
| } |
| |
| /* Kill the left processes, wait() them in the next round */ |
| for (current_node = &proc_table[temp_error_header.next_index]; |
| current_node != proc_table; |
| current_node = &proc_table[current_node->next_index]) { |
| /* Try gracefully first */ |
| dummy = NULL; |
| apr_pool_userdata_get(&dummy, HAS_GRACEFUL_KILL, |
| current_node->proc_pool); |
| if (!dummy) { |
| proc_kill_gracefully(current_node, main_server); |
| apr_pool_userdata_set("set", HAS_GRACEFUL_KILL, |
| apr_pool_cleanup_null, |
| current_node->proc_pool); |
| } else { |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, |
| "mod_fcgid: process %" APR_PID_T_FMT |
| " graceful kill fail, sending SIGKILL", |
| current_node->proc_id->pid); |
| proc_kill_force(current_node, main_server); |
| } |
| } |
| |
| /* Link the temp error list back */ |
| safe_lock(main_server); |
| /* Find the tail of error list */ |
| previous_node = error_list_header; |
| current_node = &proc_table[previous_node->next_index]; |
| while (current_node != proc_table) { |
| previous_node = current_node; |
| current_node = &proc_table[current_node->next_index]; |
| } |
| previous_node->next_index = temp_error_header.next_index; |
| safe_unlock(main_server); |
| } |
| |
| static void kill_all_subprocess(server_rec * main_server) |
| { |
| size_t i; |
| int exitcode; |
| apr_exit_why_e exitwhy; |
| fcgid_procnode *proc_table = proctable_get_table_array(); |
| |
| /* Kill gracefully */ |
| for (i = 0; i < proctable_get_table_size(); i++) { |
| if (proc_table[i].proc_pool) |
| proc_kill_gracefully(&proc_table[i], main_server); |
| } |
| apr_sleep(apr_time_from_sec(1)); |
| |
| /* Kill with SIGKILL if it doesn't work */ |
| for (i = 0; i < proctable_get_table_size(); i++) { |
| if (proc_table[i].proc_pool) { |
| if (apr_proc_wait(proc_table[i].proc_id, &exitcode, &exitwhy, |
| APR_NOWAIT) != APR_CHILD_NOTDONE) { |
| proc_table[i].diewhy = FCGID_DIE_SHUTDOWN; |
| proc_print_exit_info(&proc_table[i], exitcode, exitwhy, |
| main_server); |
| apr_pool_destroy(proc_table[i].proc_pool); |
| proc_table[i].proc_pool = NULL; |
| } else |
| proc_kill_force(&proc_table[i], main_server); |
| } |
| } |
| |
| /* Wait again */ |
| for (i = 0; i < proctable_get_table_size(); i++) { |
| if (proc_table[i].proc_pool) { |
| if (apr_proc_wait(proc_table[i].proc_id, &exitcode, &exitwhy, |
| APR_WAIT) != APR_CHILD_NOTDONE) { |
| proc_table[i].diewhy = FCGID_DIE_SHUTDOWN; |
| proc_print_exit_info(&proc_table[i], exitcode, exitwhy, |
| main_server); |
| apr_pool_destroy(proc_table[i].proc_pool); |
| proc_table[i].proc_pool = NULL; |
| } |
| } |
| } |
| } |
| |
| static void |
| fastcgi_spawn(fcgid_command * command, server_rec * main_server, |
| apr_pool_t * configpool) |
| { |
| fcgid_procnode *free_list_header, *proctable_array, |
| *procnode, *idle_list_header; |
| fcgid_proc_info procinfo; |
| apr_status_t rv; |
| int i; |
| |
| free_list_header = proctable_get_free_list(); |
| idle_list_header = proctable_get_idle_list(); |
| proctable_array = proctable_get_table_array(); |
| |
| /* Apply a slot from free list */ |
| safe_lock(main_server); |
| if (free_list_header->next_index == 0) { |
| safe_unlock(main_server); |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, |
| "mod_fcgid: too much processes, please increase FCGID_MAX_APPLICATION"); |
| return; |
| } |
| procnode = &proctable_array[free_list_header->next_index]; |
| free_list_header->next_index = procnode->next_index; |
| procnode->next_index = 0; |
| safe_unlock(main_server); |
| |
| /* Prepare to spawn */ |
| procnode->deviceid = command->deviceid; |
| procnode->inode = command->inode; |
| procnode->share_grp_id = command->share_grp_id; |
| procnode->virtualhost = command->virtualhost; |
| procnode->uid = command->uid; |
| procnode->gid = command->gid; |
| procnode->start_time = procnode->last_active_time = apr_time_now(); |
| procnode->requests_handled = 0; |
| procnode->diewhy = FCGID_DIE_KILLSELF; |
| procnode->proc_pool = NULL; |
| |
| procinfo.cgipath = command->cgipath; |
| procinfo.configpool = configpool; |
| procinfo.main_server = main_server; |
| procinfo.uid = command->uid; |
| procinfo.gid = command->gid; |
| procinfo.userdir = command->userdir; |
| if (apr_pool_create(&procnode->proc_pool, configpool) != APR_SUCCESS |
| || (procinfo.proc_environ = |
| apr_table_make(procnode->proc_pool, INITENV_CNT)) == NULL) { |
| /* Link the node back to free list in this case */ |
| if (procnode->proc_pool) |
| apr_pool_destroy(procnode->proc_pool); |
| link_node_to_list(main_server, free_list_header, procnode, |
| proctable_array); |
| |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server, |
| "mod_fcgid: can't create pool for process"); |
| return; |
| } |
| for (i = 0; i < INITENV_CNT; i++) { |
| if (command->initenv_key[i][0] == '\0') |
| break; |
| apr_table_set(procinfo.proc_environ, command->initenv_key[i], |
| command->initenv_val[i]); |
| } |
| |
| /* Spawn the process now */ |
| if ((rv = |
| proc_spawn_process(command->wrapperpath, &procinfo, |
| procnode)) != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_WARNING, rv, main_server, |
| "mod_fcgid: spawn process %s error", |
| command->cgipath); |
| |
| apr_pool_destroy(procnode->proc_pool); |
| link_node_to_list(main_server, free_list_header, |
| procnode, proctable_array); |
| return; |
| } else { |
| /* The job done */ |
| link_node_to_list(main_server, idle_list_header, |
| procnode, proctable_array); |
| ap_log_error(APLOG_MARK, APLOG_INFO, 0, main_server, |
| "mod_fcgid: server %s:%s(%" APR_PID_T_FMT ") started", |
| command->virtualhost, command->cgipath, procnode->proc_id->pid); |
| register_spawn(main_server, procnode); |
| } |
| } |
| |
| apr_status_t pm_main(server_rec * main_server, apr_pool_t * configpool) |
| { |
| fcgid_command command; |
| |
| /* Initialize the variables from configuration */ |
| g_idle_timeout = get_idle_timeout(main_server); |
| g_idle_scan_interval = get_idle_scan_interval(main_server); |
| g_busy_scan_interval = get_busy_scan_interval(main_server); |
| g_proc_lifetime = get_proc_lifetime(main_server); |
| g_error_scan_interval = get_error_scan_interval(main_server); |
| g_zombie_scan_interval = get_zombie_scan_interval(main_server); |
| g_busy_timeout = get_busy_timeout(main_server); |
| g_busy_timeout += 10; |
| |
| while (1) { |
| if (procmgr_must_exit()) |
| break; |
| |
| /* Wait for command */ |
| if (procmgr_peek_cmd(&command, main_server) == APR_SUCCESS) { |
| if (is_spawn_allowed(main_server, &command)) |
| fastcgi_spawn(&command, main_server, configpool); |
| |
| procmgr_finish_notify(main_server); |
| } |
| |
| /* Move matched node to error list */ |
| scan_idlelist_zombie(main_server); |
| scan_idlelist(main_server); |
| scan_busylist(main_server); |
| |
| /* Kill() and wait() nodes in error list */ |
| scan_errorlist(main_server); |
| } |
| |
| /* Stop all processes */ |
| kill_all_subprocess(main_server); |
| |
| return APR_SUCCESS; |
| } |