|
commit 31470e2ba2aabb4c6340fbc15cb5486ceb8c69c8
|
|
Author: Olivier Houchard <ohouchard@haproxy.com>
|
|
Date: Mon Apr 29 18:52:06 2019 +0200
|
|
|
|
BUG/MEDIUM: port_range: Make the ring buffer lock-free.
|
|
|
|
Port range uses a ring buffer, and unfortunately, when making haproxy
|
|
multithreaded, it's been overlooked, and the ring buffer is not thread-safe.
|
|
When specifying a source range, 2 or more threads could pick the same
|
|
port, and of course only one of them could use the port, the others would
|
|
always fail the connection.
|
|
To fix this, make it a lock-free ring buffer. This is easier than usual
|
|
because we know the ring buffer can never be full.
|
|
|
|
This should be backported to 1.8 and 1.9.
|
|
|
|
(cherry picked from commit 07425de71777b688e77a9c70a7088c13e66e41e9)
|
|
Signed-off-by: Olivier Houchard <cognet@ci0.org>
|
|
(cherry picked from commit bffb51147a4a5939e344b3c838628f9a944febef)
|
|
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
|
|
|
|
diff --git a/include/proto/port_range.h b/include/proto/port_range.h
|
|
index 8c63faca..f7e3f1d5 100644
|
|
--- a/include/proto/port_range.h
|
|
+++ b/include/proto/port_range.h
|
|
@@ -24,18 +24,22 @@
|
|
|
|
#include <types/port_range.h>
|
|
|
|
+#define GET_NEXT_OFF(range, off) ((off) == (range)->size - 1 ? 0 : (off) + 1)
|
|
+
|
|
/* return an available port from range <range>, or zero if none is left */
|
|
static inline int port_range_alloc_port(struct port_range *range)
|
|
{
|
|
int ret;
|
|
+ int get;
|
|
+ int put;
|
|
|
|
- if (!range->avail)
|
|
- return 0;
|
|
- ret = range->ports[range->get];
|
|
- range->get++;
|
|
- if (range->get >= range->size)
|
|
- range->get = 0;
|
|
- range->avail--;
|
|
+ get = HA_ATOMIC_LOAD(&range->get);
|
|
+ do {
|
|
+ put = HA_ATOMIC_LOAD(&range->put_t);
|
|
+ if (unlikely(put == get))
|
|
+ return 0;
|
|
+ ret = range->ports[get];
|
|
+ } while (!(HA_ATOMIC_CAS(&range->get, &get, GET_NEXT_OFF(range, get))));
|
|
return ret;
|
|
}
|
|
|
|
@@ -45,14 +49,28 @@ static inline int port_range_alloc_port(struct port_range *range)
|
|
*/
|
|
static inline void port_range_release_port(struct port_range *range, int port)
|
|
{
|
|
+ int put;
|
|
+
|
|
if (!port || !range)
|
|
return;
|
|
|
|
- range->ports[range->put] = port;
|
|
- range->avail++;
|
|
- range->put++;
|
|
- if (range->put >= range->size)
|
|
- range->put = 0;
|
|
+ put = range->put_h;
|
|
+ /* put_h is reserved for producers, so that they can each get a
|
|
+ * free slot, put_t is what is used by consumers to know if there's
|
|
+ * elements available or not
|
|
+ */
|
|
+ /* First reserve or slot, we know the ring buffer can't be full,
|
|
+ * as we will only ever release port we allocated before
|
|
+ */
|
|
+ while (!(HA_ATOMIC_CAS(&range->put_h, &put, GET_NEXT_OFF(range, put))));
|
|
+ HA_ATOMIC_STORE(&range->ports[put], port);
|
|
+ /* Wait until all the threads that got a slot before us are done */
|
|
+ while ((volatile int)range->put_t != put)
|
|
+ __ha_compiler_barrier();
|
|
+ /* Let the world know we're done, and any potential consumer they
|
|
+ * can use that port.
|
|
+ */
|
|
+ HA_ATOMIC_STORE(&range->put_t, GET_NEXT_OFF(range, put));
|
|
}
|
|
|
|
/* return a new initialized port range of N ports. The ports are not
|
|
@@ -62,8 +80,10 @@ static inline struct port_range *port_range_alloc_range(int n)
|
|
{
|
|
struct port_range *ret;
|
|
ret = calloc(1, sizeof(struct port_range) +
|
|
- n * sizeof(((struct port_range *)0)->ports[0]));
|
|
- ret->size = ret->avail = n;
|
|
+ (n + 1) * sizeof(((struct port_range *)0)->ports[0]));
|
|
+ ret->size = n + 1;
|
|
+ /* Start at the first free element */
|
|
+ ret->put_h = ret->put_t = n;
|
|
return ret;
|
|
}
|
|
|
|
diff --git a/include/types/port_range.h b/include/types/port_range.h
|
|
index 1d010f77..33455d2d 100644
|
|
--- a/include/types/port_range.h
|
|
+++ b/include/types/port_range.h
|
|
@@ -25,8 +25,7 @@
|
|
#include <netinet/in.h>
|
|
|
|
struct port_range {
|
|
- int size, get, put; /* range size, and get/put positions */
|
|
- int avail; /* number of available ports left */
|
|
+ int size, get, put_h, put_t; /* range size, and get/put positions */
|
|
uint16_t ports[0]; /* array of <size> ports, in host byte order */
|
|
};
|
|
|