]> granicus.if.org Git - apache/commitdiff
Take care not to call ap_start_lingering_close from the listener thread,
authorStefan Fritsch <sf@apache.org>
Mon, 27 Feb 2012 22:04:54 +0000 (22:04 +0000)
committerStefan Fritsch <sf@apache.org>
Mon, 27 Feb 2012 22:04:54 +0000 (22:04 +0000)
because it may block when flushing data to the client.

From the listener thread, do a lingering close without flushing. This is
OK because we only do this if there has been an error during write
completion or if our send buffers are empty because we are in keep-alive.

PR: 52229

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1294356 13f79535-47bb-0310-9956-ffa450edef68

CHANGES
include/httpd.h
server/mpm/event/event.c

diff --git a/CHANGES b/CHANGES
index 84a1c59d0dfd59d750a981457f04bc4a10894fbe..9b7512573b99bcfb11e929853fb4b7f53df64b7b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,9 +1,12 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.5.0
 
+  *) mpm_event: Don't do a blocking write when starting a lingering close
+     from the listener thread. PR 52229. [Stefan Fritsch]
+
   *) core: In maintainer mode, replace apr_palloc with a version that
      initializes the allocated memory with non-zero values, except if
-     AP_DEBUG_NO_ALLOC_POISON is defined.
+     AP_DEBUG_NO_ALLOC_POISON is defined. [Stefan Fritsch]
 
   *) mod_log_config: Check during config test that directories for access logs
      exist. PR 29941. [Stefan Fritsch]
index 82a3fff7ff2a099f509fd666dafb78b2ea754f5e..4ec83a2051f43626cc90af3c548d55e7cb748fd5 100644 (file)
@@ -1147,6 +1147,8 @@ struct conn_rec {
 
 /**
  * Enumeration of connection states
+ * The two states CONN_STATE_LINGER_NORMAL and CONN_STATE_LINGER_SHORT may
+ * only be set by the MPM. Use CONN_STATE_LINGER outside of the MPM.
  */
 typedef enum  {
     CONN_STATE_CHECK_REQUEST_LINE_READABLE,
@@ -1154,9 +1156,9 @@ typedef enum  {
     CONN_STATE_HANDLER,
     CONN_STATE_WRITE_COMPLETION,
     CONN_STATE_SUSPENDED,
-    CONN_STATE_LINGER,
-    CONN_STATE_LINGER_NORMAL,
-    CONN_STATE_LINGER_SHORT
+    CONN_STATE_LINGER,          /* connection may be closed with lingering */
+    CONN_STATE_LINGER_NORMAL,   /* MPM has started lingering close with normal timeout */
+    CONN_STATE_LINGER_SHORT     /* MPM has started lingering close with short timeout */
 } conn_state_e;
 
 /**
index d6c76f5a5a418d591746522402a28d54bc73b2b0..20b15e7e3915ecd22dd8c600859abe192fc9bab0 100644 (file)
@@ -755,70 +755,96 @@ static void set_signals(void)
 #endif
 }
 
+static int start_lingering_close_common(event_conn_state_t *cs)
+{
+    apr_status_t rv;
+    struct timeout_queue *q;
+    apr_socket_t *csd = cs->pfd.desc.s;
+#ifdef AP_DEBUG
+    {
+        rv = apr_socket_timeout_set(csd, 0);
+        AP_DEBUG_ASSERT(rv == APR_SUCCESS);
+    }
+#else
+    apr_socket_timeout_set(csd, 0);
+#endif
+    /*
+     * If some module requested a shortened waiting period, only wait for
+     * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain
+     * DoS attacks.
+     */
+    if (apr_table_get(cs->c->notes, "short-lingering-close")) {
+        cs->expiration_time =
+            apr_time_now() + apr_time_from_sec(SECONDS_TO_LINGER);
+        q = &short_linger_q;
+        cs->pub.state = CONN_STATE_LINGER_SHORT;
+    }
+    else {
+        cs->expiration_time =
+            apr_time_now() + apr_time_from_sec(MAX_SECS_TO_LINGER);
+        q = &linger_q;
+        cs->pub.state = CONN_STATE_LINGER_NORMAL;
+    }
+    apr_thread_mutex_lock(timeout_mutex);
+    TO_QUEUE_APPEND(*q, cs);
+    cs->pfd.reqevents = APR_POLLIN | APR_POLLHUP | APR_POLLERR;
+    rv = apr_pollset_add(event_pollset, &cs->pfd);
+    apr_thread_mutex_unlock(timeout_mutex);
+    if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf,
+                     "start_lingering_close: apr_pollset_add failure");
+        apr_thread_mutex_lock(timeout_mutex);
+        TO_QUEUE_REMOVE(*q, cs);
+        apr_thread_mutex_unlock(timeout_mutex);
+        apr_socket_close(cs->pfd.desc.s);
+        apr_pool_clear(cs->p);
+        ap_push_pool(worker_queue_info, cs->p);
+        return 0;
+    }
+    return 1;
+}
+
 /*
- * close our side of the connection
+ * Close our side of the connection, flushing data to the client first.
  * Pre-condition: cs is not in any timeout queue and not in the pollset,
  *                timeout_mutex is not locked
  * return: 0 if connection is fully closed,
  *         1 if connection is lingering
- * may be called by listener or by worker thread
+ * May only be called by worker thread.
  */
-static int start_lingering_close(event_conn_state_t *cs)
+static int start_lingering_close_blocking(event_conn_state_t *cs)
 {
-    apr_status_t rv;
-
     if (ap_start_lingering_close(cs->c)) {
         apr_pool_clear(cs->p);
         ap_push_pool(worker_queue_info, cs->p);
         return 0;
     }
-    else {
-        apr_socket_t *csd = ap_get_conn_socket(cs->c);
-        struct timeout_queue *q;
+    return start_lingering_close_common(cs);
+}
 
-#ifdef AP_DEBUG
-        {
-            rv = apr_socket_timeout_set(csd, 0);
-            AP_DEBUG_ASSERT(rv == APR_SUCCESS);
-        }
-#else
-        apr_socket_timeout_set(csd, 0);
-#endif
-        /*
-         * If some module requested a shortened waiting period, only wait for
-         * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain
-         * DoS attacks.
-         */
-        if (apr_table_get(cs->c->notes, "short-lingering-close")) {
-            cs->expiration_time =
-                apr_time_now() + apr_time_from_sec(SECONDS_TO_LINGER);
-            q = &short_linger_q;
-            cs->pub.state = CONN_STATE_LINGER_SHORT;
-        }
-        else {
-            cs->expiration_time =
-                apr_time_now() + apr_time_from_sec(MAX_SECS_TO_LINGER);
-            q = &linger_q;
-            cs->pub.state = CONN_STATE_LINGER_NORMAL;
-        }
-        apr_thread_mutex_lock(timeout_mutex);
-        TO_QUEUE_APPEND(*q, cs);
-        cs->pfd.reqevents = APR_POLLIN | APR_POLLHUP | APR_POLLERR;
-        rv = apr_pollset_add(event_pollset, &cs->pfd);
-        apr_thread_mutex_unlock(timeout_mutex);
-        if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) {
-            ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf,
-                         "start_lingering_close: apr_pollset_add failure");
-            apr_thread_mutex_lock(timeout_mutex);
-            TO_QUEUE_REMOVE(*q, cs);
-            apr_thread_mutex_unlock(timeout_mutex);
-            apr_socket_close(cs->pfd.desc.s);
-            apr_pool_clear(cs->p);
-            ap_push_pool(worker_queue_info, cs->p);
-            return 0;
-        }
+/*
+ * Close our side of the connection, NOT flushing data to the client.
+ * This should only be called if there has been an error or if we know
+ * that our send buffers are empty.
+ * Pre-condition: cs is not in any timeout queue and not in the pollset,
+ *                timeout_mutex is not locked
+ * return: 0 if connection is fully closed,
+ *         1 if connection is lingering
+ * may be called by listener thread
+ */
+static int start_lingering_close_nonblocking(event_conn_state_t *cs)
+{
+    conn_rec *c = cs->c;
+    apr_socket_t *csd = cs->pfd.desc.s;
+
+    if (c->aborted
+        || apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE) != APR_SUCCESS) {
+        apr_socket_close(csd);
+        apr_pool_clear(cs->p);
+        ap_push_pool(worker_queue_info, cs->p);
+        return 0;
     }
-    return 1;
+    return start_lingering_close_common(cs);
 }
 
 /*
@@ -983,7 +1009,7 @@ read_request:
     }
 
     if (cs->pub.state == CONN_STATE_LINGER) {
-        if (!start_lingering_close(cs))
+        if (!start_lingering_close_blocking(cs))
             return 0;
     }
     else if (cs->pub.state == CONN_STATE_CHECK_REQUEST_LINE_READABLE) {
@@ -1530,7 +1556,7 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy)
                         ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf,
                                      "pollset remove failed");
                         apr_thread_mutex_unlock(timeout_mutex);
-                        start_lingering_close(cs);
+                        start_lingering_close_nonblocking(cs);
                         break;
                     }
 
@@ -1541,7 +1567,7 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy)
                      * re-connect to a different process.
                      */
                     if (!have_idle_worker) {
-                        start_lingering_close(cs);
+                        start_lingering_close_nonblocking(cs);
                         break;
                     }
                     rc = push2worker(out_pfd, event_pollset);
@@ -1688,14 +1714,15 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy)
                              keepalive_q.count);
                 process_timeout_queue(&keepalive_q,
                                       timeout_time + ap_server_conf->keep_alive_timeout,
-                                      start_lingering_close);
+                                      start_lingering_close_nonblocking);
             }
             else {
                 process_timeout_queue(&keepalive_q, timeout_time,
-                                      start_lingering_close);
+                                      start_lingering_close_nonblocking);
             }
             /* Step 2: write completion timeouts */
-            process_timeout_queue(&write_completion_q, timeout_time, start_lingering_close);
+            process_timeout_queue(&write_completion_q, timeout_time,
+                                  start_lingering_close_nonblocking);
             /* Step 3: (normal) lingering close completion timeouts */
             process_timeout_queue(&linger_q, timeout_time, stop_lingering_close);
             /* Step 4: (short) lingering close completion timeouts */