From 74c0e862984edc5585b34abce9e6decd6aa05969 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 25 Oct 2010 21:53:15 -0400 Subject: [PATCH] Avoid missed-request bug when entire http request arrives before data is flushed The trigger for starting to read the first line of a request used to be, "When data has arrived and we're looking for the first line." But that's not good enough: if the entire next request gets read into our bufev->inbuf while we're still processing the current request, we'll never see any more data arrive, and so will never process it. So the fix is to make sure that whenever we hit evhttp_send_done, we call evhttp_read_cb. We can't call it directly, though, since evhttp_send_done is reachable from the user API, and evhttp_read_cb can invoke user functions, and we don't want to force everyone to have reentrant callbacks. So, we use a deferred_cb. Found by Ivan Andropov. This is bug 3008344. --- http-internal.h | 3 +++ http.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/http-internal.h b/http-internal.h index 0a07643f..932f1c03 100644 --- a/http-internal.h +++ b/http-internal.h @@ -12,6 +12,7 @@ #include "event2/event_struct.h" #include "util-internal.h" +#include "defer-internal.h" #define HTTP_CONNECT_TIMEOUT 45 #define HTTP_WRITE_TIMEOUT 50 @@ -96,6 +97,8 @@ struct evhttp_connection { void (*closecb)(struct evhttp_connection *, void *); void *closecb_arg; + struct deferred_cb read_more_deferred_cb; + struct event_base *base; struct evdns_base *dns_base; }; diff --git a/http.c b/http.c index ce120bcc..e13a4ae6 100644 --- a/http.c +++ b/http.c @@ -919,6 +919,9 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) bufferevent_enable(evcon->bufev, EV_READ); } +#define get_deferred_queue(evcon) \ + (event_base_get_deferred_cb_queue((evcon)->base)) + /* * Gets called when more data becomes available */ @@ -929,6 +932,10 @@ evhttp_read_cb(struct bufferevent *bufev, void *arg) struct evhttp_connection *evcon = arg; struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); + /* Cancel if it's pending. */ + event_deferred_cb_cancel(get_deferred_queue(evcon), + &evcon->read_more_deferred_cb); + switch (evcon->state) { case EVCON_READING_FIRSTLINE: evhttp_read_firstline(evcon, req); @@ -958,6 +965,13 @@ evhttp_read_cb(struct bufferevent *bufev, void *arg) } } +static void +evhttp_deferred_read_cb(struct deferred_cb *cb, void *data) +{ + struct evhttp_connection *evcon = data; + evhttp_read_cb(evcon->bufev, evcon); +} + static void evhttp_write_connectioncb(struct evhttp_connection *evcon, void *arg) { @@ -1011,6 +1025,9 @@ evhttp_connection_free(struct evhttp_connection *evcon) if (evcon->bufev != NULL) bufferevent_free(evcon->bufev); + event_deferred_cb_cancel(get_deferred_queue(evcon), + &evcon->read_more_deferred_cb); + if (evcon->fd != -1) { shutdown(evcon->fd, EVUTIL_SHUT_WR); evutil_closesocket(evcon->fd); @@ -1844,6 +1861,10 @@ evhttp_connection_base_new(struct event_base *base, struct evdns_base *dnsbase, bufferevent_base_set(base, evcon->bufev); } + + event_deferred_cb_init(&evcon->read_more_deferred_cb, + evhttp_deferred_read_cb, evcon); + evcon->dns_base = dnsbase; return (evcon); @@ -2036,6 +2057,13 @@ evhttp_start_read(struct evhttp_connection *evcon) bufferevent_disable(evcon->bufev, EV_WRITE); bufferevent_enable(evcon->bufev, EV_READ); evcon->state = EVCON_READING_FIRSTLINE; + + /* If there's still data pending, process it next time through the + * loop. Don't do it now; that could get recusive. */ + if (evbuffer_get_length(bufferevent_get_input(evcon->bufev))) { + event_deferred_cb_schedule(get_deferred_queue(evcon), + &evcon->read_more_deferred_cb); + } } static void -- 2.40.0