pcs 98/10/06 08:41:46
Modified: src/main http_main.c Log: WIN32: Add new options to let Apache signal itself to shutdown or restart. The option is -k, used like this: -k shutdown -k restart This lets people signal Apache on Win95 systems (where previously Apache was controlled by doing a ^C to stop it running). Reviewed by: Ken, Lars, Jim, Martin (all concept only) Revision Changes Path 1.399 +242 -84 apache-1.3/src/main/http_main.c Index: http_main.c =================================================================== RCS file: /export/home/cvs/apache-1.3/src/main/http_main.c,v retrieving revision 1.398 retrieving revision 1.399 diff -u -r1.398 -r1.399 --- http_main.c 1998/10/06 15:36:02 1.398 +++ http_main.c 1998/10/06 15:41:44 1.399 @@ -999,6 +999,10 @@ fprintf(stderr, " -l : list compiled-in modules\n"); fprintf(stderr, " -S : show parsed settings (currently only vhost settings)\n"); fprintf(stderr, " -t : run syntax test for configuration files only\n"); +#ifdef WIN32 + fprintf(stderr, " -k shutdown : tell running Apache to shutdown\n"); + fprintf(stderr, " -k restart : tell running Apache to do a graceful restart\n"); +#endif exit(1); } @@ -2542,17 +2546,41 @@ #ifdef WIN32 /* - * signal_parent() tells the parent process to wake up and do something. - * Once woken it will look at shutdown_pending and restart_pending to decide - * what to do. If neither variable is set, it will do a shutdown. This function - * if called by start_shutdown() or start_restart() in the parent's process - * space, so that the variables get set. However it can also be called - * by child processes to force the parent to exit in an emergency. + * Signalling Apache on NT. + * + * Under Unix, Apache can be told to shutdown or restart by sending various + * signals (HUP, USR, TERM). On NT we don't have easy access to signals, so + * we use "events" instead. The parent apache process goes into a loop + * where it waits forever for a set of events. Two of those events are + * called + * + * apPID_shutdown + * apPID_restart + * + * (where PID is the PID of the apache parent process). When one of these + * is signalled, the Apache parent performs the appropriate action. The events + * can become signalled through internal Apache methods (e.g. if the child + * finds a fatal error and needs to kill its parent), via the service + * control manager (the control thread will signal the shutdown event when + * requested to stop the Apache service), from the -k Apache command line, + * or from any external program which finds the Apache PID from the + * httpd.pid file. + * + * The signal_parent() function, below, is used to signal one of these events. + * It can be called by any child or parent process, since it does not + * rely on global variables. + * + * On entry, type gives the event to signal. 0 means shutdown, 1 means + * graceful restart. */ -static void signal_parent(void) +static void signal_parent(int type) { HANDLE e; + char *signal_name; + extern char signal_shutdown_name[]; + extern char signal_restart_name[]; + /* after updating the shutdown_pending or restart flags, we need * to wake up the parent process so it can see the changes. The * parent will normally be waiting for either a child process @@ -2564,21 +2592,28 @@ return; } - APD1("*** SIGNAL_PARENT SET ***"); + switch(type) { + case 0: signal_name = signal_shutdown_name; break; + case 1: signal_name = signal_restart_name; break; + default: return; + } - e = OpenEvent(EVENT_ALL_ACCESS, FALSE, "apache-signal"); + APD2("signal_parent signalling event \"%s\"", signal_name); + + e = OpenEvent(EVENT_ALL_ACCESS, FALSE, signal_name); if (!e) { - /* Um, problem, can't signal the main loop, which means we can't + /* Um, problem, can't signal the parent, which means we can't * signal ourselves to die. Ignore for now... */ ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf, - "OpenEvent on apache-signal event"); + "OpenEvent on %s event", signal_name); return; } if (SetEvent(e) == 0) { /* Same problem as above */ ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf, - "SetEvent on apache-signal event"); + "SetEvent on %s event", signal_name); + CloseHandle(e); return; } CloseHandle(e); @@ -2586,24 +2621,19 @@ #endif /* - * start_shutdown() and start_restart(), below, are a first stab at + * ap_start_shutdown() and ap_start_restart(), below, are a first stab at * functions to initiate shutdown or restart without relying on signals. * Previously this was initiated in sig_term() and restart() signal handlers, * but we want to be able to start a shutdown/restart from other sources -- * e.g. on Win32, from the service manager. Now the service manager can - * call start_shutdown() or start_restart() as appropiate. - * - * These should only be called from the parent process itself, since the - * parent process will use the shutdown_pending and restart_pending variables - * to determine whether to shutdown or restart. The child process should - * call signal_parent() directly to tell the parent to die -- this will - * cause neither of those variable to be set, which the parent will - * assume means something serious is wrong (which it will be, for the - * child to force an exit) and so do an exit anyway. + * call ap_start_shutdown() or ap_start_restart() as appropiate. Note that + * these functions can also be called by the child processes, since global + * variables are no longer used to pass on the required action to the parent. */ void ap_start_shutdown(void) { +#ifndef WIN32 if (shutdown_pending == 1) { /* Um, is this _probably_ not an error, if the user has * tried to do a shutdown twice quickly, so we won't @@ -2612,24 +2642,23 @@ return; } shutdown_pending = 1; - -#ifdef WIN32 - signal_parent(); /* get the parent process to wake up */ +#else + signal_parent(0); /* get the parent process to wake up */ #endif } /* do a graceful restart if graceful == 1 */ void ap_start_restart(int graceful) { +#ifndef WIN32 if (restart_pending == 1) { /* Probably not an error - don't bother reporting it */ return; } restart_pending = 1; is_graceful = graceful; - -#ifdef WIN32 - signal_parent(); /* get the parent process to wake up */ +#else + signal_parent(1); /* get the parent process to wake up */ #endif /* WIN32 */ } @@ -4633,11 +4662,13 @@ * * Signalling between the parent and working process uses a Win32 * event. Each child has a unique name for the event, which is - * passed to it with the -c argument when the child is spawned. The + * passed to it with the -Z argument when the child is spawned. The * parent sets (signals) this event to tell the child to die. * At present all children do a graceful die - they finish all * current jobs _and_ empty the listen queue before they exit. - * A non-graceful die would need a second event. + * A non-graceful die would need a second event. The -Z argument in + * the child is also used to create the shutdown and restart events, + * since the prefix (apPID) contains the parent process PID. * * The code below starts with functions at the lowest level - * worker threads, and works up to the top level - the main() @@ -5001,17 +5032,37 @@ event *exit_event; mutex *start_mutex; +#define MAX_SIGNAL_NAME 30 /* Long enough for apPID_shutdown, where PID is an int */ +char signal_name_prefix[MAX_SIGNAL_NAME]; +char signal_restart_name[MAX_SIGNAL_NAME]; +char signal_shutdown_name[MAX_SIGNAL_NAME]; + #define MAX_SELECT_ERRORS 100 +/* + * Initialise the signal names, in the global variables signal_name_prefix, + * signal_restart_name and signal_shutdown_name. + */ + +void setup_signal_names(char *prefix) +{ + ap_snprintf(signal_name_prefix, sizeof(signal_name_prefix), prefix); + ap_snprintf(signal_shutdown_name, sizeof(signal_shutdown_name), + "%s_shutdown", signal_name_prefix); + ap_snprintf(signal_restart_name, sizeof(signal_restart_name), + "%s_restart", signal_name_prefix); + + APD2("signal prefix %s", signal_name_prefix); +} + +/* + * worker_main() is main loop for the child process. The loop in + * this function becomes the controlling thread for the actually working + * threads (which run in a loop in child_sub_main()). + */ + void worker_main(void) { - /* - * I am writing this stuff specifically for NT. - * have pulled out a lot of the restart and - * graceful restart stuff, because that is only - * useful on Unix (not sure it even makes sense - * in a multi-threaded env. - */ int nthreads; fd_set main_fds; int srv; @@ -5059,7 +5110,13 @@ reinit_scoreboard(pconf); - //ap_acquire_mutex(start_mutex); + /* + * Wait until we have permission to start accepting connections. + * start_mutex is used to ensure that only one child ever + * goes into the listen/accept loop at once. Also wait on exit_event, + * in case we (this child) is told to die before we get a chance to + * serve any requests. + */ hObjects[0] = (HANDLE)start_mutex; hObjects[1] = (HANDLE)exit_event; rv = WaitForMultipleObjects(2, hObjects, FALSE, INFINITE); @@ -5085,7 +5142,7 @@ ap_log_error(APLOG_MARK, APLOG_CRIT|APLOG_NOERRNO, NULL, "No sockets were created for listening"); - signal_parent(); /* tell parent to die */ + signal_parent(0); /* tell parent to die */ ap_destroy_pool(pchild); cleanup_scoreboard(); @@ -5114,16 +5171,12 @@ /* spawn off the threads */ child_handles = (thread *) alloca(nthreads * sizeof(int)); - { - int i; - - for (i = 0; i < nthreads; i++) { - child_handles[i] = create_thread((void (*)(void *)) child_main, (void *) i); - } - if (nthreads > max_daemons_limit) { - max_daemons_limit = nthreads; - } + for (i = 0; i < nthreads; i++) { + child_handles[i] = create_thread((void (*)(void *)) child_main, (void *) i); } + if (nthreads > max_daemons_limit) { + max_daemons_limit = nthreads; + } while (1) { #if SEVERELY_VERBOSE @@ -5288,22 +5341,21 @@ clean_parent_exit(0); } /* standalone_main */ -/* Spawn a child Apache process. The child process has the command - * line arguments from argc and argv[], plus a -Z argument giving the - * name of an event. The child should open and poll or wait on this - * event. When it is signalled, the child should die. prefix is a - * prefix string for the event name. +/* + * Spawn a child Apache process. The child process has the command line arguments from + * argc and argv[], plus a -Z argument giving the name of an event. The child should + * open and poll or wait on this event. When it is signalled, the child should die. + * prefix is a prefix string for the event name. * - * The child_num argument on entry contains a serial number for this - * child (used to create a unique event name). On exit, this number - * will have been incremented by one, ready for the next call. + * The child_num argument on entry contains a serial number for this child (used to create + * a unique event name). On exit, this number will have been incremented by one, ready + * for the next call. * * On exit, the value pointed to be *ev will contain the event created * to signal the new child process. * - * The return value is the handle to the child process if successful, - * else -1. If -1 is returned the error will already have been logged - * by ap_log_error(). + * The return value is the handle to the child process if successful, else -1. If -1 is + * returned the error will already have been logged by ap_log_error(). */ int create_event_and_spawn(int argc, char **argv, event **ev, int *child_num, char *prefix) @@ -5311,8 +5363,15 @@ char buf[40], mod[200]; int i, rv; char **pass_argv = (char **) alloca(sizeof(char *) * (argc + 3)); - - ap_snprintf(buf, sizeof(buf), "%s_%d", prefix, ++(*child_num)); + + /* We need an event to tell the child process to kill itself when + * the parent is doing a shutdown/restart. This will be named + * apPID_CN where PID is the parent Apache process PID and + * N is a unique child serial number. prefix contains + * the "apPID" part. The child will get the name of this + * event as its -Z command line argument. + */ + ap_snprintf(buf, sizeof(buf), "%s_C%d", prefix, ++(*child_num)); _flushall(); *ev = CreateEvent(NULL, TRUE, FALSE, buf); if (!*ev) { @@ -5409,10 +5468,11 @@ int *child; int child_num = 0; int rv, cld; - char buf[100]; + char signal_prefix_string[100]; int i; time_t tmstart; - HANDLE signal_event; /* used to signal shutdown/restart to parent */ + HANDLE signal_shutdown_event; /* used to signal shutdown to parent */ + HANDLE signal_restart_event; /* used to signal a restart to parent */ HANDLE process_handles[MAX_PROCESSES]; HANDLE process_kill_events[MAX_PROCESSES]; int current_live_processes = 0; /* number of child process we know about */ @@ -5425,22 +5485,34 @@ is_graceful = 0; ++generation; - signal_event = OpenEvent(EVENT_ALL_ACCESS, FALSE, "apache-signal"); - if (!signal_event) { + ap_snprintf(signal_prefix_string, sizeof(signal_prefix_string), + "ap%d", getpid()); + setup_signal_names(signal_prefix_string); + + signal_shutdown_event = CreateEvent(NULL, TRUE, FALSE, signal_shutdown_name); + if (!signal_shutdown_event) { + ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf, + "Cannot create shutdown event %s", signal_shutdown_name); + exit(1); + } + APD2("master_main: created event %s", signal_shutdown_name); + signal_restart_event = CreateEvent(NULL, TRUE, FALSE, signal_restart_name); + if (!signal_restart_event) { + CloseHandle(signal_shutdown_event); ap_log_error(APLOG_MARK, APLOG_EMERG|APLOG_WIN32ERROR, server_conf, - "Cannot open apache-signal event"); + "Cannot create restart event %s", signal_restart_name); exit(1); } + APD2("master_main: created event %s", signal_restart_name); - sprintf(buf, "Apache%d", getpid()); - start_mutex = ap_create_mutex(buf); + start_mutex = ap_create_mutex(signal_prefix_string); ev = (event **) alloca(sizeof(event *) * nchild); child = (int *) alloca(sizeof(int) * (nchild+1)); while (processes_to_create--) { service_set_status(SERVICE_START_PENDING); if (create_process(process_handles, process_kill_events, - ¤t_live_processes, &child_num, buf, argc, argv) < 0) { + ¤t_live_processes, &child_num, signal_prefix_string, argc, argv) < 0) { goto die_now; } } @@ -5461,8 +5533,6 @@ ap_set_version(); ap_init_modules(pconf, server_conf); version_locked++; - if (!is_graceful) - reinit_scoreboard(pconf); restart_pending = shutdown_pending = 0; @@ -5478,15 +5548,17 @@ ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO, server_conf, "master_main: no child processes alive! creating one"); if (create_process(process_handles, process_kill_events, - ¤t_live_processes, &child_num, buf, argc, argv) < 0) { + ¤t_live_processes, &child_num, signal_prefix_string, + argc, argv) < 0) { goto die_now; } if (processes_to_create) { processes_to_create--; } } - process_handles[current_live_processes] = signal_event; - rv = WaitForMultipleObjects(current_live_processes+1, (HANDLE *)process_handles, + process_handles[current_live_processes] = signal_shutdown_event; + process_handles[current_live_processes+1] = signal_restart_event; + rv = WaitForMultipleObjects(current_live_processes+2, (HANDLE *)process_handles, FALSE, INFINITE); if (rv == WAIT_FAILED) { /* Something serious is wrong */ @@ -5494,14 +5566,36 @@ "WaitForMultipeObjects on process handles and apache-signal -- doing shutdown"); shutdown_pending = 1; break; + } + if (rv == WAIT_TIMEOUT) { + /* Hey, this cannot happen */ + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "WaitForMultipeObjects with INFINITE wait exited with WAIT_TIMEOUT"); + shutdown_pending = 1; } - ap_assert(rv != WAIT_TIMEOUT); + cld = rv - WAIT_OBJECT_0; APD4("main process: wait finished, cld=%d handle %d (max=%d)", cld, process_handles[cld], current_live_processes); if (cld == current_live_processes) { - /* stop_event is signalled, we should exit now */ - if (ResetEvent(signal_event) == 0) - APD1("main process: *** ERROR: ResetEvent(stop_event) failed ***"); + /* shutdown event signalled, we should exit now */ + if (ResetEvent(signal_shutdown_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, server_conf, + "ResetEvent(signal_shutdown_event)"); + /* Continue -- since we are doing a shutdown anyway */ + } + shutdown_pending = 1; + APD3("main process: stop_event signalled: shutdown_pending=%d, restart_pending=%d", + shutdown_pending, restart_pending); + break; + } + if (cld == current_live_processes+1) { + /* restart event signalled, we should exit now */ + if (ResetEvent(signal_restart_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, server_conf, + "ResetEvent(signal_restart_event)"); + /* Continue -- hopefully the restart will fix the problem */ + } + restart_pending = 1; APD3("main process: stop_event signalled: shutdown_pending=%d, restart_pending=%d", shutdown_pending, restart_pending); break; @@ -5510,7 +5604,8 @@ cleanup_process(process_handles, process_kill_events, cld, ¤t_live_processes); APD2("main_process: child in slot %d died", rv); if (processes_to_create) { - create_process(process_handles, process_kill_events, ¤t_live_processes, &child_num, buf, argc, argv); + create_process(process_handles, process_kill_events, ¤t_live_processes, + &child_num, signal_prefix_string, argc, argv); processes_to_create--; } } @@ -5527,7 +5622,6 @@ ap_log_error(APLOG_MARK,APLOG_WIN32ERROR, server_conf, "SetEvent for child process in slot #%d", i); } - break; } if (restart_pending) { @@ -5539,7 +5633,8 @@ for (i = 0; i < nchild; ++i) { if (current_live_processes >= MAX_PROCESSES) break; - create_process(process_handles, process_kill_events, ¤t_live_processes, &child_num, buf, argc, argv); + create_process(process_handles, process_kill_events, ¤t_live_processes, + &child_num, signal_prefix_string, argc, argv); processes_to_create--; } for (i = 0; i < children_to_kill; i++) { @@ -5559,7 +5654,8 @@ APD2("*** main process shutdown, processes=%d ***", current_live_processes); die_now: - CloseHandle(signal_event); + CloseHandle(signal_restart_event); + CloseHandle(signal_shutdown_event); tmstart = time(NULL); while (current_live_processes && ((tmstart+60) > time(NULL))) { @@ -5596,10 +5692,61 @@ } ap_destroy_mutex(start_mutex); + service_set_status(SERVICE_STOPPED); return (0); } +/* + * Send signal to a running Apache. On entry signal should contain + * either "shutdown" or "restart" + */ + +void send_signal(pool *p, char *signal) +{ + char prefix[20]; + FILE *fp; + int nread; + char *fname; + int end; + + fname = ap_server_root_relative (p, ap_pid_fname); + + fp = fopen(fname, "r"); + if (!fp) { + printf("Cannot read apache PID file %s\n", fname); + return; + } + prefix[0] = 'a'; + prefix[1] = 'p'; + + nread = fread(prefix+2, 1, sizeof(prefix)-3, fp); + if (nread == 0) { + fclose(fp); + printf("PID file %s was empty\n", fname); + return; + } + fclose(fp); + + /* Terminate the prefix string */ + end = 2 + nread - 1; + while (end > 0 && (prefix[end] == '\r' || prefix[end] == '\n')) + end--; + prefix[end + 1] = '\0'; + + setup_signal_names(prefix); + + if (!strcasecmp(signal, "shutdown")) + ap_start_shutdown(); + else if (!strcasecmp(signal, "restart")) + ap_start_restart(1); + else + printf("Unknown signal name \"%s\". Use either shutdown or restart.\n", + signal); + + return; +} + #ifdef WIN32 __declspec(dllexport) int apache_main(int argc, char *argv[]) @@ -5613,6 +5760,7 @@ int run_as_service = 1; int install = 0; int configtestonly = 0; + char *signal_to_send = NULL; common_init(); @@ -5637,7 +5785,7 @@ ap_setup_prelinked_modules(); - while ((c = getopt(argc, argv, "D:C:c:Xd:f:vVhlZ:iusSt")) != -1) { + while ((c = getopt(argc, argv, "D:C:c:Xd:f:vVhlZ:iusStk:")) != -1) { char **new; switch (c) { case 'c': @@ -5659,7 +5807,9 @@ cp = strchr(optarg, '_'); ap_assert(cp); *cp = 0; - start_mutex = ap_open_mutex(optarg); + setup_signal_names(optarg); + start_mutex = ap_open_mutex(signal_name_prefix); + ap_assert(start_mutex); child = 1; break; case 'i': @@ -5674,6 +5824,9 @@ case 'S': ap_dump_settings = 1; break; + case 'k': + signal_to_send = optarg; + break; #endif /* WIN32 */ case 'd': ap_cpystrn(ap_server_root, ap_os_canonical_filename(pconf, optarg), sizeof(ap_server_root)); @@ -5716,6 +5869,11 @@ if (configtestonly) { fprintf(stderr, "Syntax OK\n"); exit(0); + } + + if (signal_to_send) { + send_signal(pconf, signal_to_send); + exit(0); } if (!child && !ap_dump_settings && !install) {