]> granicus.if.org Git - libevent/commitdiff
Fix base unlocking in event_del() if event_base_set() runned in another thread
authorAzat Khuzhin <a3at.mail@gmail.com>
Tue, 27 Feb 2018 18:12:14 +0000 (21:12 +0300)
committerAzat Khuzhin <a3at.mail@gmail.com>
Wed, 28 Feb 2018 04:26:11 +0000 (07:26 +0300)
Image next situation:
  T1:                                        T2:
   event_del_()
     lock the event.ev_base.th_base_lock
     event_del_nolock_()                     event_set_base()
     unlock the event.ev_base.th_base_lock

In this case we will unlock the wrong base after event_del_nolock_()
returns, and deadlock is likely to happens, since event_base_set() do
not check any mutexes (due to it is possible to do this only if event is
not inserted anywhere).

So event_del_() has to cache the base before removing the event, and
cached base.th_base_lock after.

event.c

diff --git a/event.c b/event.c
index 17570c2743bdfc35bdb86fc11ef7bb803eaceef9..c55607076c07a4cc1be0697c255bf1f9600a1c29 100644 (file)
--- a/event.c
+++ b/event.c
@@ -2749,17 +2749,16 @@ static int
 event_del_(struct event *ev, int blocking)
 {
        int res;
+       struct event_base *base = ev->ev_base;
 
-       if (EVUTIL_FAILURE_CHECK(!ev->ev_base)) {
+       if (EVUTIL_FAILURE_CHECK(!base)) {
                event_warnx("%s: event has no event_base set.", __func__);
                return -1;
        }
 
-       EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
-
+       EVBASE_ACQUIRE_LOCK(base, th_base_lock);
        res = event_del_nolock_(ev, blocking);
-
-       EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
+       EVBASE_RELEASE_LOCK(base, th_base_lock);
 
        return (res);
 }