On Wed, Jul 19, 2017 at 11:14 PM, Stefan Priebe - Profihost AG
<[email protected]> wrote:
> Am 19.07.2017 um 22:46 schrieb Yann Ylavic:
>>
>> Attached is a v2 if you feel confident enough, still ;)
>
> Thanks, yes i will.
If you managed to install v2 already you may want to ignore this new
v3, which only addresses a very unlikely error case where
lingering_count++ is missing (plus some non-functional changes, a bit
of renaming and the factorization which would have avoided this
mistake in the first place).
Otherwise, you could try this one instead.
Thanks,
Yann.
Index: server/mpm/event/event.c
===================================================================
--- server/mpm/event/event.c (revision 1802058)
+++ server/mpm/event/event.c (working copy)
@@ -180,6 +180,7 @@ static int max_workers = 0; /* Max
static int server_limit = 0; /* ServerLimit */
static int thread_limit = 0; /* ThreadLimit */
static int had_healthy_child = 0;
+static int have_idle_worker = 0;
static int dying = 0;
static int workers_may_exit = 0;
static int start_thread_may_exit = 0;
@@ -243,6 +244,8 @@ struct event_conn_state_t {
apr_pollfd_t pfd;
/** public parts of the connection state */
conn_state_t pub;
+ /** defer lingering close */
+ int defer_linger;
};
APR_RING_HEAD(timeout_head_t, event_conn_state_t);
@@ -260,11 +263,13 @@ struct timeout_queue {
* keepalive_q uses vhost's KeepAliveTimeOut
* linger_q uses MAX_SECS_TO_LINGER
* short_linger_q uses SECONDS_TO_LINGER
+ * defer_linger_q uses SECONDS_TO_LINGER
*/
static struct timeout_queue *write_completion_q,
*keepalive_q,
*linger_q,
- *short_linger_q;
+ *short_linger_q,
+ *defer_linger_q;
static volatile apr_time_t queues_next_expiry;
/* Prevent extra poll/wakeup calls for timeouts close in the future (queues
@@ -470,6 +475,10 @@ static apr_os_thread_t *listener_os_thread;
*/
static apr_socket_t **worker_sockets;
+/* Forward declare */
+static void get_worker(int blocking, int *all_busy);
+static void push_cs2worker(event_conn_state_t *cs, int *all_busy);
+
static void disable_listensocks(int process_slot)
{
int i;
@@ -699,6 +708,7 @@ static apr_status_t decrement_connection_count(voi
{
event_conn_state_t *cs = cs_;
switch (cs->pub.state) {
+ case CONN_STATE_LINGER:
case CONN_STATE_LINGER_NORMAL:
case CONN_STATE_LINGER_SHORT:
apr_atomic_dec32(&lingering_count);
@@ -731,34 +741,30 @@ static void notify_resume(event_conn_state_t *cs,
ap_run_resume_connection(cs->c, cs->r);
}
-static int start_lingering_close_common(event_conn_state_t *cs, int in_worker)
+static void prepare_lingering_close(event_conn_state_t *cs, int defer)
{
- apr_status_t rv;
- struct timeout_queue *q;
- apr_socket_t *csd = cs->pfd.desc.s;
+ apr_socket_t *csd = ap_get_conn_socket(cs->c);
#ifdef AP_DEBUG
{
- rv = apr_socket_timeout_set(csd, 0);
+ apr_status_t rv = apr_socket_timeout_set(csd, 0);
AP_DEBUG_ASSERT(rv == APR_SUCCESS);
}
#else
apr_socket_timeout_set(csd, 0);
#endif
- cs->queue_timestamp = apr_time_now();
- /*
- * 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")) {
- q = short_linger_q;
- cs->pub.state = CONN_STATE_LINGER_SHORT;
+ if (defer) {
+ cs->defer_linger = 1;
+ cs->pub.state = CONN_STATE_LINGER;
}
- else {
- q = linger_q;
- cs->pub.state = CONN_STATE_LINGER_NORMAL;
- }
apr_atomic_inc32(&lingering_count);
+}
+
+static int start_lingering_close_common(event_conn_state_t *cs,
+ struct timeout_queue *q,
+ int in_worker)
+{
+ apr_status_t rv;
+ cs->queue_timestamp = apr_time_now();
if (in_worker) {
notify_suspend(cs);
}
@@ -796,40 +802,55 @@ static void notify_resume(event_conn_state_t *cs,
*/
static int start_lingering_close_blocking(event_conn_state_t *cs)
{
- if (ap_start_lingering_close(cs->c)) {
+ struct timeout_queue *q;
+ int closed = ap_start_lingering_close(cs->c);
+ if (closed || cs->defer_linger) {
notify_suspend(cs);
+ if (!closed) {
+ apr_socket_close(ap_get_conn_socket(cs->c));
+ }
ap_push_pool(worker_queue_info, cs->p);
return 0;
}
- return start_lingering_close_common(cs, 1);
+ prepare_lingering_close(cs, 0);
+ /*
+ * 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")) {
+ q = short_linger_q;
+ cs->pub.state = CONN_STATE_LINGER_SHORT;
+ }
+ else {
+ q = linger_q;
+ cs->pub.state = CONN_STATE_LINGER_NORMAL;
+ }
+ return start_lingering_close_common(cs, q, 1);
}
/*
- * 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.
+ * Defer to a worker the flush and close of our side of the connection.
* 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
+ * May be called by listener thread
+ *
+ * The caller wants the nonblocking garantee and (still) to shutdown the
+ * connection (i.e. run the pre_close hook, send a TLS close notify alert
+ * possibly, the FIN packet, ...), quite antithetical hence the defer.
*/
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 (ap_prep_lingering_close(c)
- || c->aborted
- || ap_shutdown_conn(c, 0) != APR_SUCCESS || c->aborted
- || apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE) != APR_SUCCESS) {
- apr_socket_close(csd);
- ap_push_pool(worker_queue_info, cs->p);
- if (dying)
- ap_queue_interrupt_one(worker_queue);
- return 0;
- }
- return start_lingering_close_common(cs, 0);
+ /* Ask for writability on the socket and then let some worker do the job.
+ * This function is likely called(-back) when a timeout occurs (keepalive,
+ * write completion) so it should take a limited time to complete in any
+ * case, currently SECONDS_TO_LINGER.
+ */
+ prepare_lingering_close(cs, 1);
+ cs->pub.sense = CONN_SENSE_WANT_WRITE;
+ return start_lingering_close_common(cs, defer_linger_q, 0);
}
/*
@@ -856,6 +877,19 @@ static int stop_lingering_close(event_conn_state_t
}
/*
+ * forcibly close a lingering connection after the defer period has
+ * expired
+ * Pre-condition: cs is not in any timeout queue and not in the pollset
+ * return: irrelevant (need same prototype as start_lingering_close)
+ */
+static int defer_lingering_close(event_conn_state_t *cs)
+{
+ int all_busy = 0;
+ push_cs2worker(cs, &all_busy);
+ return 0;
+}
+
+/*
* This runs before any non-MPM cleanup code on the connection;
* if the connection is currently suspended as far as modules
* know, provide notification of resumption.
@@ -1287,16 +1321,13 @@ static apr_status_t push_timer2worker(timer_event_
}
/*
- * Pre-condition: pfd->cs is neither in pollset nor timeout queue
+ * Pre-condition: cs is neither in pollset nor timeout queue
* this function may only be called by the listener
*/
-static apr_status_t push2worker(const apr_pollfd_t * pfd,
- apr_pollset_t * pollset)
+static void push_cs2worker(event_conn_state_t *cs, int *all_busy)
{
- listener_poll_type *pt = (listener_poll_type *) pfd->client_data;
- event_conn_state_t *cs = (event_conn_state_t *) pt->baton;
apr_status_t rc;
-
+ get_worker(1, all_busy);
rc = ap_queue_push(worker_queue, cs->pfd.desc.s, cs, cs->p);
if (rc != APR_SUCCESS) {
/* trash the connection; we couldn't queue the connected
@@ -1304,28 +1335,33 @@ static apr_status_t push_timer2worker(timer_event_
*/
apr_bucket_alloc_destroy(cs->bucket_alloc);
apr_socket_close(cs->pfd.desc.s);
- ap_log_error(APLOG_MARK, APLOG_CRIT, rc,
- ap_server_conf, APLOGNO(00471) "push2worker: ap_queue_push failed");
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rc, ap_server_conf, APLOGNO(00471)
+ "push_cs2worker: ap_queue_push failed");
ap_push_pool(worker_queue_info, cs->p);
+
+ /* XXX: shouldn't we call this?
+ signal_threads(ST_GRACEFUL);
+ */
}
-
- return rc;
+ else {
+ have_idle_worker = 0;
+ }
}
/* get_worker:
- * If *have_idle_worker_p == 0, reserve a worker thread, and set
- * *have_idle_worker_p = 1.
- * If *have_idle_worker_p is already 1, will do nothing.
+ * If have_idle_worker == 0, reserve a worker thread, and set
+ * have_idle_worker = 1.
+ * If have_idle_worker is already 1, will do nothing.
* If blocking == 1, block if all workers are currently busy.
* If no worker was available immediately, will set *all_busy to 1.
* XXX: If there are no workers, we should not block immediately but
* XXX: close all keep-alive connections first.
*/
-static void get_worker(int *have_idle_worker_p, int blocking, int *all_busy)
+static void get_worker(int blocking, int *all_busy)
{
apr_status_t rc;
- if (*have_idle_worker_p) {
+ if (have_idle_worker) {
/* already reserved a worker thread - must have hit a
* transient error on a previous pass
*/
@@ -1338,7 +1374,7 @@ static apr_status_t push_timer2worker(timer_event_
rc = ap_queue_info_try_get_idler(worker_queue_info);
if (rc == APR_SUCCESS || APR_STATUS_IS_EOF(rc)) {
- *have_idle_worker_p = 1;
+ have_idle_worker = 1;
}
else if (!blocking && rc == APR_EAGAIN) {
*all_busy = 1;
@@ -1675,7 +1711,6 @@ static void * APR_THREAD_FUNC listener_thread(apr_
struct process_score *ps = ap_get_scoreboard_process(process_slot);
apr_pool_t *tpool = apr_thread_pool_get(thd);
int closed = 0, listeners_disabled = 0;
- int have_idle_worker = 0;
apr_time_t last_log;
last_log = apr_time_now();
@@ -1845,22 +1880,25 @@ static void * APR_THREAD_FUNC listener_thread(apr_
/* one of the sockets is readable */
event_conn_state_t *cs = (event_conn_state_t *) pt->baton;
struct timeout_queue *remove_from_q = cs->sc->wc_q;
- int blocking = 1;
switch (cs->pub.state) {
+ case CONN_STATE_LINGER:
+ remove_from_q = defer_linger_q;
+ goto pass_to_worker;
+
case CONN_STATE_CHECK_REQUEST_LINE_READABLE:
cs->pub.state = CONN_STATE_READ_REQUEST_LINE;
remove_from_q = cs->sc->ka_q;
- /* don't wait for a worker for a keepalive request */
- blocking = 0;
- /* FALL THROUGH */
+ goto pass_to_worker;
+
case CONN_STATE_WRITE_COMPLETION:
- get_worker(&have_idle_worker, blocking,
- &workers_were_busy);
+pass_to_worker:
apr_thread_mutex_lock(timeout_mutex);
TO_QUEUE_REMOVE(remove_from_q, cs);
rc = apr_pollset_remove(event_pollset, &cs->pfd);
apr_thread_mutex_unlock(timeout_mutex);
+ TO_QUEUE_ELEM_INIT(cs);
+
/*
* Some of the pollset backends, like KQueue or Epoll
* automagically remove the FD if the socket is closed,
@@ -1870,33 +1908,17 @@ static void * APR_THREAD_FUNC listener_thread(apr_
if (rc != APR_SUCCESS && !APR_STATUS_IS_NOTFOUND(rc)) {
ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf,
APLOGNO(03094) "pollset remove failed");
- start_lingering_close_nonblocking(cs);
- break;
+ prepare_lingering_close(cs, 1);
}
- TO_QUEUE_ELEM_INIT(cs);
- /* If we didn't get a worker immediately for a keep-alive
- * request, we close the connection, so that the client can
- * re-connect to a different process.
- */
- if (!have_idle_worker) {
- start_lingering_close_nonblocking(cs);
- break;
- }
- rc = push2worker(out_pfd, event_pollset);
- if (rc != APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_CRIT, rc,
- ap_server_conf, APLOGNO(03095)
- "push2worker failed");
- }
- else {
- have_idle_worker = 0;
- }
+ push_cs2worker(cs, &workers_were_busy);
break;
+
case CONN_STATE_LINGER_NORMAL:
case CONN_STATE_LINGER_SHORT:
process_lingering_close(cs, out_pfd);
break;
+
default:
ap_log_error(APLOG_MARK, APLOG_CRIT, rc,
ap_server_conf, APLOGNO(03096)
@@ -1964,7 +1986,7 @@ static void * APR_THREAD_FUNC listener_thread(apr_
}
apr_pool_tag(ptrans, "transaction");
- get_worker(&have_idle_worker, 1, &workers_were_busy);
+ get_worker(1, &workers_were_busy);
rc = lr->accept_func(&csd, lr, ptrans);
/* later we trash rv and rely on csd to indicate
@@ -2073,6 +2095,9 @@ static void * APR_THREAD_FUNC listener_thread(apr_
/* Step 4: (short) lingering close completion timeouts */
process_timeout_queue(short_linger_q, timeout_time,
stop_lingering_close);
+ /* Step 5: (defer) lingering close completion timeouts */
+ process_timeout_queue(defer_linger_q, timeout_time,
+ defer_lingering_close);
apr_thread_mutex_unlock(timeout_mutex);
@@ -3499,6 +3524,7 @@ static int event_pre_config(apr_pool_t * pconf, ap
max_workers = active_daemons_limit * threads_per_child;
had_healthy_child = 0;
ap_extended_status = 0;
+ have_idle_worker = 0;
return OK;
}
@@ -3524,6 +3550,8 @@ static int event_post_config(apr_pool_t *pconf, ap
NULL);
short_linger_q = TO_QUEUE_MAKE(pconf, apr_time_from_sec(SECONDS_TO_LINGER),
NULL);
+ defer_linger_q = TO_QUEUE_MAKE(pconf, apr_time_from_sec(SECONDS_TO_LINGER),
+ NULL);
for (; s; s = s->next) {
event_srv_cfg *sc = apr_pcalloc(pconf, sizeof *sc);