has anyone played with this before? I've seen it mentioned, and joe s had a patch to create a linger thread for worker back in 2004
the attached patch hasn't been seriously tested (or even seriously coded) if somebody has looked at it seriously, perhaps you can save me some time :)
Index: server/mpm/event/event.c =================================================================== --- server/mpm/event/event.c (revision 1096609) +++ server/mpm/event/event.c (working copy) @@ -147,6 +147,11 @@ #define apr_time_from_msec(x) (x * 1000) #endif +#ifndef MAX_SECS_TO_LINGER +#define MAX_SECS_TO_LINGER 30 +#endif +#define SECONDS_TO_LINGER 2 + /* * Actual definitions of config globals */ @@ -172,7 +177,8 @@ static apr_thread_mutex_t *timeout_mutex; APR_RING_HEAD(timeout_head_t, conn_state_t); -static struct timeout_head_t timeout_head, keepalive_timeout_head; +static struct timeout_head_t timeout_head, keepalive_timeout_head, + recv_fin_timeout_head; static apr_pollset_t *event_pollset; @@ -659,6 +665,7 @@ long conn_id = ID_FROM_CHILD_THREAD(my_child_num, my_thread_num); int rc; ap_sb_handle_t *sbh; + apr_status_t rv; ap_create_sb_handle(&sbh, p, my_child_num, my_thread_num); @@ -782,10 +789,45 @@ } if (cs->state == CONN_STATE_LINGER) { - ap_lingering_close(c); - apr_pool_clear(p); - ap_push_pool(worker_queue_info, p); - return 0; + if (ap_start_lingering_close(c)) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + "lingering-close finished immediately"); + apr_pool_clear(p); + ap_push_pool(worker_queue_info, p); + return 0; + } + else { + apr_socket_t *csd = ap_get_module_config(cs->c->conn_config, &core_module); + + rv = apr_socket_timeout_set(csd, 0); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + cs->state = CONN_STATE_LINGER_WAITING; + /* + * 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(c->notes, "short-lingering-close")) { + cs->expiration_time = + apr_time_now() + apr_time_from_sec(SECONDS_TO_LINGER); + } + else { + cs->expiration_time = + apr_time_now() + apr_time_from_sec(MAX_SECS_TO_LINGER); + } + apr_thread_mutex_lock(timeout_mutex); + APR_RING_INSERT_TAIL(&recv_fin_timeout_head, cs, conn_state_t, timeout_list); + apr_thread_mutex_unlock(timeout_mutex); + cs->pfd.reqevents = APR_POLLIN | APR_POLLHUP | APR_POLLERR; + rv = apr_pollset_add(event_pollset, &cs->pfd); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, + "process_socket: apr_pollset_add failure"); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + } + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + "queued a socket to lingering-close"); + } } else if (cs->state == CONN_STATE_CHECK_REQUEST_LINE_READABLE) { apr_status_t rc; @@ -888,6 +930,7 @@ APR_RING_INIT(&timeout_head, conn_state_t, timeout_list); APR_RING_INIT(&keepalive_timeout_head, conn_state_t, timeout_list); + APR_RING_INIT(&recv_fin_timeout_head, conn_state_t, timeout_list); for (lr = ap_listeners; lr != NULL; lr = lr->next) { apr_pollfd_t *pfd = apr_palloc(p, sizeof(*pfd)); @@ -1063,6 +1106,38 @@ return APR_SUCCESS; } +static int process_lingering_close(conn_state_t *cs, const apr_pollfd_t *pfd) +{ + apr_socket_t *csd = ap_get_module_config(cs->c->conn_config, &core_module); + char dummybuf[512]; + apr_size_t nbytes; + apr_status_t rv; + + /* socket is already in non-blocking state */ + do { + nbytes = sizeof(dummybuf); + rv = apr_socket_recv(csd, dummybuf, &nbytes); + ap_log_error(APLOG_MARK, APLOG_INFO, rv, ap_server_conf, + "read on lingering socket: %d/%ld", + rv, (long)nbytes); + } while (rv == APR_SUCCESS); + + if (!APR_STATUS_IS_EOF(rv)) { + return 0; + } + + rv = apr_pollset_remove(event_pollset, pfd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + rv = apr_socket_close(csd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + apr_pool_clear(cs->p); + ap_push_pool(worker_queue_info, cs->p); + + return 1; +} + static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) { timer_event_t *ep; @@ -1178,17 +1253,29 @@ apr_thread_mutex_unlock(g_timer_ring_mtx); } - while (num && get_worker(&have_idle_worker)) { + while (num) { pt = (listener_poll_type *) out_pfd->client_data; if (pt->type == PT_CSD) { /* one of the sockets is readable */ cs = (conn_state_t *) pt->baton; switch (cs->state) { case CONN_STATE_CHECK_REQUEST_LINE_READABLE: + get_worker(&have_idle_worker); cs->state = CONN_STATE_READ_REQUEST_LINE; break; case CONN_STATE_WRITE_COMPLETION: + get_worker(&have_idle_worker); break; + case CONN_STATE_LINGER_WAITING: + ap_log_error(APLOG_MARK, APLOG_INFO, 0, + ap_server_conf, + "event_loop: woke up for linger_waiting"); + if (process_lingering_close(cs, out_pfd)) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, + ap_server_conf, + "got eof on lingering socket"); + } + break; default: ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf, @@ -1202,14 +1289,16 @@ apr_thread_mutex_unlock(timeout_mutex); APR_RING_ELEM_INIT(cs, timeout_list); - rc = push2worker(out_pfd, event_pollset); - if (rc != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rc, - ap_server_conf, "push2worker failed"); + if (cs->state != CONN_STATE_LINGER_WAITING) { + rc = push2worker(out_pfd, event_pollset); + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rc, + ap_server_conf, "push2worker failed"); + } + else { + have_idle_worker = 0; + } } - else { - have_idle_worker = 0; - } } else if (pt->type == PT_ACCEPT) { /* A Listener Socket is ready for an accept() */ @@ -1349,6 +1438,29 @@ cs = APR_RING_FIRST(&timeout_head); } + /* Step 3: lingering close completion timeouts */ + + /* XXX need two lists, one per possible linger timeout value? + * if some subset use short-lingering-close, entries won't be in order + * by timeout + */ + cs = APR_RING_FIRST(&recv_fin_timeout_head); + while (!APR_RING_EMPTY(&recv_fin_timeout_head, conn_state_t, timeout_list) + && cs->expiration_time < timeout_time) { + apr_socket_t *csd; + apr_status_t rv; + + APR_RING_REMOVE(cs, timeout_list); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, + "socket reached timeout in lingering-close state"); + csd = ap_get_module_config(cs->c->conn_config, &core_module); + rv = apr_socket_close(csd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + apr_thread_mutex_unlock(timeout_mutex); + apr_pool_clear(cs->p); + ap_push_pool(worker_queue_info, cs->p); + } + apr_thread_mutex_unlock(timeout_mutex); } /* listener main loop */ @@ -1559,7 +1671,10 @@ /* Create the main pollset */ rv = apr_pollset_create(&event_pollset, - threads_per_child, + threads_per_child, /* XXX don't we need more, to handle + * connections in K-A or lingering + * close? + */ pchild, APR_POLLSET_THREADSAFE | APR_POLLSET_NOCOPY); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, Index: server/connection.c =================================================================== --- server/connection.c (revision 1096607) +++ server/connection.c (working copy) @@ -93,15 +93,13 @@ * all the response data has been sent to the client. */ #define SECONDS_TO_LINGER 2 -AP_DECLARE(void) ap_lingering_close(conn_rec *c) + +AP_DECLARE(int) ap_start_lingering_close(conn_rec *c) { - char dummybuf[512]; - apr_size_t nbytes; - apr_time_t timeup = 0; apr_socket_t *csd = ap_get_module_config(c->conn_config, &core_module); if (!csd) { - return; + return 1; } ap_update_child_status(c->sbh, SERVER_CLOSING, NULL); @@ -109,7 +107,7 @@ #ifdef NO_LINGCLOSE ap_flush_conn(c); /* just close it */ apr_socket_close(csd); - return; + return 1; #endif /* Close the connection, being careful to send out whatever is still @@ -122,7 +120,7 @@ if (c->aborted) { apr_socket_close(csd); - return; + return 1; } /* Shut down the socket for write, which will send a FIN @@ -131,6 +129,20 @@ if (apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE) != APR_SUCCESS || c->aborted) { apr_socket_close(csd); + return 1; + } + + return 0; +} + +AP_DECLARE(void) ap_lingering_close(conn_rec *c) +{ + char dummybuf[512]; + apr_size_t nbytes; + apr_time_t timeup = 0; + apr_socket_t *csd = ap_get_module_config(c->conn_config, &core_module); + + if (ap_start_lingering_close(c)) { return; } Index: include/http_connection.h =================================================================== --- include/http_connection.h (revision 1096607) +++ include/http_connection.h (working copy) @@ -71,7 +71,9 @@ */ AP_DECLARE(void) ap_lingering_close(conn_rec *c); - /* Hooks */ +AP_DECLARE(int) ap_start_lingering_close(conn_rec *c); + +/* Hooks */ /** * create_connection is a RUN_FIRST hook which allows modules to create * connections. In general, you should not install filters with the Index: include/httpd.h =================================================================== --- include/httpd.h (revision 1096624) +++ include/httpd.h (working copy) @@ -1133,7 +1133,8 @@ CONN_STATE_HANDLER, CONN_STATE_WRITE_COMPLETION, CONN_STATE_SUSPENDED, - CONN_STATE_LINGER + CONN_STATE_LINGER, + CONN_STATE_LINGER_WAITING } conn_state_e; /**