summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--listener.c4
-rw-r--r--test/regress_listener.c136
2 files changed, 136 insertions, 4 deletions
diff --git a/listener.c b/listener.c
index bf55f7b5..125c7286 100644
--- a/listener.c
+++ b/listener.c
@@ -433,10 +433,8 @@ listener_read_cb(evutil_socket_t fd, short what, void *p)
++lev->refcnt;
cb = lev->cb;
user_data = lev->user_data;
- UNLOCK(lev);
cb(lev, new_fd, (struct sockaddr*)&ss, (int)socklen,
user_data);
- LOCK(lev);
if (lev->refcnt == 1) {
int freed = listener_decref_and_unlock(lev);
EVUTIL_ASSERT(freed);
@@ -458,9 +456,7 @@ listener_read_cb(evutil_socket_t fd, short what, void *p)
++lev->refcnt;
errorcb = lev->errorcb;
user_data = lev->user_data;
- UNLOCK(lev);
errorcb(lev, user_data);
- LOCK(lev);
listener_decref_and_unlock(lev);
} else {
event_sock_warn(fd, "Error from accept() call");
diff --git a/test/regress_listener.c b/test/regress_listener.c
index 871da4c6..adf6697d 100644
--- a/test/regress_listener.c
+++ b/test/regress_listener.c
@@ -49,6 +49,11 @@
#include "event2/listener.h"
#include "event2/event.h"
#include "event2/util.h"
+#ifndef EVENT__DISABLE_THREAD_SUPPORT
+#include "event2/thread.h"
+#include "regress_thread.h"
+#endif
+
#include "regress.h"
#include "tinytest.h"
@@ -304,6 +309,127 @@ end:
}
#endif
+#ifndef EVENT__DISABLE_THREAD_SUPPORT
+
+static THREAD_FN
+disable_thread(void * arg)
+{
+ struct evconnlistener *lev = (struct evconnlistener *)arg;
+ evconnlistener_disable(lev);
+ return NULL;
+}
+
+static void
+acceptcb_for_thread_test(struct evconnlistener *listener, evutil_socket_t fd,
+ struct sockaddr *addr, int socklen, void *arg)
+{
+ THREAD_T *threadid_arg;
+ THREAD_T threadid;
+ struct timeval delay = { .tv_sec = 0, .tv_usec = 5000 };
+
+ threadid_arg = (THREAD_T *)arg;
+
+ evutil_closesocket(fd);
+
+ /* We need to run evconnlistener_disable() from disable_thread
+ * in parallel with processing this callback to trigger deadlock regression
+ */
+ THREAD_START(threadid, disable_thread, listener);
+ evutil_usleep_(&delay);
+
+ *threadid_arg = threadid;
+}
+
+static void
+regress_listener_disable_in_thread(void *arg)
+{
+ struct basic_test_data *data = arg;
+ struct event_base *base = data->base;
+ struct evconnlistener *listener = NULL;
+ struct sockaddr_in sin;
+ struct sockaddr_storage ss;
+ ev_socklen_t slen = sizeof(ss);
+ evutil_socket_t fd = EVUTIL_INVALID_SOCKET;
+ unsigned int flags = LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE|LEV_OPT_THREADSAFE;
+ THREAD_T threadid;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
+ sin.sin_port = 0; /* "You pick!" */
+
+
+ listener = evconnlistener_new_bind(base, acceptcb_for_thread_test, (void*)(&threadid),
+ flags, -1, (struct sockaddr *)&sin, sizeof(sin));
+
+ tt_assert(listener);
+
+ tt_assert(getsockname(evconnlistener_get_fd(listener),
+ (struct sockaddr*)&ss, &slen) == 0);
+
+ tt_assert(evutil_socket_connect_(&fd, (struct sockaddr*)&ss, slen) != -1);
+
+ event_base_loop(base, EVLOOP_ONCE);
+
+ THREAD_JOIN(threadid);
+end:
+ if (listener)
+ evconnlistener_free(listener);
+}
+
+static void
+errorcb_for_thread_test(struct evconnlistener *listener, void *arg)
+{
+ THREAD_T *threadid_arg;
+ THREAD_T threadid;
+ struct timeval delay = { .tv_sec = 0, .tv_usec = 5000 };
+
+ threadid_arg = (THREAD_T *)arg;
+
+ /* We need to run evconnlistener_disable() from disable_thread
+ * in parallel with processing this callback to trigger deadlock regression
+ */
+ THREAD_START(threadid, disable_thread, listener);
+ evutil_usleep_(&delay);
+
+ *threadid_arg = threadid;
+}
+
+static void
+acceptcb_for_thread_test_error(struct evconnlistener *listener, evutil_socket_t fd,
+ struct sockaddr *addr, int socklen, void *arg)
+{
+}
+
+static void
+regress_listener_disable_in_thread_error(void *arg)
+{
+ struct basic_test_data *data = arg;
+ struct event_base *base = data->base;
+ struct evconnlistener *listener = NULL;
+ unsigned int flags = LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE|LEV_OPT_THREADSAFE;
+ THREAD_T threadid;
+
+ /* send, so that pair[0] will look 'readable'*/
+ tt_int_op(send(data->pair[1], "hello", 5, 0), >, 0);
+
+ /* Start a listener with a bogus socket. */
+ listener = evconnlistener_new(base, acceptcb_for_thread_test_error, (void*)&threadid,
+ flags, 0,
+ data->pair[0]);
+ tt_assert(listener);
+
+ evconnlistener_set_error_cb(listener, errorcb_for_thread_test);
+
+ event_base_loop(base, EVLOOP_ONCE);
+
+ THREAD_JOIN(threadid);
+end:
+ if (listener)
+ evconnlistener_free(listener);
+}
+#endif
+
struct testcase_t listener_testcases[] = {
{ "randport", regress_pick_a_port, TT_FORK|TT_NEED_BASE,
@@ -332,6 +458,16 @@ struct testcase_t listener_testcases[] = {
{ "immediate_close", regress_listener_immediate_close,
TT_FORK|TT_NEED_BASE, &basic_setup, NULL, },
+#ifndef EVENT__DISABLE_THREAD_SUPPORT
+ { "disable_in_thread", regress_listener_disable_in_thread,
+ TT_FORK|TT_NEED_BASE|TT_NEED_THREADS,
+ &basic_setup, NULL, },
+
+ { "disable_in_thread_error", regress_listener_disable_in_thread_error,
+ TT_FORK|TT_NEED_BASE|TT_NEED_THREADS|TT_NEED_SOCKETPAIR,
+ &basic_setup, NULL, },
+#endif
+
END_OF_TESTCASES,
};