Nick Mathewson wrote:

1. I added BEV_OPT_THREADSAFE in evhttp_connection_base_new (evcon->bufev =

We won't want to do this unconditionally: not everybody will want the
overhead of locking in an evhttp that isn't going to be used in
multiple threads.
Maybe evthread_use_pthreads() could enable this behavior?
There could be a more complicated way to change it, but I think keeping these things roughly in sync makes sense.
First thing to do is, in the future, please use the "unified diff"
format that "diff -u" generates?  It's lots easier to read.
Oops. below --
As for the patch itself: there's going to be more work needed to make
evhttp threadsafe.  It looks like most of what you're doing so far is
to make evhttp's use of bufferevents threadsafe.  That's a necessary
component, but we'll also need to make sure that the evhttp structures
themselves are safe to use from multiple threads at a time.  That
means at a minimum getting them locks to prevent concurrent
modifications.  We'll also need to figure out the correct behavior wrt
preventing deadlocks between http locking and bufferevent locking --
possibly by the expedient of just having the bufferevent and its
evhttp objects share a lock.
I'm parsing the request in the network thread, so I think this is all working because I assume that what's in evhttp_connection & evhttp is *read-only* by the time I'm in EVCON_WRITING.

If this is true, everything but the bufferevent could be lock-free if it were formalized a bit more (i.e. a read-only/const portion and a writable portion of the evhttp structure?), so this might actually avoid unnecessary locking. There certainly may be writes to these structures in other code paths that I didn't check for, so it might not be so easy. Certainly my "sloppy" approach to evhttp doesn't generalize to dealing with the request earlier (e.g., a multi-CPU header parser), but that would require a new event base per thread as well.

mike

--- http.c    2011-04-27 14:07:21.000000000 -0700
+++ /home/mike/libevent/http.c    2011-05-21 23:23:14.813264896 -0700
@@ -369,8 +369,6 @@
    evcon->cb = cb;
    evcon->cb_arg = arg;

-    bufferevent_enable(evcon->bufev, EV_WRITE);
-
    /* Disable the read callback: we don't actually care about data;
     * we only care about close detection.  (We don't disable reading,
     * since we *do* want to learn about any close events.) */
@@ -379,6 +377,9 @@
        evhttp_write_cb,
        evhttp_error_cb,
        evcon);
+
+    // mbh: moved after setcb, so we don't race with the network thread:
+    bufferevent_enable(evcon->bufev, EV_WRITE);
}

static void
@@ -391,7 +392,6 @@
evhttp_send_continue(struct evhttp_connection *evcon,
            struct evhttp_request *req)
{
-    bufferevent_enable(evcon->bufev, EV_WRITE);
    evbuffer_add_printf(bufferevent_get_output(evcon->bufev),
            "HTTP/%d.%d 100 Continue\r\n\r\n",
            req->major, req->minor);
@@ -402,6 +402,9 @@
        evhttp_write_cb,
        evhttp_error_cb,
        evcon);
+
+    // mbh: moved after, to avoid races:
+    bufferevent_enable(evcon->bufev, EV_WRITE);
}

/** Helper: returns true iff evconn is in any connected state. */
@@ -995,6 +998,7 @@
static void
evhttp_read_cb(struct bufferevent *bufev, void *arg)
{
+
    struct evhttp_connection *evcon = arg;
    struct evhttp_request *req = TAILQ_FIRST(&evcon->requests);

@@ -2037,6 +2041,7 @@
        goto error;
    }

+#if 0
    if ((evcon->bufev = bufferevent_new(-1,
            evhttp_read_cb,
            evhttp_write_cb,
@@ -2044,6 +2049,19 @@
        event_warn("%s: bufferevent_new failed", __func__);
        goto error;
    }
+#else
+    // threadsafe, using newer API
+    evcon->bufev = bufferevent_socket_new(NULL, -1, BEV_OPT_THREADSAFE);
+
+    if (evcon->bufev == NULL) {
+        printf("bufferevent_socket_net failed\n");
+        event_warn("%s: bufferevent_new failed", __func__);
+        goto error;
+    }
+
+ bufferevent_setcb(evcon->bufev, evhttp_read_cb, evhttp_write_cb, evhttp_error_cb, evcon);
+
+#endif

    evcon->state = EVCON_DISCONNECTED;
    TAILQ_INIT(&evcon->requests);
@@ -2142,8 +2160,8 @@
        evcon);
    bufferevent_settimeout(evcon->bufev, 0,
        evcon->timeout != -1 ? evcon->timeout : HTTP_CONNECT_TIMEOUT);
-    /* make sure that we get a write callback */
-    bufferevent_enable(evcon->bufev, EV_WRITE);
+
+    // mbh: bufferevent_enable was here

    if (bufferevent_socket_connect_hostname(evcon->bufev, evcon->dns_base,
        AF_UNSPEC, evcon->address, evcon->port) < 0) {
@@ -2159,6 +2177,10 @@

    evcon->state = EVCON_CONNECTING;

+    // mbh: moved to avoid races
+    /* make sure that we get a write callback */
+    bufferevent_enable(evcon->bufev, EV_WRITE);
+
    return (0);
}

@@ -2367,7 +2389,15 @@
{
    evhttp_response_code(req, code, reason);

+    if (req->evcon && req->evcon->bufev) {
+        struct bufferevent *bev = req->evcon->bufev;
+
+        bufferevent_incref(bev);
    evhttp_send(req, databuf);
+
+        // note: req may be freed, but our incref is still around:
+        bufferevent_decref(bev);
+    }
}


Reply via email to