| | 1 | /*- |
| | 2 | * Copyright (c) 2009-2010 Apple Inc. All rights reserved. |
| | 3 | * |
| | 4 | * @APPLE_APACHE_LICENSE_HEADER_START@ |
| | 5 | * |
| | 6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| | 7 | * you may not use this file except in compliance with the License. |
| | 8 | * You may obtain a copy of the License at |
| | 9 | * |
| | 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| | 11 | * |
| | 12 | * Unless required by applicable law or agreed to in writing, software |
| | 13 | * distributed under the License is distributed on an "AS IS" BASIS, |
| | 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | 15 | * See the License for the specific language governing permissions and |
| | 16 | * limitations under the License. |
| | 17 | * |
| | 18 | * @APPLE_APACHE_LICENSE_HEADER_END@ |
| | 19 | */ |
| | 20 | |
| | 21 | /*- |
| | 22 | * Experimental GCD Apache MPM. |
| | 23 | * |
| | 24 | * This Apache module makes use of Apple's Grand Central Dispatch (GCD) |
| | 25 | * concurrent programming framework to manage Apache concurrency. While |
| | 26 | * similar in design to the event MPM, this MPM associates GCD dispatch |
| | 27 | * queues, rather than threads, with various tasks. |
| | 28 | * |
| | 29 | * Four types of queues are used in this module: |
| | 30 | * |
| | 31 | * main queue - configuration and shutdown, blocked in the steady state, |
| | 32 | * signals queue - serial queue to await and process signals, |
| | 33 | * listener queue - concurrent queue accepting from listen sockets, |
| | 34 | * connection queues - one serial queue for each active connection, quantity |
| | 35 | * limited by gcdmpm_connection_sema. |
| | 36 | * |
| | 37 | * Because we wish to restart across privilege drops, we do need multiple |
| | 38 | * processes for the GCD MPM. gcd_parent.c encapsulates a parent process, |
| | 39 | * whose single goal is to spawn (and respawn) a GCD-enabled child process |
| | 40 | * implemented in this file. This also makes it easier to implement |
| | 41 | * ungraceful shutdown and restart by simply calling exit() from the child. |
| | 42 | * |
| | 43 | * Author: Robert N. M. Watson <robert@fledge.watson.org>. |
| | 44 | */ |
| | 45 | |
| | 46 | /*- |
| | 47 | * gcdmpm TODO |
| | 48 | * |
| | 49 | * - Because we don't bother with threads, and hence thread IDs, we don't |
| | 50 | * properly maintain the scoreboard. This should be solved in some |
| | 51 | * moderately scalable way. |
| | 52 | * - Are there custom configuration parameters that could/should be |
| | 53 | * supported? |
| | 54 | * - Error checking for apr and GCD routines leaves something to be desired, |
| | 55 | * but is not inconsistent with other MPMs. |
| | 56 | * - APR routines for fork/wait/sigsuspend should be used, rather than UNIX |
| | 57 | * interfaces in gcd_parent.c. |
| | 58 | * - The semaphore wait for a free connection slot following accept(2) is |
| | 59 | * non-interruptible, which could prevent the listener pool from draining |
| | 60 | * during graceful restart or shutdown. As soon as necessary connections |
| | 61 | * close, it will be able to continue. This may be a bug. |
| | 62 | * - There is the (unexploited) opportunity to check for graceful |
| | 63 | * restart/shutdown while the HTTP connection is idle. |
| | 64 | */ |
| | 65 | |
| | 66 | #include <dispatch/dispatch.h> |
| | 67 | |
| | 68 | #include "apr.h" |
| | 69 | #include "apr_portable.h" |
| | 70 | #include "apr_signal.h" |
| | 71 | |
| | 72 | #if !APR_HAS_THREADS |
| | 73 | #error "The GCD MPM requires APR threading support, which is not present." |
| | 74 | #endif |
| | 75 | |
| | 76 | #include "httpd.h" |
| | 77 | #include "http_main.h" |
| | 78 | #include "http_log.h" |
| | 79 | #include "http_config.h" |
| | 80 | #include "http_connection.h" |
| | 81 | #include "http_core.h" |
| | 82 | #include "http_vhost.h" |
| | 83 | #include "mpm_common.h" |
| | 84 | #include "ap_listen.h" |
| | 85 | #include "scoreboard.h" |
| | 86 | #include "unixd.h" |
| | 87 | |
| | 88 | #include "gcd.h" |
| | 89 | |
| | 90 | /* |
| | 91 | * Semaphore that the main queue will block on waiting for restart/shutdown |
| | 92 | * events. Fired from the gcdmpm_signals_queue. |
| | 93 | */ |
| | 94 | static dispatch_semaphore_t gcdmpm_signalfired_sema; |
| | 95 | |
| | 96 | /* |
| | 97 | * A queue to process signals, a semaphore to notify when the queue has |
| | 98 | * drained, and dispatch sources for each signal. |
| | 99 | */ |
| | 100 | static dispatch_queue_t gcdmpm_signals_queue; |
| | 101 | static dispatch_source_t ds_sighup, ds_sigterm; |
| | 102 | static dispatch_source_t ds_sig_graceful, ds_sig_graceful_stop; |
| | 103 | |
| | 104 | /* |
| | 105 | * A concurrent queue in which to perform accept(2) operations, a semaphore |
| | 106 | * to notify when the queue has drained, and an array of dispatch sources |
| | 107 | * (size listener_count), one for each listen socket. |
| | 108 | */ |
| | 109 | static dispatch_queue_t gcdmpm_listen_queue; |
| | 110 | static dispatch_source_t *ds_listeners; |
| | 111 | |
| | 112 | /* |
| | 113 | * Connection queues are created dynamically as sockets are accepted; the |
| | 114 | * number of queues is bounded by gcdmpm_connection_sema, and each active |
| | 115 | * connection is counted towards gcdmpm_connection_group which can then be |
| | 116 | * waited on during a graceful shutdown. gcdmpm_connection_limit is a bound |
| | 117 | * on the number of connection queues we will create concurrently. |
| | 118 | * |
| | 119 | * Each per-connection queue executes in serial, with gcdmpm_process_socket() |
| | 120 | * bumping between connection states in response to dispatch sources set up |
| | 121 | * in gcdmpm_newconn(). |
| | 122 | */ |
| | 123 | static dispatch_semaphore_t gcdmpm_connection_sema; |
| | 124 | static dispatch_group_t gcdmpm_connection_group; |
| | 125 | u_int gcdmpm_connection_limit = 1024; |
| | 126 | |
| | 127 | /* |
| | 128 | * Each in-flight connection is described by one instance of |
| | 129 | * gcdmpm_connection, which holds references to its associated queue, |
| | 130 | * dispatch sources, socket, and Apache connection state. |
| | 131 | */ |
| | 132 | struct gcdmpm_connection { |
| | 133 | /* GCD-related state. */ |
| | 134 | dispatch_queue_t gc_queue; |
| | 135 | dispatch_source_t gc_read_source; |
| | 136 | dispatch_source_t gc_write_source; |
| | 137 | dispatch_source_t gc_timer_source; |
| | 138 | int gc_read_source_enabled; |
| | 139 | int gc_write_source_enabled; |
| | 140 | int gc_timer_source_enabled; |
| | 141 | |
| | 142 | /* Apache state. */ |
| | 143 | apr_socket_t *gc_sock; |
| | 144 | conn_state_t *gc_cs; |
| | 145 | apr_pool_t *gc_pool; |
| | 146 | }; |
| | 147 | |
| | 148 | /* |
| | 149 | * Close a connection: tear down GCD state, tear down Apache state, and |
| | 150 | * signal gcdmpm_connection_sema so that processing can start on a new |
| | 151 | * connection if we're at the limit. |
| | 152 | */ |
| | 153 | static void |
| | 154 | gcdmpm_closeconn(struct gcdmpm_connection *gcp) |
| | 155 | { |
| | 156 | |
| | 157 | dispatch_source_cancel(gcp->gc_read_source); |
| | 158 | dispatch_source_cancel(gcp->gc_write_source); |
| | 159 | dispatch_source_cancel(gcp->gc_timer_source); |
| | 160 | |
| | 161 | dispatch_resume(gcp->gc_read_source); |
| | 162 | dispatch_resume(gcp->gc_write_source); |
| | 163 | dispatch_resume(gcp->gc_timer_source); |
| | 164 | |
| | 165 | dispatch_release(gcp->gc_read_source); |
| | 166 | dispatch_release(gcp->gc_write_source); |
| | 167 | dispatch_release(gcp->gc_timer_source); |
| | 168 | |
| | 169 | ap_lingering_close(gcp->gc_cs->c); |
| | 170 | dispatch_release(gcp->gc_queue); |
| | 171 | apr_pool_destroy(gcp->gc_pool); |
| | 172 | |
| | 173 | dispatch_semaphore_signal(gcdmpm_connection_sema); |
| | 174 | } |
| | 175 | |
| | 176 | /* |
| | 177 | * Suspend I/O and timer dispatch sources for a connection. |
| | 178 | */ |
| | 179 | static void |
| | 180 | gcdmpm_suspend_sources(struct gcdmpm_connection *gcp) |
| | 181 | { |
| | 182 | |
| | 183 | if (gcp->gc_read_source_enabled) { |
| | 184 | dispatch_suspend(gcp->gc_read_source); |
| | 185 | gcp->gc_read_source_enabled = 0; |
| | 186 | } |
| | 187 | if (gcp->gc_write_source_enabled) { |
| | 188 | dispatch_suspend(gcp->gc_write_source); |
| | 189 | gcp->gc_write_source_enabled = 0; |
| | 190 | } |
| | 191 | if (gcp->gc_timer_source_enabled) { |
| | 192 | dispatch_suspend(gcp->gc_timer_source); |
| | 193 | gcp->gc_timer_source_enabled = 0; |
| | 194 | } |
| | 195 | } |
| | 196 | |
| | 197 | /* |
| | 198 | * Resume I/O dispatch sources for a connection. |
| | 199 | */ |
| | 200 | static void |
| | 201 | gcdmpm_read_set(struct gcdmpm_connection *gcp) |
| | 202 | { |
| | 203 | |
| | 204 | dispatch_resume(gcp->gc_read_source); |
| | 205 | gcp->gc_read_source_enabled = 1; |
| | 206 | } |
| | 207 | |
| | 208 | static void |
| | 209 | gcdmpm_write_set(struct gcdmpm_connection *gcp) |
| | 210 | { |
| | 211 | |
| | 212 | dispatch_resume(gcp->gc_write_source); |
| | 213 | gcp->gc_write_source_enabled = 1; |
| | 214 | } |
| | 215 | |
| | 216 | /* |
| | 217 | * Set a timeout for a connection. |
| | 218 | */ |
| | 219 | static void |
| | 220 | gcdmpm_timer_set(struct gcdmpm_connection *gcp, apr_interval_time_t it) |
| | 221 | { |
| | 222 | uint64_t dt; |
| | 223 | |
| | 224 | /* NB: Only second granularity at this point. */ |
| | 225 | dt = (uint64_t)apr_time_sec(it) * NSEC_PER_SEC; |
| | 226 | dispatch_source_set_timer(gcp->gc_timer_source, |
| | 227 | dispatch_time(DISPATCH_TIME_NOW, dt), DISPATCH_TIME_FOREVER, 0); |
| | 228 | dispatch_resume(gcp->gc_timer_source); |
| | 229 | gcp->gc_timer_source_enabled = 1; |
| | 230 | } |
| | 231 | |
| | 232 | /* |
| | 233 | * Setup and operate a single connection. This code is modeled on the event |
| | 234 | * MPM's process_socket(). |
| | 235 | */ |
| | 236 | static void |
| | 237 | gcdmpm_process_socket(struct gcdmpm_connection *gcp) |
| | 238 | { |
| | 239 | ap_sb_handle_t *sbh; |
| | 240 | conn_state_t *cs; |
| | 241 | conn_rec *c; |
| | 242 | apr_status_t rv; |
| | 243 | |
| | 244 | gcdmpm_suspend_sources(gcp); |
| | 245 | |
| | 246 | ap_create_sb_handle(&sbh, gcp->gc_pool, 0, 0); |
| | 247 | |
| | 248 | /* |
| | 249 | * Allocate state for a new connection. |
| | 250 | */ |
| | 251 | if (gcp->gc_cs == NULL) { |
| | 252 | cs = gcp->gc_cs = apr_pcalloc(gcp->gc_pool, |
| | 253 | sizeof(*gcp->gc_cs)); |
| | 254 | cs->bucket_alloc = apr_bucket_alloc_create(gcp->gc_pool); |
| | 255 | c = cs->c = ap_run_create_connection(gcp->gc_pool, |
| | 256 | ap_server_conf, gcp->gc_sock, 0, sbh, |
| | 257 | cs->bucket_alloc); |
| | 258 | c->cs = cs; |
| | 259 | |
| | 260 | ap_update_vhost_given_ip(c); |
| | 261 | rv = ap_run_pre_connection(c, gcp->gc_sock); |
| | 262 | if (rv != OK && rv != DONE) { |
| | 263 | ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, |
| | 264 | ap_server_conf, |
| | 265 | "gcdmpm_process_socket: connection aborted"); |
| | 266 | c->aborted = 1; |
| | 267 | } |
| | 268 | cs->state = CONN_STATE_CHECK_REQUEST_LINE_READABLE; |
| | 269 | } else { |
| | 270 | cs = gcp->gc_cs; |
| | 271 | c = cs->c; |
| | 272 | c->sbh = sbh; |
| | 273 | } |
| | 274 | |
| | 275 | /* |
| | 276 | * If clogging input filters are present (mod_ssl), hand over control. |
| | 277 | */ |
| | 278 | if (c->clogging_input_filters && !c->aborted) { |
| | 279 | ap_run_process_connection(c); |
| | 280 | if (cs->state != CONN_STATE_SUSPENDED) { |
| | 281 | cs->state = CONN_STATE_LINGER; |
| | 282 | } |
| | 283 | } |
| | 284 | |
| | 285 | read_request: |
| | 286 | /* |
| | 287 | * Ready to read on the socket. |
| | 288 | */ |
| | 289 | if (cs->state == CONN_STATE_READ_REQUEST_LINE) { |
| | 290 | if (!c->aborted) { |
| | 291 | ap_run_process_connection(c); |
| | 292 | } else { |
| | 293 | cs->state = CONN_STATE_LINGER; |
| | 294 | } |
| | 295 | } |
| | 296 | |
| | 297 | /* |
| | 298 | * Need to write on the socket. |
| | 299 | */ |
| | 300 | if (cs->state == CONN_STATE_WRITE_COMPLETION) { |
| | 301 | ap_filter_t *output_filter = c->output_filters; |
| | 302 | |
| | 303 | while (output_filter->next != NULL) { |
| | 304 | output_filter = output_filter->next; |
| | 305 | } |
| | 306 | rv = output_filter->frec->filter_func.out_func(output_filter, |
| | 307 | NULL); |
| | 308 | if (rv != APR_SUCCESS) { |
| | 309 | ap_log_error(APLOG_MARK, APLOG_WARNING, rv, |
| | 310 | ap_server_conf, |
| | 311 | "network write failure im core output filter"); |
| | 312 | cs->state = CONN_STATE_LINGER; |
| | 313 | } else if (c->data_in_output_filters) { |
| | 314 | gcdmpm_timer_set(gcp, ap_server_conf->timeout); |
| | 315 | gcdmpm_write_set(gcp); |
| | 316 | return; |
| | 317 | } else if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted) { |
| | 318 | /* Could check shutdown/restart_pending here as well? */ |
| | 319 | cs->state = CONN_STATE_LINGER; |
| | 320 | } else if (c->data_in_input_filters) { |
| | 321 | cs->state = CONN_STATE_READ_REQUEST_LINE; |
| | 322 | goto read_request; |
| | 323 | } else { |
| | 324 | cs->state = CONN_STATE_CHECK_REQUEST_LINE_READABLE; |
| | 325 | } |
| | 326 | } |
| | 327 | |
| | 328 | if (cs->state == CONN_STATE_LINGER) { |
| | 329 | gcdmpm_closeconn(gcp); |
| | 330 | } else if (cs->state == CONN_STATE_CHECK_REQUEST_LINE_READABLE) { |
| | 331 | gcdmpm_timer_set(gcp, ap_server_conf->keep_alive_timeout); |
| | 332 | gcdmpm_read_set(gcp); |
| | 333 | } |
| | 334 | } |
| | 335 | |
| | 336 | /* |
| | 337 | * Allocate basic state for a new connection, configure GCD, and give it a |
| | 338 | * a kick so that further new connection processing can be performed |
| | 339 | * asynchronously in its per-connection queue. All further processing on the |
| | 340 | * connection is done by gcdmpm_process_socket(). |
| | 341 | */ |
| | 342 | static void |
| | 343 | gcdmpm_newconn(apr_socket_t *sock, apr_pool_t *ptrans) |
| | 344 | { |
| | 345 | struct gcdmpm_connection *gcp; |
| | 346 | int fd; |
| | 347 | |
| | 348 | gcp = apr_pcalloc(ptrans, sizeof(*gcp)); |
| | 349 | gcp->gc_sock = sock; |
| | 350 | gcp->gc_pool = ptrans; |
| | 351 | |
| | 352 | /* |
| | 353 | * Create a queue from which we'll run any connection-related events. |
| | 354 | */ |
| | 355 | gcp->gc_queue = dispatch_queue_create("gcdmpm_work", NULL); |
| | 356 | dispatch_suspend(gcp->gc_queue); |
| | 357 | |
| | 358 | /* |
| | 359 | * Create three sources: read/write on the socket, and a timer. |
| | 360 | */ |
| | 361 | apr_os_sock_get(&fd, sock); |
| | 362 | gcp->gc_read_source = dispatch_source_create( |
| | 363 | DISPATCH_SOURCE_TYPE_READ, fd, 0, gcp->gc_queue); |
| | 364 | dispatch_source_set_event_handler(gcp->gc_read_source, ^{ |
| | 365 | switch (gcp->gc_cs->state) { |
| | 366 | case CONN_STATE_CHECK_REQUEST_LINE_READABLE: |
| | 367 | gcp->gc_cs->state = CONN_STATE_READ_REQUEST_LINE; |
| | 368 | break; |
| | 369 | default: |
| | 370 | ap_log_error(APLOG_MARK, APLOG_ERR, 0, |
| | 371 | ap_server_conf, "gcdmpm_newconn: unexpected " |
| | 372 | "state %d", gcp->gc_cs->state); |
| | 373 | AP_DEBUG_ASSERT(0); |
| | 374 | } |
| | 375 | gcdmpm_process_socket(gcp); |
| | 376 | }); |
| | 377 | |
| | 378 | gcp->gc_write_source = dispatch_source_create( |
| | 379 | DISPATCH_SOURCE_TYPE_WRITE, fd, 0, gcp->gc_queue); |
| | 380 | dispatch_source_set_event_handler(gcp->gc_write_source, ^{ |
| | 381 | gcdmpm_process_socket(gcp); |
| | 382 | }); |
| | 383 | |
| | 384 | gcp->gc_timer_source = dispatch_source_create( |
| | 385 | DISPATCH_SOURCE_TYPE_TIMER, 0, 0, gcp->gc_queue); |
| | 386 | dispatch_source_set_event_handler(gcp->gc_timer_source, ^{ |
| | 387 | gcp->gc_cs->state = CONN_STATE_LINGER; |
| | 388 | gcdmpm_process_socket(gcp); |
| | 389 | }); |
| | 390 | |
| | 391 | /* |
| | 392 | * Continue setup asynchronously in gcdmpm_process_socket(). |
| | 393 | */ |
| | 394 | dispatch_group_async(gcdmpm_connection_group, gcp->gc_queue, ^{ |
| | 395 | gcdmpm_process_socket(gcp); |
| | 396 | }); |
| | 397 | dispatch_resume(gcp->gc_queue); |
| | 398 | } |
| | 399 | |
| | 400 | /* |
| | 401 | * Implement connection accept for an accept-ready socket; block if all slots |
| | 402 | * are in use. |
| | 403 | */ |
| | 404 | static void |
| | 405 | gcdmpm_server_accept(ap_listen_rec *lr) |
| | 406 | { |
| | 407 | apr_allocator_t *allocator; |
| | 408 | apr_pool_t *ptrans; |
| | 409 | apr_socket_t *sock; |
| | 410 | apr_status_t rv; |
| | 411 | |
| | 412 | /* |
| | 413 | * XXXGCD: This is a non-interruptible wait, and may block a listener |
| | 414 | * thread waiting for a worker thread to complete during shutdown. |
| | 415 | */ |
| | 416 | dispatch_semaphore_wait(gcdmpm_connection_sema, DISPATCH_TIME_FOREVER); |
| | 417 | |
| | 418 | apr_allocator_create(&allocator); |
| | 419 | apr_allocator_max_free_set(allocator, ap_max_mem_free); |
| | 420 | apr_pool_create_ex(&ptrans, pconf, NULL, allocator); |
| | 421 | apr_allocator_owner_set(allocator, ptrans); |
| | 422 | apr_pool_tag(ptrans, "transaction"); |
| | 423 | |
| | 424 | rv = lr->accept_func((void **)&sock, lr, ptrans); |
| | 425 | AP_DEBUG_ASSERT(rv == APR_SUCCESS || sock == NULL); |
| | 426 | if (sock == NULL) { |
| | 427 | /* XXXRW: does ptrans need to be freed here? */ |
| | 428 | return; |
| | 429 | } |
| | 430 | |
| | 431 | gcdmpm_newconn(sock, ptrans); |
| | 432 | } |
| | 433 | |
| | 434 | /* |
| | 435 | * Create two queues: a signal queue, and concurrent listen queue. A |
| | 436 | * separate signal queue prevents the concurrency bound on the listen queue |
| | 437 | * from blocking signal delivery. |
| | 438 | */ |
| | 439 | static void |
| | 440 | gcdmpm_setup_queues(void) |
| | 441 | { |
| | 442 | |
| | 443 | /* Serialized signal queue. */ |
| | 444 | gcdmpm_signals_queue = dispatch_queue_create("gcdmpm_signals", NULL); |
| | 445 | |
| | 446 | /* Concurrent listener queue. */ |
| | 447 | gcdmpm_listen_queue = dispatch_queue_create("gcdmpm_listen", NULL); |
| | 448 | dispatch_queue_set_width(gcdmpm_listen_queue, listener_count); |
| | 449 | } |
| | 450 | |
| | 451 | /* |
| | 452 | * Set up signal handlers for SIGHUP, SIGTERM, and their GRACEFUL variants. |
| | 453 | */ |
| | 454 | static void |
| | 455 | gcdmpm_setup_signals(void) |
| | 456 | { |
| | 457 | |
| | 458 | apr_signal(SIGPIPE, SIG_IGN); |
| | 459 | |
| | 460 | apr_signal(SIGHUP, SIG_IGN); |
| | 461 | ds_sighup = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, |
| | 462 | SIGHUP, 0, gcdmpm_signals_queue); |
| | 463 | dispatch_source_set_event_handler(ds_sighup, ^{ |
| | 464 | mpm_state = AP_MPMQ_STOPPING; |
| | 465 | is_graceful = 0; |
| | 466 | restart_pending = 1; |
| | 467 | dispatch_semaphore_signal(gcdmpm_signalfired_sema); |
| | 468 | }); |
| | 469 | dispatch_resume(ds_sighup); |
| | 470 | |
| | 471 | apr_signal(AP_SIG_GRACEFUL, SIG_IGN); |
| | 472 | ds_sig_graceful = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, |
| | 473 | AP_SIG_GRACEFUL, 0, gcdmpm_signals_queue); |
| | 474 | dispatch_source_set_event_handler(ds_sig_graceful, ^{ |
| | 475 | mpm_state = AP_MPMQ_STOPPING; |
| | 476 | is_graceful = 1; |
| | 477 | restart_pending = 1; |
| | 478 | dispatch_semaphore_signal(gcdmpm_signalfired_sema); |
| | 479 | }); |
| | 480 | dispatch_resume(ds_sig_graceful); |
| | 481 | |
| | 482 | apr_signal(SIGTERM, SIG_IGN); |
| | 483 | ds_sigterm = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, |
| | 484 | SIGTERM, 0, gcdmpm_signals_queue); |
| | 485 | dispatch_source_set_event_handler(ds_sigterm, ^{ |
| | 486 | mpm_state = AP_MPMQ_STOPPING; |
| | 487 | is_graceful = 0; |
| | 488 | shutdown_pending = 1; |
| | 489 | dispatch_semaphore_signal(gcdmpm_signalfired_sema); |
| | 490 | }); |
| | 491 | dispatch_resume(ds_sigterm); |
| | 492 | |
| | 493 | apr_signal(AP_SIG_GRACEFUL_STOP, SIG_IGN); |
| | 494 | ds_sig_graceful_stop = dispatch_source_create( |
| | 495 | DISPATCH_SOURCE_TYPE_SIGNAL, AP_SIG_GRACEFUL_STOP, 0, |
| | 496 | gcdmpm_signals_queue); |
| | 497 | dispatch_source_set_event_handler(ds_sig_graceful_stop, ^{ |
| | 498 | mpm_state = AP_MPMQ_STOPPING; |
| | 499 | is_graceful = 1; |
| | 500 | shutdown_pending = 1; |
| | 501 | dispatch_semaphore_signal(gcdmpm_signalfired_sema); |
| | 502 | }); |
| | 503 | dispatch_resume(ds_sig_graceful_stop); |
| | 504 | } |
| | 505 | |
| | 506 | /* |
| | 507 | * Create connection group and semaphore. |
| | 508 | */ |
| | 509 | static void |
| | 510 | gcdmpm_setup_connections(void) |
| | 511 | { |
| | 512 | |
| | 513 | gcdmpm_connection_sema = |
| | 514 | dispatch_semaphore_create(gcdmpm_connection_limit); |
| | 515 | gcdmpm_connection_group = dispatch_group_create(); |
| | 516 | } |
| | 517 | |
| | 518 | /* |
| | 519 | * Cancel signal registration. |
| | 520 | */ |
| | 521 | static void |
| | 522 | gcdmpm_cleanup_signals(void) |
| | 523 | { |
| | 524 | |
| | 525 | dispatch_source_cancel(ds_sighup); |
| | 526 | dispatch_release(ds_sighup); |
| | 527 | |
| | 528 | dispatch_source_cancel(ds_sigterm); |
| | 529 | dispatch_release(ds_sigterm); |
| | 530 | |
| | 531 | dispatch_source_cancel(ds_sig_graceful); |
| | 532 | dispatch_release(ds_sig_graceful); |
| | 533 | |
| | 534 | dispatch_source_cancel(ds_sig_graceful_stop); |
| | 535 | dispatch_release(ds_sig_graceful_stop); |
| | 536 | } |
| | 537 | |
| | 538 | /* |
| | 539 | * Set up a dispatch source for each listening socket. |
| | 540 | */ |
| | 541 | static void |
| | 542 | gcdmpm_setup_listeners(void) |
| | 543 | { |
| | 544 | ap_listen_rec *lr; |
| | 545 | u_int i; |
| | 546 | int fd; |
| | 547 | |
| | 548 | ds_listeners = malloc(listener_count * sizeof(*ds_listeners)); |
| | 549 | for (lr = ap_listeners, i = 0; lr != NULL; lr = lr->next, i++) { |
| | 550 | lr->accept_func = ap_unixd_accept; |
| | 551 | apr_socket_opt_set(lr->sd, APR_SO_NONBLOCK, 1); |
| | 552 | apr_os_sock_get(&fd, lr->sd); |
| | 553 | ds_listeners[i] = dispatch_source_create( |
| | 554 | DISPATCH_SOURCE_TYPE_READ, fd, 0, gcdmpm_listen_queue); |
| | 555 | dispatch_source_set_event_handler(ds_listeners[i], ^{ |
| | 556 | gcdmpm_server_accept(lr); |
| | 557 | }); |
| | 558 | dispatch_resume(ds_listeners[i]); |
| | 559 | } |
| | 560 | } |
| | 561 | |
| | 562 | /* |
| | 563 | * Cancel listeners. |
| | 564 | */ |
| | 565 | static void |
| | 566 | gcdmpm_cleanup_listeners(void) |
| | 567 | { |
| | 568 | u_int i; |
| | 569 | |
| | 570 | for (i = 0; i < listener_count; i++) { |
| | 571 | dispatch_source_cancel(ds_listeners[i]); |
| | 572 | dispatch_release(ds_listeners[i]); |
| | 573 | } |
| | 574 | free(ds_listeners); |
| | 575 | } |
| | 576 | |
| | 577 | /* |
| | 578 | * Wait for any in-progress connections to terminate. |
| | 579 | */ |
| | 580 | static void |
| | 581 | gcdmpm_cleanup_connections(void) |
| | 582 | { |
| | 583 | |
| | 584 | dispatch_group_wait(gcdmpm_connection_group, DISPATCH_TIME_FOREVER); |
| | 585 | dispatch_release(gcdmpm_connection_group); |
| | 586 | dispatch_release(gcdmpm_connection_sema); |
| | 587 | } |
| | 588 | |
| | 589 | /* |
| | 590 | * Wait for listeners and the signal queue to terminate. |
| | 591 | */ |
| | 592 | static void |
| | 593 | gcdmpm_cleanup_queues(void) |
| | 594 | { |
| | 595 | |
| | 596 | dispatch_sync(gcdmpm_listen_queue, ^{}); |
| | 597 | dispatch_release(gcdmpm_listen_queue); |
| | 598 | |
| | 599 | dispatch_sync(gcdmpm_signals_queue, ^{}); |
| | 600 | dispatch_release(gcdmpm_signals_queue); |
| | 601 | } |
| | 602 | |
| | 603 | /* |
| | 604 | * This is the main run loop of the GCD MPM. |
| | 605 | */ |
| | 606 | apr_status_t |
| | 607 | gcdmpm_child_run(void) |
| | 608 | { |
| | 609 | apr_status_t rv; |
| | 610 | |
| | 611 | rv = ap_run_drop_privileges(pconf, ap_server_conf); |
| | 612 | if (rv) |
| | 613 | return (APEXIT_CHILDFATAL); |
| | 614 | |
| | 615 | restart_pending = shutdown_pending = 0; |
| | 616 | mpm_state = AP_MPMQ_RUNNING; |
| | 617 | |
| | 618 | while (!shutdown_pending) { |
| | 619 | gcdmpm_signalfired_sema = dispatch_semaphore_create(0); |
| | 620 | gcdmpm_setup_queues(); |
| | 621 | gcdmpm_setup_signals(); |
| | 622 | gcdmpm_setup_connections(); |
| | 623 | |
| | 624 | ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, |
| | 625 | "%s configured -- resuming normal operations", |
| | 626 | ap_get_server_description()); |
| | 627 | ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, |
| | 628 | "Server built: %s", ap_get_server_built()); |
| | 629 | |
| | 630 | gcdmpm_setup_listeners(); |
| | 631 | |
| | 632 | /* Block until restart/shutdown signal received. */ |
| | 633 | dispatch_semaphore_wait(gcdmpm_signalfired_sema, |
| | 634 | DISPATCH_TIME_FOREVER); |
| | 635 | |
| | 636 | /* On ungraceful restart/shutdown, just exit. */ |
| | 637 | if (!is_graceful) |
| | 638 | break; |
| | 639 | |
| | 640 | gcdmpm_cleanup_listeners(); |
| | 641 | gcdmpm_cleanup_connections(); |
| | 642 | gcdmpm_cleanup_signals(); |
| | 643 | gcdmpm_cleanup_queues(); |
| | 644 | dispatch_release(gcdmpm_signalfired_sema); |
| | 645 | } |
| | 646 | return (0); |
| | 647 | } |