* Make sure all child processes that have been spawned by the parent process
* have died. This includes process registered as "other_children".
* @warning This is only defined if the MPM defines
- * MPM_NEEDS_RECLAIM_CHILD_PROCESS
+ * AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
* @param terminate Either 1 or 0. If 1, send the child processes SIGTERM
* each time through the loop. If 0, give the process time to die
* on its own before signalling it.
* MPM_CHILD_PID -- Get the pid from the specified spot in the scoreboard
* MPM_NOTE_CHILD_KILLED -- Note the child died in the scoreboard
* </pre>
+ * @tip The MPM child processes which are reclaimed are those listed
+ * in the scoreboard as well as those currently registered via
+ * ap_register_extra_mpm_process().
*/
#ifdef AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
void ap_reclaim_child_processes(int terminate);
#endif
+/**
+ * Tell ap_reclaim_child_processes() about an MPM child process which has no
+ * entry in the scoreboard.
+ * @warning This is only defined if the MPM defines
+ * AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+ * @param pid The process id of an MPM child process which should be
+ * reclaimed when ap_reclaim_child_processes() is called.
+ * @tip If an extra MPM child process terminates prior to calling
+ * ap_reclaim_child_processes(), remove it from the list of such processes
+ * by calling ap_unregister_extra_mpm_process().
+ */
+#ifdef AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+void ap_register_extra_mpm_process(pid_t pid);
+#endif
+
+/**
+ * Unregister an MPM child process which was previously registered by a
+ * call to ap_register_extra_mpm_process().
+ * @warning This is only defined if the MPM defines
+ * AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+ * @param pid The process id of an MPM child process which no longer needs to
+ * be reclaimed.
+ * @return 1 if the process was found and removed, 0 otherwise
+ */
+#ifdef AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+int ap_unregister_extra_mpm_process(pid_t pid);
+#endif
+
/**
* Determine if any child process has died. If no child process died, then
* this process sleeps for the amount of time specified by the MPM defined
clean_child_exit(0);
}
/* else */
+ if (ap_scoreboard_image->parent[slot].pid != 0) {
+ /* This new child process is squatting on the scoreboard
+ * entry owned by an exiting child process, which cannot
+ * exit until all active requests complete.
+ * Don't forget about this exiting child process, or we
+ * won't be able to kill it if it doesn't exit by the
+ * time the server is shut down.
+ */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf,
+ "taking over scoreboard slot from %" APR_PID_T_FMT "%s",
+ ap_scoreboard_image->parent[slot].pid,
+ ap_scoreboard_image->parent[slot].quiescing ?
+ " (quiescing)" : "");
+ ap_register_extra_mpm_process(ap_scoreboard_image->parent[slot].pid);
+ }
ap_scoreboard_image->parent[slot].quiescing = 0;
ap_scoreboard_image->parent[slot].pid = pid;
return 0;
make_child(ap_server_conf, child_slot);
--remaining_children_to_start;
}
+ }
+ else if (ap_unregister_extra_mpm_process(pid.pid) == 1) {
+ /* handled */
#if APR_HAS_OTHER_CHILD
}
else if (apr_proc_other_child_alert(&pid, APR_OC_REASON_DEATH,
#endif
#ifdef AP_MPM_WANT_RECLAIM_CHILD_PROCESSES
+
+typedef enum {DO_NOTHING, SEND_SIGTERM, SEND_SIGKILL, GIVEUP} action_t;
+
+typedef struct extra_process_t {
+ struct extra_process_t *next;
+ pid_t pid;
+} extra_process_t;
+
+static extra_process_t *extras;
+
+void ap_register_extra_mpm_process(pid_t pid)
+{
+ extra_process_t *p = (extra_process_t *)malloc(sizeof(extra_process_t));
+
+ p->next = extras;
+ p->pid = pid;
+ extras = p;
+}
+
+int ap_unregister_extra_mpm_process(pid_t pid)
+{
+ extra_process_t *cur = extras;
+ extra_process_t *prev = NULL;
+
+ while (cur && cur->pid != pid) {
+ prev = cur;
+ cur = cur->next;
+ }
+
+ if (cur) {
+ if (prev) {
+ prev->next = cur->next;
+ }
+ else {
+ extras = cur->next;
+ }
+ free(cur);
+ return 1; /* found */
+ }
+ else {
+ /* we don't know about any such process */
+ return 0;
+ }
+}
+
+static int reclaim_one_pid(pid_t pid, action_t action)
+{
+ apr_proc_t proc;
+ apr_status_t waitret;
+
+ proc.pid = pid;
+ waitret = apr_proc_wait(&proc, NULL, NULL, APR_NOWAIT);
+ if (waitret != APR_CHILD_NOTDONE) {
+ return 1;
+ }
+
+ switch(action) {
+ case DO_NOTHING:
+ break;
+
+ case SEND_SIGTERM:
+ /* ok, now it's being annoying */
+ ap_log_error(APLOG_MARK, APLOG_WARNING,
+ 0, ap_server_conf,
+ "child process %" APR_PID_T_FMT
+ " still did not exit, "
+ "sending a SIGTERM",
+ pid);
+ kill(pid, SIGTERM);
+ break;
+
+ case SEND_SIGKILL:
+ ap_log_error(APLOG_MARK, APLOG_ERR,
+ 0, ap_server_conf,
+ "child process %" APR_PID_T_FMT
+ " still did not exit, "
+ "sending a SIGKILL",
+ pid);
+#ifndef BEOS
+ kill(pid, SIGKILL);
+#else
+ /* sending a SIGKILL kills the entire team on BeOS, and as
+ * httpd thread is part of that team it removes any chance
+ * of ever doing a restart. To counter this I'm changing to
+ * use a kinder, gentler way of killing a specific thread
+ * that is just as effective.
+ */
+ kill_thread(pid);
+#endif
+ break;
+
+ case GIVEUP:
+ /* gave it our best shot, but alas... If this really
+ * is a child we are trying to kill and it really hasn't
+ * exited, we will likely fail to bind to the port
+ * after the restart.
+ */
+ ap_log_error(APLOG_MARK, APLOG_ERR,
+ 0, ap_server_conf,
+ "could not make child process %" APR_PID_T_FMT
+ " exit, "
+ "attempting to continue anyway",
+ pid);
+ break;
+ }
+
+ return 0;
+}
+
void ap_reclaim_child_processes(int terminate)
{
apr_time_t waittime = 1024 * 16;
- apr_status_t waitret;
int i;
+ extra_process_t *cur_extra;
int not_dead_yet;
int max_daemons;
apr_time_t starttime = apr_time_now();
* at which elapsed time from starting the reclaim
*/
struct {
- enum {DO_NOTHING, SEND_SIGTERM, SEND_SIGKILL, GIVEUP} action;
+ action_t action;
apr_time_t action_time;
} action_table[] = {
{DO_NOTHING, 0}, /* dummy entry for iterations where we reap
not_dead_yet = 0;
for (i = 0; i < max_daemons; ++i) {
pid_t pid = MPM_CHILD_PID(i);
- apr_proc_t proc;
- if (pid == 0)
- continue;
+ if (pid == 0) {
+ continue; /* not every scoreboard entry is in use */
+ }
- proc.pid = pid;
- waitret = apr_proc_wait(&proc, NULL, NULL, APR_NOWAIT);
- if (waitret != APR_CHILD_NOTDONE) {
+ if (reclaim_one_pid(pid, action_table[cur_action].action)) {
MPM_NOTE_CHILD_KILLED(i);
- continue;
}
-
- ++not_dead_yet;
- switch(action_table[cur_action].action) {
- case DO_NOTHING:
- break;
-
- case SEND_SIGTERM:
- /* ok, now it's being annoying */
- ap_log_error(APLOG_MARK, APLOG_WARNING,
- 0, ap_server_conf,
- "child process %" APR_PID_T_FMT
- " still did not exit, "
- "sending a SIGTERM",
- pid);
- kill(pid, SIGTERM);
- break;
-
- case SEND_SIGKILL:
- ap_log_error(APLOG_MARK, APLOG_ERR,
- 0, ap_server_conf,
- "child process %" APR_PID_T_FMT
- " still did not exit, "
- "sending a SIGKILL",
- pid);
-#ifndef BEOS
- kill(pid, SIGKILL);
-#else
- /* sending a SIGKILL kills the entire team on BeOS, and as
- * httpd thread is part of that team it removes any chance
- * of ever doing a restart. To counter this I'm changing to
- * use a kinder, gentler way of killing a specific thread
- * that is just as effective.
- */
- kill_thread(pid);
-#endif
- break;
-
- case GIVEUP:
- /* gave it our best shot, but alas... If this really
- * is a child we are trying to kill and it really hasn't
- * exited, we will likely fail to bind to the port
- * after the restart.
- */
- ap_log_error(APLOG_MARK, APLOG_ERR,
- 0, ap_server_conf,
- "could not make child process %" APR_PID_T_FMT
- " exit, "
- "attempting to continue anyway",
- pid);
- break;
+ else {
+ ++not_dead_yet;
}
}
+ cur_extra = extras;
+ while (cur_extra) {
+ extra_process_t *next = cur_extra->next;
+
+ if (reclaim_one_pid(cur_extra->pid, action_table[cur_action].action)) {
+ AP_DEBUG_ASSERT(1 == ap_unregister_extra_mpm_process(cur_extra->pid));
+ }
+ else {
+ ++not_dead_yet;
+ }
+ cur_extra = next;
+ }
#if APR_HAS_OTHER_CHILD
apr_proc_other_child_refresh_all(APR_OC_REASON_RESTART);
#endif