]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: threads/unix: Fix a deadlock when a listener is temporarily disabled
authorChristopher Faulet <cfaulet@haproxy.com>
Fri, 16 Mar 2018 09:04:47 +0000 (10:04 +0100)
committerWilly Tarreau <w@1wt.eu>
Fri, 16 Mar 2018 10:19:07 +0000 (11:19 +0100)
When a listener is temporarily disabled, we start by locking it and then we call
.pause callback of the underlying protocol (tcp/unix). For TCP listeners, this
is not a problem. But listeners bound on an unix socket are in fact closed
instead. So .pause callback relies on unbind_listener function to do its job.

Unfortunatly, unbind_listener hold the listener's lock and then call an internal
function to unbind it. So, there is a deadlock here. This happens during a
reload. To fix the problemn, the function do_unbind_listener, which is lockless,
is now exported and is called when a listener bound on an unix socket is
temporarily disabled.

This patch must be backported in 1.8.

include/proto/listener.h
src/listener.c
src/proto_uxst.c

index a33aeed4a80c4bf559cb32eddbf6e218403bd235..fdace2b5f967a4a2f2846c32e2c20d7e76958875 100644 (file)
@@ -60,6 +60,11 @@ int disable_all_listeners(struct protocol *proto);
 /* Dequeues all of the listeners waiting for a resource in wait queue <queue>. */
 void dequeue_all_listeners(struct list *list);
 
+/* Must be called with the lock held. Depending on <do_close> value, it does
+ * what unbind_listener or unbind_listener_no_close should do.
+ */
+void do_unbind_listener(struct listener *listener, int do_close);
+
 /* This function closes the listening socket for the specified listener,
  * provided that it's already in a listening state. The listener enters the
  * LI_ASSIGNED state. This function is intended to be used as a generic
index f60a14624fa15f09eef6ba1f19b703ab0a5789e9..283058ace46d848ee4b05d9cb3a5cb902ef0dd96 100644 (file)
@@ -49,8 +49,6 @@ static struct bind_kw_list bind_keywords = {
 
 struct xfer_sock_list *xfer_sock_list = NULL;
 
-static void __do_unbind_listener(struct listener *listener, int do_close);
-
 /* This function adds the specified listener's file descriptor to the polling
  * lists if it is in the LI_LISTEN state. The listener enters LI_READY or
  * LI_FULL state depending on its number of connections. In deamon mode, we
@@ -68,9 +66,9 @@ static void enable_listener(struct listener *listener)
                         * want any fd event to reach it.
                         */
                        if (!(global.tune.options & GTUNE_SOCKET_TRANSFER))
-                               __do_unbind_listener(listener, 1);
+                               do_unbind_listener(listener, 1);
                        else {
-                               __do_unbind_listener(listener, 0);
+                               do_unbind_listener(listener, 0);
                                listener->state = LI_LISTEN;
                        }
                }
@@ -308,8 +306,10 @@ void dequeue_all_listeners(struct list *list)
        HA_SPIN_UNLOCK(LISTENER_QUEUE_LOCK, &lq_lock);
 }
 
-/* must be called with the lock held */
-static void __do_unbind_listener(struct listener *listener, int do_close)
+/* Must be called with the lock held. Depending on <do_close> value, it does
+ * what unbind_listener or unbind_listener_no_close should do.
+ */
+void do_unbind_listener(struct listener *listener, int do_close)
 {
        if (listener->state == LI_READY)
                fd_stop_recv(listener->fd);
@@ -331,13 +331,6 @@ static void __do_unbind_listener(struct listener *listener, int do_close)
        }
 }
 
-static void do_unbind_listener(struct listener *listener, int do_close)
-{
-       HA_SPIN_LOCK(LISTENER_LOCK, &listener->lock);
-       __do_unbind_listener(listener, do_close);
-       HA_SPIN_UNLOCK(LISTENER_LOCK, &listener->lock);
-}
-
 /* This function closes the listening socket for the specified listener,
  * provided that it's already in a listening state. The listener enters the
  * LI_ASSIGNED state. This function is intended to be used as a generic
@@ -345,7 +338,9 @@ static void do_unbind_listener(struct listener *listener, int do_close)
  */
 void unbind_listener(struct listener *listener)
 {
+       HA_SPIN_LOCK(LISTENER_LOCK, &listener->lock);
        do_unbind_listener(listener, 1);
+       HA_SPIN_UNLOCK(LISTENER_LOCK, &listener->lock);
 }
 
 /* This function pretends the listener is dead, but keeps the FD opened, so
@@ -353,7 +348,9 @@ void unbind_listener(struct listener *listener)
  */
 void unbind_listener_no_close(struct listener *listener)
 {
+       HA_SPIN_LOCK(LISTENER_LOCK, &listener->lock);
        do_unbind_listener(listener, 0);
+       HA_SPIN_UNLOCK(LISTENER_LOCK, &listener->lock);
 }
 
 /* This function closes all listening sockets bound to the protocol <proto>,
index 0f717385ec3ee2d9766486d488d0cc31580ccc20..9fc50dff46f62d0df8f4d32f4faf4f2bb68c457e 100644 (file)
@@ -393,7 +393,9 @@ static int uxst_pause_listener(struct listener *l)
        if (((struct sockaddr_un *)&l->addr)->sun_path[0])
                return 1;
 
-       unbind_listener(l);
+       /* Listener's lock already held. Call lockless version of
+        * unbind_listener. */
+       do_unbind_listener(l, 1);
        return 0;
 }