Index: server/mpm/MPM.NAMING =================================================================== --- server/mpm/MPM.NAMING (revision 895969) +++ server/mpm/MPM.NAMING (working copy) @@ -12,3 +12,4 @@ worker ........ Multi Process model with threads. One acceptor thread, multiple worker threads. netware ....... Multi-threaded MPM for Netware + gcd ........... Experimental Grand Central Dispatch (GCD) MPM. Index: server/mpm/config.m4 =================================================================== --- server/mpm/config.m4 (revision 895969) +++ server/mpm/config.m4 (working copy) @@ -38,6 +38,30 @@ fi ]) +dnl Is GCD supported on this platform? +AC_CACHE_CHECK([whether GCD is available], [ac_cv_have_gcd], [ + AC_CHECK_LIB(dispatch, dispatch_main) + if test "$ac_cv_func_dispatch_main" != "no"; then + ac_cv_have_gcd=yes + else + ac_cv_have_gcd=no + fi +]) + +dnl Check for C blocks support. +AC_CACHE_CHECK([for C blocks support], ac_cv_blocks, [ + save_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -fblocks" + AC_TRY_COMPILE([], [ ^{ ;}; ], ac_cv_cblocks=yes, ac_cv_cblocks=no)] + CFLAGS=$save_CFLAGS +) +if test "$ac_cv_cblocks" = "yes"; then + CFLAGS="$CFLAGS -fblocks" + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + dnl See if this is a forking platform w.r.t. MPMs case $host in *mingw32* | *os2-emx*) Index: server/mpm/config2.m4 =================================================================== --- server/mpm/config2.m4 (revision 895969) +++ server/mpm/config2.m4 (working copy) @@ -1,7 +1,7 @@ AC_MSG_CHECKING(which MPM to use by default) AC_ARG_WITH(mpm, APACHE_HELP_STRING(--with-mpm=MPM,Choose the process model for Apache to use by default. - MPM={simple|event|worker|prefork|winnt} + MPM={simple|event|worker|prefork|winnt|gcd} This will be statically linked as the only available MPM unless --enable-mpms-shared is also specified. ),[ Index: server/mpm/gcd/Makefile.in =================================================================== --- server/mpm/gcd/Makefile.in (revision 0) +++ server/mpm/gcd/Makefile.in (revision 0) @@ -0,0 +1 @@ +include $(top_srcdir)/build/special.mk Property changes on: server/mpm/gcd/Makefile.in ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + FreeBSD=%H Added: svn:eol-style + native Index: server/mpm/gcd/gcd.h =================================================================== --- server/mpm/gcd/gcd.h (revision 0) +++ server/mpm/gcd/gcd.h (revision 0) @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009-2010 Apple Inc. All rights reserved. + * + * @APPLE_APACHE_LICENSE_HEADER_START@ + * + * Licensed 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. + * + * @APPLE_APACHE_LICENSE_HEADER_END@ + */ + +/* + * Variables from gcd_parent.c. + */ +extern int mpm_state; +extern volatile ap_generation_t my_generation; +extern volatile int is_graceful; +extern volatile int restart_pending; +extern volatile int shutdown_pending; +extern apr_pool_t *pconf; +extern u_int listener_count; + +/* + * Variables and functions from gcd.c. + */ +apr_status_t gcdmpm_child_run(void); +extern u_int gcdmpm_connection_limit; Property changes on: server/mpm/gcd/gcd.h ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + FreeBSD=%H Added: svn:eol-style + native Index: server/mpm/gcd/mpm_default.h =================================================================== --- server/mpm/gcd/mpm_default.h (revision 0) +++ server/mpm/gcd/mpm_default.h (revision 0) @@ -0,0 +1,3 @@ +#ifndef DEFAULT_PIDLOG +#define DEFAULT_PIDLOG DEFAULT_REL_RUNTIMEDIR "/httpd.pid" +#endif Index: server/mpm/gcd/config.m4 =================================================================== --- server/mpm/gcd/config.m4 (revision 0) +++ server/mpm/gcd/config.m4 (revision 0) @@ -0,0 +1,9 @@ +AC_MSG_CHECKING(if gcd MPM supports this platform) +if test $ac_cv_have_gcd != yes; then + AC_MSG_RESULT(no - This is not a GCD-aware platform) +elif test $ac_cv_cblocks != yes; then + AC_MSG_RESULT(no - No compiler support for blocks) +else + AC_MSG_RESULT(yes) + APACHE_MPM_SUPPORTED(gcd, yes, yes) +fi Index: server/mpm/gcd/config3.m4 =================================================================== --- server/mpm/gcd/config3.m4 (revision 0) +++ server/mpm/gcd/config3.m4 (revision 0) @@ -0,0 +1 @@ +APACHE_MPM_MODULE(gcd, $enable_mpm_gcd, gcd_parent.lo gcd.lo) Index: server/mpm/gcd/gcd_parent.c =================================================================== --- server/mpm/gcd/gcd_parent.c (revision 0) +++ server/mpm/gcd/gcd_parent.c (revision 0) @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2009-2010 Apple Inc. All rights reserved. + * + * @APPLE_APACHE_LICENSE_HEADER_START@ + * + * Licensed 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. + * + * @APPLE_APACHE_LICENSE_HEADER_END@ + */ + +/* + * This file implements the main Apache work loop and process, and is + * responsible for starting (and restarting) a child process that implements + * connection listening and processing using GCD. We are careful not to kick + * GCD (as we would be with threading) before calling fork(), so have to use + * normal UNIX signal handlers for signal processing. The body of the GCD + * MPM is found in gcd.c:gcdmpm_child_run(). + */ + +#include "apr.h" +#include "apr_portable.h" +#include "apr_signal.h" + +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_connection.h" +#include "http_vhost.h" +#include "mpm_common.h" +#include "ap_listen.h" +#include "scoreboard.h" +#include "unixd.h" + +#include "gcd.h" +#include "mpm_default.h" + +/* + * Global variables relating to MPM state and state transitions. + */ +int mpm_state = AP_MPMQ_STARTING; +volatile ap_generation_t my_generation = 0; +volatile int is_graceful; +volatile int restart_pending; +volatile int shutdown_pending; + +static pid_t child_pid; + +apr_pool_t *pconf; /* Pool for config stuff. */ +u_int listener_count; + +/* + * Handle signals in the parent; gcdmpm_run will forward to the child. + */ +static void +gcdmpm_signal_handler(int sig) +{ + + switch (sig) { + case AP_SIG_GRACEFUL: + is_graceful = 1; + case SIGHUP: + restart_pending = 1; + break; + case AP_SIG_GRACEFUL_STOP: + is_graceful = 1; + case SIGTERM: + shutdown_pending = 1; + break; + } +} + +static const char * +gcdmpm_get_name(void) +{ + + return ("gcd"); +} + +static int +gcdmpm_query(int query_code, int *result, apr_status_t *rv) +{ + + *rv = APR_SUCCESS; + switch (query_code) { + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_DYNAMIC; + break; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_STATIC; + break; + case AP_MPMQ_IS_ASYNC: + *result = 1; + break; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + case AP_MPMQ_MAX_DAEMONS: + case AP_MPMQ_MAX_DAEMON_USED: + *result = 1; + break; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = gcdmpm_connection_limit; + break; + case AP_MPMQ_MAX_THREADS: + *result = gcdmpm_connection_limit; + break; + case AP_MPMQ_MIN_SPARE_DAEMONS: + case AP_MPMQ_MAX_SPARE_DAEMONS: + case AP_MPMQ_MIN_SPARE_THREADS: + case AP_MPMQ_MAX_SPARE_THREADS: + *result = 0; + break; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = -1; + break; + case AP_MPMQ_MPM_STATE: + *result = mpm_state; + break; + case AP_MPMQ_GENERATION: + *result = my_generation; + break; + default: + *rv = APR_ENOTIMPL; + break; + } + return (OK); +} + +static int +gcdmpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) +{ + int rv; + + restart_pending = shutdown_pending = 0; + + ap_log_pid(pconf, ap_pid_fname); + + if (!is_graceful) { + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + mpm_state = AP_MPMQ_STOPPING; + return (DONE); + } + ap_scoreboard_image->global->running_generation = + my_generation; + } + + /* + * XXXGCD: Should use APR APIs for fork/wait/... + */ + child_pid = fork(); + if (child_pid < 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, errno, s, + "fork: Unable to fork new process"); + apr_sleep(apr_time_from_sec(10)); + return -1; + } + if (child_pid == 0) { + exit(gcdmpm_child_run()); + } + + apr_signal(AP_SIG_GRACEFUL, gcdmpm_signal_handler); + apr_signal(SIGHUP, gcdmpm_signal_handler); + apr_signal(AP_SIG_GRACEFUL_STOP, gcdmpm_signal_handler); + apr_signal(SIGTERM, gcdmpm_signal_handler); + + while (!restart_pending && !shutdown_pending) { + static const sigset_t ss; + (void)sigsuspend(&ss); + } + + if (shutdown_pending && !is_graceful) { + const char *pidfile; + + kill(child_pid, SIGTERM); + pidfile = ap_server_root_relative(pconf, ap_pid_fname); + if (pidfile != NULL && unlink(pidfile) == 0) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, + ap_server_conf, "removed PID file %s (pid=%ld)", + pidfile, (long)getpid()); + } + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + "caught SIGTERM, shutting down"); + return (DONE); + } else if (shutdown_pending) { + const char *pidfile; + + kill(child_pid, AP_SIG_GRACEFUL_STOP); + ap_close_listeners(); + pidfile = ap_server_root_relative(pconf, ap_pid_fname); + if (pidfile != NULL && unlink(pidfile) == 0) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, + ap_server_conf, "removed PID file %s (pid=%ld)", + pidfile, (long)getpid()); + } + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + "caught " AP_SIG_GRACEFUL_STOP_STRING + ", shutting down gracefully"); + do { + rv = waitpid(child_pid, NULL, 0); + if (rv < 0 && errno != EINTR) { + /* XXXGCD: handle? */ + } + } while (rv != child_pid); + return (DONE); + } + + ++my_generation; + ap_scoreboard_image->global->running_generation = my_generation; + + if (is_graceful) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + AP_SIG_GRACEFUL_STRING " received. " + "Doing graceful restart"); + kill(child_pid, AP_SIG_GRACEFUL); + } else { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + "SIGHUP received. Attempting restart"); + kill(child_pid, SIGHUP); + } + return (OK); +} + +static int +gcdmpm_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, + server_rec *s) +{ + + pconf = p; + listener_count = ap_setup_listeners(ap_server_conf); + if (listener_count < 0) { + ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, + "no listening sockets available, shutting down"); + return (DONE); + } + return (OK); +} + +static int +gcdmpm_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) +{ + int debug, foreground, no_detach; + apr_status_t rv; + + /* XXXRW: Possibly more should be done here. */ + + mpm_state = AP_MPMQ_STARTING; + debug = ap_exists_config_define("DEBUG"); + if (debug) { + foreground = 1; + no_detach = 0; + } else { + no_detach = ap_exists_config_define("NO_DETACH"); + foreground = ap_exists_config_define("FOREGROUND"); + } + if (!foreground) { + rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND + : APR_PROC_DETACH_DAEMONIZE); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, + "apr_proc_detach failed"); + return (HTTP_INTERNAL_SERVER_ERROR); + } + } + ap_listen_pre_config(); + ap_pid_fname = DEFAULT_PIDLOG; + return (OK); +} + +static void +gcdmpm_hooks(apr_pool_t *p) +{ + static const char *const aszSucc[] = { "core.c", NULL }; + + ap_hook_open_logs(gcdmpm_open_logs, NULL, aszSucc, + APR_HOOK_REALLY_FIRST); + ap_hook_pre_config(gcdmpm_pre_config, NULL, NULL, + APR_HOOK_REALLY_FIRST); + /* ap_hook_check_config() */ + ap_hook_mpm(gcdmpm_run, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_query(gcdmpm_query, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_mpm_get_name(gcdmpm_get_name, NULL, NULL, APR_HOOK_MIDDLE); +} + +static const command_rec gcdmpm_cmds[] = { + LISTEN_COMMANDS, + AP_GRACEFUL_SHUTDOWN_TIMEOUT_COMMAND, + { NULL } +}; + +module AP_MODULE_DECLARE_DATA mpm_gcd_module = { + MPM20_MODULE_STUFF, + NULL, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + gcdmpm_cmds, /* command apr_table_t */ + gcdmpm_hooks /* register_hooks */ +}; Property changes on: server/mpm/gcd/gcd_parent.c ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + FreeBSD=%H Added: svn:eol-style + native Index: server/mpm/gcd/gcd.c =================================================================== --- server/mpm/gcd/gcd.c (revision 0) +++ server/mpm/gcd/gcd.c (revision 0) @@ -0,0 +1,647 @@ +/*- + * Copyright (c) 2009-2010 Apple Inc. All rights reserved. + * + * @APPLE_APACHE_LICENSE_HEADER_START@ + * + * Licensed 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. + * + * @APPLE_APACHE_LICENSE_HEADER_END@ + */ + +/*- + * Experimental GCD Apache MPM. + * + * This Apache module makes use of Apple's Grand Central Dispatch (GCD) + * concurrent programming framework to manage Apache concurrency. While + * similar in design to the event MPM, this MPM associates GCD dispatch + * queues, rather than threads, with various tasks. + * + * Four types of queues are used in this module: + * + * main queue - configuration and shutdown, blocked in the steady state, + * signals queue - serial queue to await and process signals, + * listener queue - concurrent queue accepting from listen sockets, + * connection queues - one serial queue for each active connection, quantity + * limited by gcdmpm_connection_sema. + * + * Because we wish to restart across privilege drops, we do need multiple + * processes for the GCD MPM. gcd_parent.c encapsulates a parent process, + * whose single goal is to spawn (and respawn) a GCD-enabled child process + * implemented in this file. This also makes it easier to implement + * ungraceful shutdown and restart by simply calling exit() from the child. + * + * Author: Robert N. M. Watson . + */ + +/*- + * gcdmpm TODO + * + * - Because we don't bother with threads, and hence thread IDs, we don't + * properly maintain the scoreboard. This should be solved in some + * moderately scalable way. + * - Are there custom configuration parameters that could/should be + * supported? + * - Error checking for apr and GCD routines leaves something to be desired, + * but is not inconsistent with other MPMs. + * - APR routines for fork/wait/sigsuspend should be used, rather than UNIX + * interfaces in gcd_parent.c. + * - The semaphore wait for a free connection slot following accept(2) is + * non-interruptible, which could prevent the listener pool from draining + * during graceful restart or shutdown. As soon as necessary connections + * close, it will be able to continue. This may be a bug. + * - There is the (unexploited) opportunity to check for graceful + * restart/shutdown while the HTTP connection is idle. + */ + +#include + +#include "apr.h" +#include "apr_portable.h" +#include "apr_signal.h" + +#if !APR_HAS_THREADS +#error "The GCD MPM requires APR threading support, which is not present." +#endif + +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" +#include "http_connection.h" +#include "http_core.h" +#include "http_vhost.h" +#include "mpm_common.h" +#include "ap_listen.h" +#include "scoreboard.h" +#include "unixd.h" + +#include "gcd.h" + +/* + * Semaphore that the main queue will block on waiting for restart/shutdown + * events. Fired from the gcdmpm_signals_queue. + */ +static dispatch_semaphore_t gcdmpm_signalfired_sema; + +/* + * A queue to process signals, a semaphore to notify when the queue has + * drained, and dispatch sources for each signal. + */ +static dispatch_queue_t gcdmpm_signals_queue; +static dispatch_source_t ds_sighup, ds_sigterm; +static dispatch_source_t ds_sig_graceful, ds_sig_graceful_stop; + +/* + * A concurrent queue in which to perform accept(2) operations, a semaphore + * to notify when the queue has drained, and an array of dispatch sources + * (size listener_count), one for each listen socket. + */ +static dispatch_queue_t gcdmpm_listen_queue; +static dispatch_source_t *ds_listeners; + +/* + * Connection queues are created dynamically as sockets are accepted; the + * number of queues is bounded by gcdmpm_connection_sema, and each active + * connection is counted towards gcdmpm_connection_group which can then be + * waited on during a graceful shutdown. gcdmpm_connection_limit is a bound + * on the number of connection queues we will create concurrently. + * + * Each per-connection queue executes in serial, with gcdmpm_process_socket() + * bumping between connection states in response to dispatch sources set up + * in gcdmpm_newconn(). + */ +static dispatch_semaphore_t gcdmpm_connection_sema; +static dispatch_group_t gcdmpm_connection_group; +u_int gcdmpm_connection_limit = 1024; + +/* + * Each in-flight connection is described by one instance of + * gcdmpm_connection, which holds references to its associated queue, + * dispatch sources, socket, and Apache connection state. + */ +struct gcdmpm_connection { + /* GCD-related state. */ + dispatch_queue_t gc_queue; + dispatch_source_t gc_read_source; + dispatch_source_t gc_write_source; + dispatch_source_t gc_timer_source; + int gc_read_source_enabled; + int gc_write_source_enabled; + int gc_timer_source_enabled; + + /* Apache state. */ + apr_socket_t *gc_sock; + conn_state_t *gc_cs; + apr_pool_t *gc_pool; +}; + +/* + * Close a connection: tear down GCD state, tear down Apache state, and + * signal gcdmpm_connection_sema so that processing can start on a new + * connection if we're at the limit. + */ +static void +gcdmpm_closeconn(struct gcdmpm_connection *gcp) +{ + + dispatch_source_cancel(gcp->gc_read_source); + dispatch_source_cancel(gcp->gc_write_source); + dispatch_source_cancel(gcp->gc_timer_source); + + dispatch_resume(gcp->gc_read_source); + dispatch_resume(gcp->gc_write_source); + dispatch_resume(gcp->gc_timer_source); + + dispatch_release(gcp->gc_read_source); + dispatch_release(gcp->gc_write_source); + dispatch_release(gcp->gc_timer_source); + + ap_lingering_close(gcp->gc_cs->c); + dispatch_release(gcp->gc_queue); + apr_pool_destroy(gcp->gc_pool); + + dispatch_semaphore_signal(gcdmpm_connection_sema); +} + +/* + * Suspend I/O and timer dispatch sources for a connection. + */ +static void +gcdmpm_suspend_sources(struct gcdmpm_connection *gcp) +{ + + if (gcp->gc_read_source_enabled) { + dispatch_suspend(gcp->gc_read_source); + gcp->gc_read_source_enabled = 0; + } + if (gcp->gc_write_source_enabled) { + dispatch_suspend(gcp->gc_write_source); + gcp->gc_write_source_enabled = 0; + } + if (gcp->gc_timer_source_enabled) { + dispatch_suspend(gcp->gc_timer_source); + gcp->gc_timer_source_enabled = 0; + } +} + +/* + * Resume I/O dispatch sources for a connection. + */ +static void +gcdmpm_read_set(struct gcdmpm_connection *gcp) +{ + + dispatch_resume(gcp->gc_read_source); + gcp->gc_read_source_enabled = 1; +} + +static void +gcdmpm_write_set(struct gcdmpm_connection *gcp) +{ + + dispatch_resume(gcp->gc_write_source); + gcp->gc_write_source_enabled = 1; +} + +/* + * Set a timeout for a connection. + */ +static void +gcdmpm_timer_set(struct gcdmpm_connection *gcp, apr_interval_time_t it) +{ + uint64_t dt; + + /* NB: Only second granularity at this point. */ + dt = (uint64_t)apr_time_sec(it) * NSEC_PER_SEC; + dispatch_source_set_timer(gcp->gc_timer_source, + dispatch_time(DISPATCH_TIME_NOW, dt), DISPATCH_TIME_FOREVER, 0); + dispatch_resume(gcp->gc_timer_source); + gcp->gc_timer_source_enabled = 1; +} + +/* + * Setup and operate a single connection. This code is modeled on the event + * MPM's process_socket(). + */ +static void +gcdmpm_process_socket(struct gcdmpm_connection *gcp) +{ + ap_sb_handle_t *sbh; + conn_state_t *cs; + conn_rec *c; + apr_status_t rv; + + gcdmpm_suspend_sources(gcp); + + ap_create_sb_handle(&sbh, gcp->gc_pool, 0, 0); + + /* + * Allocate state for a new connection. + */ + if (gcp->gc_cs == NULL) { + cs = gcp->gc_cs = apr_pcalloc(gcp->gc_pool, + sizeof(*gcp->gc_cs)); + cs->bucket_alloc = apr_bucket_alloc_create(gcp->gc_pool); + c = cs->c = ap_run_create_connection(gcp->gc_pool, + ap_server_conf, gcp->gc_sock, 0, sbh, + cs->bucket_alloc); + c->cs = cs; + + ap_update_vhost_given_ip(c); + rv = ap_run_pre_connection(c, gcp->gc_sock); + if (rv != OK && rv != DONE) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, + ap_server_conf, + "gcdmpm_process_socket: connection aborted"); + c->aborted = 1; + } + cs->state = CONN_STATE_CHECK_REQUEST_LINE_READABLE; + } else { + cs = gcp->gc_cs; + c = cs->c; + c->sbh = sbh; + } + + /* + * If clogging input filters are present (mod_ssl), hand over control. + */ + if (c->clogging_input_filters && !c->aborted) { + ap_run_process_connection(c); + if (cs->state != CONN_STATE_SUSPENDED) { + cs->state = CONN_STATE_LINGER; + } + } + +read_request: + /* + * Ready to read on the socket. + */ + if (cs->state == CONN_STATE_READ_REQUEST_LINE) { + if (!c->aborted) { + ap_run_process_connection(c); + } else { + cs->state = CONN_STATE_LINGER; + } + } + + /* + * Need to write on the socket. + */ + if (cs->state == CONN_STATE_WRITE_COMPLETION) { + ap_filter_t *output_filter = c->output_filters; + + while (output_filter->next != NULL) { + output_filter = output_filter->next; + } + rv = output_filter->frec->filter_func.out_func(output_filter, + NULL); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rv, + ap_server_conf, + "network write failure im core output filter"); + cs->state = CONN_STATE_LINGER; + } else if (c->data_in_output_filters) { + gcdmpm_timer_set(gcp, ap_server_conf->timeout); + gcdmpm_write_set(gcp); + return; + } else if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted) { + /* Could check shutdown/restart_pending here as well? */ + cs->state = CONN_STATE_LINGER; + } else if (c->data_in_input_filters) { + cs->state = CONN_STATE_READ_REQUEST_LINE; + goto read_request; + } else { + cs->state = CONN_STATE_CHECK_REQUEST_LINE_READABLE; + } + } + + if (cs->state == CONN_STATE_LINGER) { + gcdmpm_closeconn(gcp); + } else if (cs->state == CONN_STATE_CHECK_REQUEST_LINE_READABLE) { + gcdmpm_timer_set(gcp, ap_server_conf->keep_alive_timeout); + gcdmpm_read_set(gcp); + } +} + +/* + * Allocate basic state for a new connection, configure GCD, and give it a + * a kick so that further new connection processing can be performed + * asynchronously in its per-connection queue. All further processing on the + * connection is done by gcdmpm_process_socket(). + */ +static void +gcdmpm_newconn(apr_socket_t *sock, apr_pool_t *ptrans) +{ + struct gcdmpm_connection *gcp; + int fd; + + gcp = apr_pcalloc(ptrans, sizeof(*gcp)); + gcp->gc_sock = sock; + gcp->gc_pool = ptrans; + + /* + * Create a queue from which we'll run any connection-related events. + */ + gcp->gc_queue = dispatch_queue_create("gcdmpm_work", NULL); + dispatch_suspend(gcp->gc_queue); + + /* + * Create three sources: read/write on the socket, and a timer. + */ + apr_os_sock_get(&fd, sock); + gcp->gc_read_source = dispatch_source_create( + DISPATCH_SOURCE_TYPE_READ, fd, 0, gcp->gc_queue); + dispatch_source_set_event_handler(gcp->gc_read_source, ^{ + switch (gcp->gc_cs->state) { + case CONN_STATE_CHECK_REQUEST_LINE_READABLE: + gcp->gc_cs->state = CONN_STATE_READ_REQUEST_LINE; + break; + default: + ap_log_error(APLOG_MARK, APLOG_ERR, 0, + ap_server_conf, "gcdmpm_newconn: unexpected " + "state %d", gcp->gc_cs->state); + AP_DEBUG_ASSERT(0); + } + gcdmpm_process_socket(gcp); + }); + + gcp->gc_write_source = dispatch_source_create( + DISPATCH_SOURCE_TYPE_WRITE, fd, 0, gcp->gc_queue); + dispatch_source_set_event_handler(gcp->gc_write_source, ^{ + gcdmpm_process_socket(gcp); + }); + + gcp->gc_timer_source = dispatch_source_create( + DISPATCH_SOURCE_TYPE_TIMER, 0, 0, gcp->gc_queue); + dispatch_source_set_event_handler(gcp->gc_timer_source, ^{ + gcp->gc_cs->state = CONN_STATE_LINGER; + gcdmpm_process_socket(gcp); + }); + + /* + * Continue setup asynchronously in gcdmpm_process_socket(). + */ + dispatch_group_async(gcdmpm_connection_group, gcp->gc_queue, ^{ + gcdmpm_process_socket(gcp); + }); + dispatch_resume(gcp->gc_queue); +} + +/* + * Implement connection accept for an accept-ready socket; block if all slots + * are in use. + */ +static void +gcdmpm_server_accept(ap_listen_rec *lr) +{ + apr_allocator_t *allocator; + apr_pool_t *ptrans; + apr_socket_t *sock; + apr_status_t rv; + + /* + * XXXGCD: This is a non-interruptible wait, and may block a listener + * thread waiting for a worker thread to complete during shutdown. + */ + dispatch_semaphore_wait(gcdmpm_connection_sema, DISPATCH_TIME_FOREVER); + + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + apr_pool_create_ex(&ptrans, pconf, NULL, allocator); + apr_allocator_owner_set(allocator, ptrans); + apr_pool_tag(ptrans, "transaction"); + + rv = lr->accept_func((void **)&sock, lr, ptrans); + AP_DEBUG_ASSERT(rv == APR_SUCCESS || sock == NULL); + if (sock == NULL) { + /* XXXRW: does ptrans need to be freed here? */ + return; + } + + gcdmpm_newconn(sock, ptrans); +} + +/* + * Create two queues: a signal queue, and concurrent listen queue. A + * separate signal queue prevents the concurrency bound on the listen queue + * from blocking signal delivery. + */ +static void +gcdmpm_setup_queues(void) +{ + + /* Serialized signal queue. */ + gcdmpm_signals_queue = dispatch_queue_create("gcdmpm_signals", NULL); + + /* Concurrent listener queue. */ + gcdmpm_listen_queue = dispatch_queue_create("gcdmpm_listen", NULL); + dispatch_queue_set_width(gcdmpm_listen_queue, listener_count); +} + +/* + * Set up signal handlers for SIGHUP, SIGTERM, and their GRACEFUL variants. + */ +static void +gcdmpm_setup_signals(void) +{ + + apr_signal(SIGPIPE, SIG_IGN); + + apr_signal(SIGHUP, SIG_IGN); + ds_sighup = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, + SIGHUP, 0, gcdmpm_signals_queue); + dispatch_source_set_event_handler(ds_sighup, ^{ + mpm_state = AP_MPMQ_STOPPING; + is_graceful = 0; + restart_pending = 1; + dispatch_semaphore_signal(gcdmpm_signalfired_sema); + }); + dispatch_resume(ds_sighup); + + apr_signal(AP_SIG_GRACEFUL, SIG_IGN); + ds_sig_graceful = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, + AP_SIG_GRACEFUL, 0, gcdmpm_signals_queue); + dispatch_source_set_event_handler(ds_sig_graceful, ^{ + mpm_state = AP_MPMQ_STOPPING; + is_graceful = 1; + restart_pending = 1; + dispatch_semaphore_signal(gcdmpm_signalfired_sema); + }); + dispatch_resume(ds_sig_graceful); + + apr_signal(SIGTERM, SIG_IGN); + ds_sigterm = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, + SIGTERM, 0, gcdmpm_signals_queue); + dispatch_source_set_event_handler(ds_sigterm, ^{ + mpm_state = AP_MPMQ_STOPPING; + is_graceful = 0; + shutdown_pending = 1; + dispatch_semaphore_signal(gcdmpm_signalfired_sema); + }); + dispatch_resume(ds_sigterm); + + apr_signal(AP_SIG_GRACEFUL_STOP, SIG_IGN); + ds_sig_graceful_stop = dispatch_source_create( + DISPATCH_SOURCE_TYPE_SIGNAL, AP_SIG_GRACEFUL_STOP, 0, + gcdmpm_signals_queue); + dispatch_source_set_event_handler(ds_sig_graceful_stop, ^{ + mpm_state = AP_MPMQ_STOPPING; + is_graceful = 1; + shutdown_pending = 1; + dispatch_semaphore_signal(gcdmpm_signalfired_sema); + }); + dispatch_resume(ds_sig_graceful_stop); +} + +/* + * Create connection group and semaphore. + */ +static void +gcdmpm_setup_connections(void) +{ + + gcdmpm_connection_sema = + dispatch_semaphore_create(gcdmpm_connection_limit); + gcdmpm_connection_group = dispatch_group_create(); +} + +/* + * Cancel signal registration. + */ +static void +gcdmpm_cleanup_signals(void) +{ + + dispatch_source_cancel(ds_sighup); + dispatch_release(ds_sighup); + + dispatch_source_cancel(ds_sigterm); + dispatch_release(ds_sigterm); + + dispatch_source_cancel(ds_sig_graceful); + dispatch_release(ds_sig_graceful); + + dispatch_source_cancel(ds_sig_graceful_stop); + dispatch_release(ds_sig_graceful_stop); +} + +/* + * Set up a dispatch source for each listening socket. + */ +static void +gcdmpm_setup_listeners(void) +{ + ap_listen_rec *lr; + u_int i; + int fd; + + ds_listeners = malloc(listener_count * sizeof(*ds_listeners)); + for (lr = ap_listeners, i = 0; lr != NULL; lr = lr->next, i++) { + lr->accept_func = ap_unixd_accept; + apr_socket_opt_set(lr->sd, APR_SO_NONBLOCK, 1); + apr_os_sock_get(&fd, lr->sd); + ds_listeners[i] = dispatch_source_create( + DISPATCH_SOURCE_TYPE_READ, fd, 0, gcdmpm_listen_queue); + dispatch_source_set_event_handler(ds_listeners[i], ^{ + gcdmpm_server_accept(lr); + }); + dispatch_resume(ds_listeners[i]); + } +} + +/* + * Cancel listeners. + */ +static void +gcdmpm_cleanup_listeners(void) +{ + u_int i; + + for (i = 0; i < listener_count; i++) { + dispatch_source_cancel(ds_listeners[i]); + dispatch_release(ds_listeners[i]); + } + free(ds_listeners); +} + +/* + * Wait for any in-progress connections to terminate. + */ +static void +gcdmpm_cleanup_connections(void) +{ + + dispatch_group_wait(gcdmpm_connection_group, DISPATCH_TIME_FOREVER); + dispatch_release(gcdmpm_connection_group); + dispatch_release(gcdmpm_connection_sema); +} + +/* + * Wait for listeners and the signal queue to terminate. + */ +static void +gcdmpm_cleanup_queues(void) +{ + + dispatch_sync(gcdmpm_listen_queue, ^{}); + dispatch_release(gcdmpm_listen_queue); + + dispatch_sync(gcdmpm_signals_queue, ^{}); + dispatch_release(gcdmpm_signals_queue); +} + +/* + * This is the main run loop of the GCD MPM. + */ +apr_status_t +gcdmpm_child_run(void) +{ + apr_status_t rv; + + rv = ap_run_drop_privileges(pconf, ap_server_conf); + if (rv) + return (APEXIT_CHILDFATAL); + + restart_pending = shutdown_pending = 0; + mpm_state = AP_MPMQ_RUNNING; + + while (!shutdown_pending) { + gcdmpm_signalfired_sema = dispatch_semaphore_create(0); + gcdmpm_setup_queues(); + gcdmpm_setup_signals(); + gcdmpm_setup_connections(); + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + "%s configured -- resuming normal operations", + ap_get_server_description()); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + "Server built: %s", ap_get_server_built()); + + gcdmpm_setup_listeners(); + + /* Block until restart/shutdown signal received. */ + dispatch_semaphore_wait(gcdmpm_signalfired_sema, + DISPATCH_TIME_FOREVER); + + /* On ungraceful restart/shutdown, just exit. */ + if (!is_graceful) + break; + + gcdmpm_cleanup_listeners(); + gcdmpm_cleanup_connections(); + gcdmpm_cleanup_signals(); + gcdmpm_cleanup_queues(); + dispatch_release(gcdmpm_signalfired_sema); + } + return (0); +} Property changes on: server/mpm/gcd/gcd.c ___________________________________________________________________ Added: svn:mime-type + text/plain Added: svn:keywords + FreeBSD=%H Added: svn:eol-style + native Index: modules/arch/unix/config5.m4 =================================================================== --- modules/arch/unix/config5.m4 (revision 895969) +++ modules/arch/unix/config5.m4 (working copy) @@ -4,6 +4,7 @@ if ap_mpm_is_enabled "simple" \ || ap_mpm_is_enabled "worker" \ || ap_mpm_is_enabled "event" \ + || ap_mpm_is_enabled "gcd" \ || ap_mpm_is_enabled "prefork"; then unixd_mods_enable=yes else