|
commit 937604b4cfccddd607b8d4883815c4e3f9ab70d0
|
|
Author: Willy Tarreau <w@1wt.eu>
|
|
Date: Wed Jul 24 16:45:02 2019 +0200
|
|
|
|
BUG/MEDIUM: protocols: add a global lock for the init/deinit stuff
|
|
|
|
Dragan Dosen found that the listeners lock is not sufficient to protect
|
|
the listeners list when proxies are stopping because the listeners are
|
|
also unlinked from the protocol list, and under certain situations like
|
|
bombing with soft-stop signals or shutting down many frontends in parallel
|
|
from multiple CLI connections, it could be possible to provoke multiple
|
|
instances of delete_listener() to be called in parallel for different
|
|
listeners, thus corrupting the protocol lists.
|
|
|
|
Such operations are pretty rare, they are performed once per proxy upon
|
|
startup and once per proxy on shut down. Thus there is no point trying
|
|
to optimize anything and we can use a global lock to protect the protocol
|
|
lists during these manipulations.
|
|
|
|
This fix (or a variant) will have to be backported as far as 1.8.
|
|
|
|
(cherry picked from commit daacf3664506d56a1f3b050ccba504886a18b12a)
|
|
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
|
|
|
|
diff --git a/include/proto/protocol.h b/include/proto/protocol.h
|
|
index 7bbebb8e..f25f77f0 100644
|
|
--- a/include/proto/protocol.h
|
|
+++ b/include/proto/protocol.h
|
|
@@ -23,9 +23,11 @@
|
|
#define _PROTO_PROTOCOL_H
|
|
|
|
#include <sys/socket.h>
|
|
+#include <common/hathreads.h>
|
|
#include <types/protocol.h>
|
|
|
|
extern struct protocol *__protocol_by_family[AF_CUST_MAX];
|
|
+__decl_hathreads(extern HA_SPINLOCK_T proto_lock);
|
|
|
|
/* Registers the protocol <proto> */
|
|
void protocol_register(struct protocol *proto);
|
|
diff --git a/include/types/protocol.h b/include/types/protocol.h
|
|
index 1d3404b9..f38baeb9 100644
|
|
--- a/include/types/protocol.h
|
|
+++ b/include/types/protocol.h
|
|
@@ -80,9 +80,9 @@ struct protocol {
|
|
int (*pause)(struct listener *l); /* temporarily pause this listener for a soft restart */
|
|
void (*add)(struct listener *l, int port); /* add a listener for this protocol and port */
|
|
|
|
- struct list listeners; /* list of listeners using this protocol */
|
|
- int nb_listeners; /* number of listeners */
|
|
- struct list list; /* list of registered protocols */
|
|
+ struct list listeners; /* list of listeners using this protocol (under proto_lock) */
|
|
+ int nb_listeners; /* number of listeners (under proto_lock) */
|
|
+ struct list list; /* list of registered protocols (under proto_lock) */
|
|
};
|
|
|
|
#define CONNECT_HAS_DATA 0x00000001 /* There's data available to be sent */
|
|
diff --git a/src/listener.c b/src/listener.c
|
|
index 40a774ed..b5fe2ac2 100644
|
|
--- a/src/listener.c
|
|
+++ b/src/listener.c
|
|
@@ -433,6 +433,9 @@ static void limit_listener(struct listener *l, struct list *list)
|
|
* used as a protocol's generic enable_all() primitive, for use after the
|
|
* fork(). It puts the listeners into LI_READY or LI_FULL states depending on
|
|
* their number of connections. It always returns ERR_NONE.
|
|
+ *
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
*/
|
|
int enable_all_listeners(struct protocol *proto)
|
|
{
|
|
@@ -447,6 +450,9 @@ int enable_all_listeners(struct protocol *proto)
|
|
* the polling lists when they are in the LI_READY or LI_FULL states. It is
|
|
* intended to be used as a protocol's generic disable_all() primitive. It puts
|
|
* the listeners into LI_LISTEN, and always returns ERR_NONE.
|
|
+ *
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
*/
|
|
int disable_all_listeners(struct protocol *proto)
|
|
{
|
|
@@ -516,6 +522,9 @@ void unbind_listener_no_close(struct listener *listener)
|
|
/* This function closes all listening sockets bound to the protocol <proto>,
|
|
* and the listeners end in LI_ASSIGNED state if they were higher. It does not
|
|
* detach them from the protocol. It always returns ERR_NONE.
|
|
+ *
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
*/
|
|
int unbind_all_listeners(struct protocol *proto)
|
|
{
|
|
@@ -580,14 +589,19 @@ int create_listeners(struct bind_conf *bc, const struct sockaddr_storage *ss,
|
|
* number of listeners is updated, as well as the global number of listeners
|
|
* and jobs. Note that the listener must have previously been unbound. This
|
|
* is the generic function to use to remove a listener.
|
|
+ *
|
|
+ * Will grab the proto_lock.
|
|
+ *
|
|
*/
|
|
void delete_listener(struct listener *listener)
|
|
{
|
|
HA_SPIN_LOCK(LISTENER_LOCK, &listener->lock);
|
|
if (listener->state == LI_ASSIGNED) {
|
|
listener->state = LI_INIT;
|
|
+ HA_SPIN_LOCK(PROTO_LOCK, &proto_lock);
|
|
LIST_DEL(&listener->proto_list);
|
|
listener->proto->nb_listeners--;
|
|
+ HA_SPIN_UNLOCK(PROTO_LOCK, &proto_lock);
|
|
_HA_ATOMIC_SUB(&jobs, 1);
|
|
_HA_ATOMIC_SUB(&listeners, 1);
|
|
}
|
|
diff --git a/src/proto_sockpair.c b/src/proto_sockpair.c
|
|
index a4faa370..e7dd670d 100644
|
|
--- a/src/proto_sockpair.c
|
|
+++ b/src/proto_sockpair.c
|
|
@@ -80,6 +80,9 @@ INITCALL1(STG_REGISTER, protocol_register, &proto_sockpair);
|
|
/* Add <listener> to the list of sockpair listeners (port is ignored). The
|
|
* listener's state is automatically updated from LI_INIT to LI_ASSIGNED.
|
|
* The number of listeners for the protocol is updated.
|
|
+ *
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
*/
|
|
static void sockpair_add_listener(struct listener *listener, int port)
|
|
{
|
|
@@ -97,6 +100,8 @@ static void sockpair_add_listener(struct listener *listener, int port)
|
|
* loose them across the fork(). A call to uxst_enable_listeners() is needed
|
|
* to complete initialization.
|
|
*
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
* The return value is composed from ERR_NONE, ERR_RETRYABLE and ERR_FATAL.
|
|
*/
|
|
static int sockpair_bind_listeners(struct protocol *proto, char *errmsg, int errlen)
|
|
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
|
|
index 64ffb83c..bcbe27a7 100644
|
|
--- a/src/proto_tcp.c
|
|
+++ b/src/proto_tcp.c
|
|
@@ -1103,6 +1103,9 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
|
|
* The sockets will be registered but not added to any fd_set, in order not to
|
|
* loose them across the fork(). A call to enable_all_listeners() is needed
|
|
* to complete initialization. The return value is composed from ERR_*.
|
|
+ *
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
*/
|
|
static int tcp_bind_listeners(struct protocol *proto, char *errmsg, int errlen)
|
|
{
|
|
@@ -1121,6 +1124,9 @@ static int tcp_bind_listeners(struct protocol *proto, char *errmsg, int errlen)
|
|
/* Add <listener> to the list of tcpv4 listeners, on port <port>. The
|
|
* listener's state is automatically updated from LI_INIT to LI_ASSIGNED.
|
|
* The number of listeners for the protocol is updated.
|
|
+ *
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
*/
|
|
static void tcpv4_add_listener(struct listener *listener, int port)
|
|
{
|
|
@@ -1136,6 +1142,9 @@ static void tcpv4_add_listener(struct listener *listener, int port)
|
|
/* Add <listener> to the list of tcpv6 listeners, on port <port>. The
|
|
* listener's state is automatically updated from LI_INIT to LI_ASSIGNED.
|
|
* The number of listeners for the protocol is updated.
|
|
+ *
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
*/
|
|
static void tcpv6_add_listener(struct listener *listener, int port)
|
|
{
|
|
diff --git a/src/proto_uxst.c b/src/proto_uxst.c
|
|
index 66093af6..7263240f 100644
|
|
--- a/src/proto_uxst.c
|
|
+++ b/src/proto_uxst.c
|
|
@@ -379,6 +379,9 @@ static int uxst_unbind_listener(struct listener *listener)
|
|
/* Add <listener> to the list of unix stream listeners (port is ignored). The
|
|
* listener's state is automatically updated from LI_INIT to LI_ASSIGNED.
|
|
* The number of listeners for the protocol is updated.
|
|
+ *
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
*/
|
|
static void uxst_add_listener(struct listener *listener, int port)
|
|
{
|
|
@@ -594,6 +597,8 @@ static int uxst_connect_server(struct connection *conn, int flags)
|
|
* loose them across the fork(). A call to uxst_enable_listeners() is needed
|
|
* to complete initialization.
|
|
*
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
* The return value is composed from ERR_NONE, ERR_RETRYABLE and ERR_FATAL.
|
|
*/
|
|
static int uxst_bind_listeners(struct protocol *proto, char *errmsg, int errlen)
|
|
@@ -613,6 +618,9 @@ static int uxst_bind_listeners(struct protocol *proto, char *errmsg, int errlen)
|
|
/* This function stops all listening UNIX sockets bound to the protocol
|
|
* <proto>. It does not detaches them from the protocol.
|
|
* It always returns ERR_NONE.
|
|
+ *
|
|
+ * Must be called with proto_lock held.
|
|
+ *
|
|
*/
|
|
static int uxst_unbind_listeners(struct protocol *proto)
|
|
{
|
|
diff --git a/src/protocol.c b/src/protocol.c
|
|
index 96e01c82..ac45cf2e 100644
|
|
--- a/src/protocol.c
|
|
+++ b/src/protocol.c
|
|
@@ -18,18 +18,26 @@
|
|
#include <common/mini-clist.h>
|
|
#include <common/standard.h>
|
|
|
|
-#include <types/protocol.h>
|
|
+#include <proto/protocol.h>
|
|
|
|
/* List head of all registered protocols */
|
|
static struct list protocols = LIST_HEAD_INIT(protocols);
|
|
struct protocol *__protocol_by_family[AF_CUST_MAX] = { };
|
|
|
|
+/* This is the global spinlock we may need to register/unregister listeners or
|
|
+ * protocols. Its main purpose is in fact to serialize the rare stop/deinit()
|
|
+ * phases.
|
|
+ */
|
|
+__decl_spinlock(proto_lock);
|
|
+
|
|
/* Registers the protocol <proto> */
|
|
void protocol_register(struct protocol *proto)
|
|
{
|
|
+ HA_SPIN_LOCK(PROTO_LOCK, &proto_lock);
|
|
LIST_ADDQ(&protocols, &proto->list);
|
|
if (proto->sock_domain >= 0 && proto->sock_domain < AF_CUST_MAX)
|
|
__protocol_by_family[proto->sock_domain] = proto;
|
|
+ HA_SPIN_UNLOCK(PROTO_LOCK, &proto_lock);
|
|
}
|
|
|
|
/* Unregisters the protocol <proto>. Note that all listeners must have
|
|
@@ -37,8 +45,10 @@ void protocol_register(struct protocol *proto)
|
|
*/
|
|
void protocol_unregister(struct protocol *proto)
|
|
{
|
|
+ HA_SPIN_LOCK(PROTO_LOCK, &proto_lock);
|
|
LIST_DEL(&proto->list);
|
|
LIST_INIT(&proto->list);
|
|
+ HA_SPIN_UNLOCK(PROTO_LOCK, &proto_lock);
|
|
}
|
|
|
|
/* binds all listeners of all registered protocols. Returns a composition
|
|
@@ -50,6 +60,7 @@ int protocol_bind_all(char *errmsg, int errlen)
|
|
int err;
|
|
|
|
err = 0;
|
|
+ HA_SPIN_LOCK(PROTO_LOCK, &proto_lock);
|
|
list_for_each_entry(proto, &protocols, list) {
|
|
if (proto->bind_all) {
|
|
err |= proto->bind_all(proto, errmsg, errlen);
|
|
@@ -57,6 +68,7 @@ int protocol_bind_all(char *errmsg, int errlen)
|
|
break;
|
|
}
|
|
}
|
|
+ HA_SPIN_UNLOCK(PROTO_LOCK, &proto_lock);
|
|
return err;
|
|
}
|
|
|
|
@@ -71,11 +83,13 @@ int protocol_unbind_all(void)
|
|
int err;
|
|
|
|
err = 0;
|
|
+ HA_SPIN_LOCK(PROTO_LOCK, &proto_lock);
|
|
list_for_each_entry(proto, &protocols, list) {
|
|
if (proto->unbind_all) {
|
|
err |= proto->unbind_all(proto);
|
|
}
|
|
}
|
|
+ HA_SPIN_UNLOCK(PROTO_LOCK, &proto_lock);
|
|
return err;
|
|
}
|
|
|
|
@@ -89,11 +103,13 @@ int protocol_enable_all(void)
|
|
int err;
|
|
|
|
err = 0;
|
|
+ HA_SPIN_LOCK(PROTO_LOCK, &proto_lock);
|
|
list_for_each_entry(proto, &protocols, list) {
|
|
if (proto->enable_all) {
|
|
err |= proto->enable_all(proto);
|
|
}
|
|
}
|
|
+ HA_SPIN_UNLOCK(PROTO_LOCK, &proto_lock);
|
|
return err;
|
|
}
|
|
|
|
@@ -107,11 +123,13 @@ int protocol_disable_all(void)
|
|
int err;
|
|
|
|
err = 0;
|
|
+ HA_SPIN_LOCK(PROTO_LOCK, &proto_lock);
|
|
list_for_each_entry(proto, &protocols, list) {
|
|
if (proto->disable_all) {
|
|
err |= proto->disable_all(proto);
|
|
}
|
|
}
|
|
+ HA_SPIN_UNLOCK(PROTO_LOCK, &proto_lock);
|
|
return err;
|
|
}
|
|
|