11

I am currently writing a multi-threaded application using libevent.

Some events are triggered by IO, but I need a couple of events that are triggered accross threads by the code itself, using event_active().

I have tried to write a simple program that shows where my problem is:

The event is created using event_new(), and the fd set to -1.

When calling event_add(), if a timeout struct is used, the event is later properly handled by event_base_dispatch.

If event_add(ev, NULL) is used instead, it returns 0 (apparently successful), but event_base_dispatch() returns 1 (which means no the event was not properly registered.)

This behavior can be tested using the following code and swapping the event_add lines:

#include <event2/event.h>
#include <unistd.h>

void cb_func (evutil_socket_t fd, short flags, void * _param) {
  puts("Callback function called!");
}

void run_base_with_ticks(struct event_base *base)
{
  struct timeval one_sec;

  one_sec.tv_sec = 1;
  one_sec.tv_usec = 0;
  struct event * ev1;
  ev1 = event_new(base, -1, EV_PERSIST, cb_func, NULL);
  //int result = event_add(ev1, NULL);
  int result = event_add(ev1, &one_sec);
  printf("event_add result: %d\n",result);

  while (1) {
     result = event_base_dispatch(base);
     if (result == 1) {
       printf("Failed: event considered as not pending dispite successful event_add\n");
       sleep(1);
     } else {
       puts("Tick");
     }
  }
}

int main () {
  struct event_base *base = event_base_new();
  run_base_with_ticks(base);
  return 0;
}

Compilation: g++ sample.cc -levent

The thing is, I do not need the timeout, and do not want to use a n-years timeout as a workaround. So if this is not the right way to use user-triggered events, I would like to know how it is done.

Quentin
  • 269
  • 3
  • 9

2 Answers2

8

Your approach is sound. In Libevent 2.0, you can use event_active() to activate an event from another thread. Just make sure that you use evthread_use_windows_threads() or evthread_use_pthreads() as appropriate beforehand, to tell Libevent to use the right threading library.

As for needing an extra event: in Libevent 2.0 and earlier, an event loop will exit immediately when there are no pending events added. Your best bet there is probably the timeout trick you discovered.

If you don't like that, you can use the internal "event_base_add_virtual" function to tell the event_base that it has a virtual event. This function isn't exported, though, so you'll have to say something like:

    void event_base_add_virtual(struct event_base *);
    // ...
    base = event_base_new();
    event_base_add_virtual(base); // keep it from exiting

That's a bit of a hack, though, and it uses an undocumented function, so you'd need to watch out in case it doesn't work with a later version of Libevent.

Finally, this method won't help you now, but there's a patch pending for future versions of Libevent (2.1 and later) to add a new flag to event_base_loop() to keep it from exiting when the loop is out of events. The patch is over on Github; it is mainly waiting for code review, and for a better name for the option.

nickm
  • 486
  • 2
  • 2
  • Thank you Nick for your quick reply. – Quentin Oct 05 '11 at 03:06
  • I have a few comments about this: - As for my current project, I think I can get around this by using the event loop slightly differently and using the timeout to actually (unnecessarily) trigger the processing after a relatively long wait (1 second is an eternity in the server world). - Why not just make it possible to use pure user-triggered events on top of fd or timeout ones? - If the event is going to be ignored, shouldn't event_add return something else than 0? I was tempted by the event_base_add_virtual trick, but I prefer my code compatible with later versions. – Quentin Oct 05 '11 at 03:15
  • 2
    It is possible to use pure user-triggered events: You make them with event_new(base, -1/*no fd*/, 0/*no events*/, callback, callback_data). You activate them with event_active(). You don't event_add() them, though: event_add() is only for events that a base should be polling for itself. – nickm Oct 06 '11 at 01:56
  • `EVLOOP_NO_EXIT_ON_EMPTY` appears to be the actual name for the flag. – Erwan Legrand Jan 26 '18 at 17:55
  • Related: https://stackoverflow.com/questions/19757003/libevent-evloop-no-exit-on-empty-not-working – Erwan Legrand Jan 26 '18 at 17:58
1

I just got burned by this with libevent-2.0.21-stable. It is quite clearly a bug. I hope they fix it in a future release. In the meantime, updating the docs to warn us about it would be helpful.

The best workaround seems to be the fake timeout as described in the question.

@nickm, you didn't read the question. His example code uses event_new() like you described; there is a bug in libevent that causes it to fail when using a NULL timeout (but return 0 when you call event_add()).