Skip to content

segfault in threaded evhttp client usage (race condition) #217

@pphaneuf

Description

@pphaneuf

A fairly simple test program gets me a segfault, not 100% of the time, but fairly easy to reproduce, if you run the program a few times.

I think this threaded usage is done correctly? Never using an object from more than one thread at once, but what seems to happen is that the request callback runs (and even returns) before evhttp_make_request even completes, which seems to indicate there might not be enough locking in there?

Note the evhttp_connection_set_timeout with a negative timeout, to cause an early error (I think this might be a separate bug, allowing this to happen, but it only forces something that could happen for a valid reason).

Also, I'm calling evhttp_connection_free from the request callback, this is okay, right?

When crashing, the segfault is here:

#0  0x000000000185d9d0 in ?? ()
#1  0x00007f750899e8cf in bufferevent_suspend_read (bufev=0x185e0b0, what=<optimized out>) at bufferevent.c:72
#2  0x00007f75089a098e in bufferevent_socket_connect_hostname (bev=0x185e0b0, evdns_base=0x185d6d0, family=family@entry=0, hostname=0x185e090 "", port=80) at bufferevent_sock.c:494
#3  0x00007f75089adccf in evhttp_connection_connect (evcon=evcon@entry=0x185df20) at http.c:2216
#4  0x00007f75089ae768 in evhttp_make_request (evcon=0x185df20, req=0x185e420, type=<optimized out>, uri=<optimized out>) at http.c:2268
#5  0x0000000000400e05 in main () at test_timeout.c:60

I think the problem is that evhttp_connection_connect sets up the timeout callback, which, being already expired, would wake up the dispatch loop in the other thread, and fire off its callback... Meanwhile, evhttp_connection_connect calls bufferevent_socket_connect_hostname, with less than successful results. 😉

Here's the test program, commented out printfs seem to affect the timing enough to avoid the race condition, and commenting out THREAD_DISPATCH makes it disappear entirely (so the negative timeout is not the main culprit):

// gcc -ggdb -pthread -o test_timeout test_timeout.c -levent -levent_pthreads

#include <event2/dns.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/thread.h>
#include <pthread.h>
#include <stdio.h>

int done = 0;
struct event_base* base;
struct evdns_base* dns;
struct evhttp_connection* conn;

void request_done(struct evhttp_request* req, void* userdata) {
  printf("request_done: %p\n", req);

  if (req) {
    printf("response code: %i\n", evhttp_request_get_response_code(req));
  }

  //printf("freeing connection\n");
  evhttp_connection_free(conn);
  //printf("exiting loop\n");
  event_base_loopexit(base, NULL);
}

void* event_dispatch_thread(void* userdata) {
  printf("event dispatch thread running\n");
  printf("event_base_dispatch: %i\n", event_base_dispatch(base));

  return NULL;
}

#define THREAD_DISPATCH

int main() {
#ifdef THREAD_DISPATCH
  pthread_t thr;
#endif
  struct evhttp_request* req;

  evthread_use_pthreads();
  base = event_base_new();
  dns = evdns_base_new(base, 1);

#ifdef THREAD_DISPATCH
  printf("pthread_create: %i\n",
         pthread_create(&thr, NULL, &event_dispatch_thread, NULL));
#endif

  conn = evhttp_connection_base_new(base, dns, "www.google.com", 80);

#if 1
  evhttp_connection_set_timeout(conn, -42);
#endif

  req = evhttp_request_new(&request_done, NULL);
  printf("evhttp_make_request: %i\n",
         evhttp_make_request(conn, req, EVHTTP_REQ_GET, "/"));

#ifdef THREAD_DISPATCH
  printf("waiting for event_dispatch_thread\n");
  pthread_join(thr, NULL);
  printf("event_dispatch_thread joined\n");
#else
  event_dispatch_thread(NULL);
#endif

  evdns_base_free(dns, 1);
  event_base_free(base);

  return 0;
}

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions