]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: threads/pool: Make pool thread-safe by locking all access to a pool
authorChristopher Faulet <cfaulet@haproxy.com>
Tue, 29 Aug 2017 07:52:38 +0000 (09:52 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 31 Oct 2017 12:58:30 +0000 (13:58 +0100)
A lock has been added for each memory pool. It is used to protect the pool
during allocations and releases. It is also used when pool info are dumped.

include/common/hathreads.h
include/common/memory.h
src/haproxy.c
src/memory.c
src/proxy.c
src/ssl_sock.c

index 257f09b8aeb8e8e3f2a3a092b47e546ffc55e4d1..3e2a350b61f3fb15f6a41a10e9ec28012edcfbe5 100644 (file)
@@ -138,6 +138,7 @@ int  thread_need_sync(void);
 
 enum lock_label {
        THREAD_SYNC_LOCK = 0,
+       POOL_LOCK,
        LOCK_LABELS
 };
 struct lock_stat {
@@ -220,7 +221,7 @@ struct ha_rwlock {
 
 static inline void show_lock_stats()
 {
-       const char *labels[LOCK_LABELS] = {"THREAD_SYNC" };
+       const char *labels[LOCK_LABELS] = {"THREAD_SYNC", "POOL"};
        int lbl;
 
        for (lbl = 0; lbl < LOCK_LABELS; lbl++) {
index f2d69783f0896bc24708bf22415a8b5c078b4f77..999150d35ea085c888a0b4d0c77f55ec9bcd8966 100644 (file)
@@ -27,6 +27,7 @@
 
 #include <common/config.h>
 #include <common/mini-clist.h>
+#include <common/hathreads.h>
 
 #ifndef DEBUG_DONT_SHARE_POOLS
 #define MEM_F_SHARED   0x1
@@ -46,6 +47,9 @@
 
 struct pool_head {
        void **free_list;
+#ifdef USE_THREAD
+       HA_SPINLOCK_T lock;     /* the spin lock */
+#endif
        struct list list;       /* list of all known pools */
        unsigned int used;      /* how many chunks are currently in use */
        unsigned int allocated; /* how many chunks have been allocated */
@@ -69,6 +73,7 @@ extern int mem_poison_byte;
  * A call to the garbage collector is performed at most once in case malloc()
  * returns an error, before returning NULL.
  */
+void *__pool_refill_alloc(struct pool_head *pool, unsigned int avail);
 void *pool_refill_alloc(struct pool_head *pool, unsigned int avail);
 
 /* Try to find an existing shared pool with the same characteristics and
@@ -93,8 +98,12 @@ void pool_flush2(struct pool_head *pool);
 /*
  * This function frees whatever can be freed in all pools, but respecting
  * the minimum thresholds imposed by owners.
+ *
+ * <pool_ctx> is used when pool_gc2 is called to release resources to allocate
+ * an element in __pool_refill_alloc. It is important because <pool_ctx> is
+ * already locked, so we need to skip the lock here.
  */
-void pool_gc2();
+void pool_gc2(struct pool_head *pool_ctx);
 
 /*
  * This function destroys a pull by freeing it completely.
@@ -107,7 +116,7 @@ void *pool_destroy2(struct pool_head *pool);
  * available, otherwise returns NULL. No malloc() is attempted, and poisonning
  * is never performed. The purpose is to get the fastest possible allocation.
  */
-static inline void *pool_get_first(struct pool_head *pool)
+static inline void *__pool_get_first(struct pool_head *pool)
 {
        void *p;
 
@@ -122,6 +131,15 @@ static inline void *pool_get_first(struct pool_head *pool)
        return p;
 }
 
+static inline void *pool_get_first(struct pool_head *pool)
+{
+       void *ret;
+
+       SPIN_LOCK(POOL_LOCK, &pool->lock);
+       ret = __pool_get_first(pool);
+       SPIN_UNLOCK(POOL_LOCK, &pool->lock);
+       return ret;
+}
 /*
  * Returns a pointer to type <type> taken from the pool <pool_type> or
  * dynamically allocated. In the first case, <pool_type> is updated to point to
@@ -132,9 +150,10 @@ static inline void *pool_alloc_dirty(struct pool_head *pool)
 {
        void *p;
 
-       if ((p = pool_get_first(pool)) == NULL)
-               p = pool_refill_alloc(pool, 0);
-
+       SPIN_LOCK(POOL_LOCK, &pool->lock);
+       if ((p = __pool_get_first(pool)) == NULL)
+               p = __pool_refill_alloc(pool, 0);
+       SPIN_UNLOCK(POOL_LOCK, &pool->lock);
        return p;
 }
 
@@ -150,8 +169,10 @@ static inline void *pool_alloc2(struct pool_head *pool)
        p = pool_alloc_dirty(pool);
 #ifdef DEBUG_MEMORY_POOLS
        if (p) {
+               SPIN_LOCK(POOL_LOCK, &pool->lock);
                /* keep track of where the element was allocated from */
                *POOL_LINK(pool, p) = (void *)pool;
+               SPIN_UNLOCK(POOL_LOCK, &pool->lock);
        }
 #endif
        if (p && mem_poison_byte >= 0) {
@@ -173,6 +194,7 @@ static inline void *pool_alloc2(struct pool_head *pool)
 static inline void pool_free2(struct pool_head *pool, void *ptr)
 {
         if (likely(ptr != NULL)) {
+               SPIN_LOCK(POOL_LOCK, &pool->lock);
 #ifdef DEBUG_MEMORY_POOLS
                /* we'll get late corruption if we refill to the wrong pool or double-free */
                if (*POOL_LINK(pool, ptr) != (void *)pool)
@@ -181,10 +203,9 @@ static inline void pool_free2(struct pool_head *pool, void *ptr)
                *POOL_LINK(pool, ptr) = (void *)pool->free_list;
                 pool->free_list = (void *)ptr;
                 pool->used--;
+               SPIN_UNLOCK(POOL_LOCK, &pool->lock);
        }
 }
-
-
 #endif /* _COMMON_MEMORY_H */
 
 /*
index 080cb6f01a1f330f59de8179cf9c8d03cbad3bfe..07331898fb69a0ca1dfb66ccba0f8f8370f5cae2 100644 (file)
@@ -745,7 +745,7 @@ static void sig_soft_stop(struct sig_handler *sh)
 {
        soft_stop();
        signal_unregister_handler(sh);
-       pool_gc2();
+       pool_gc2(NULL);
 }
 
 /*
@@ -754,7 +754,7 @@ static void sig_soft_stop(struct sig_handler *sh)
 static void sig_pause(struct sig_handler *sh)
 {
        pause_proxies();
-       pool_gc2();
+       pool_gc2(NULL);
 }
 
 /*
@@ -818,7 +818,7 @@ static void dump(struct sig_handler *sh)
 {
        /* dump memory usage then free everything possible */
        dump_pools();
-       pool_gc2();
+       pool_gc2(NULL);
 }
 
 /* This function check if cfg_cfgfiles containes directories.
index 24e1df706c88a3531ee4f91b8d92d56d53ad0ef4..9313aa93e7d8251fb1a4424db3da78088cb2ee9c 100644 (file)
@@ -57,6 +57,8 @@ struct pool_head *create_pool(char *name, unsigned int size, unsigned int flags)
                size  = ((size + POOL_EXTRA + align - 1) & -align) - POOL_EXTRA;
        }
 
+       /* TODO: thread: we do not lock pool list for now because all pools are
+        * created during HAProxy startup (so before threads creation) */
        start = &pools;
        pool = NULL;
 
@@ -91,6 +93,7 @@ struct pool_head *create_pool(char *name, unsigned int size, unsigned int flags)
                LIST_ADDQ(start, &pool->list);
        }
        pool->users++;
+       SPIN_INIT(&pool->lock);
        return pool;
 }
 
@@ -102,7 +105,7 @@ struct pool_head *create_pool(char *name, unsigned int size, unsigned int flags)
  * A call to the garbage collector is performed at most once in case malloc()
  * returns an error, before returning NULL.
  */
-void *pool_refill_alloc(struct pool_head *pool, unsigned int avail)
+void *__pool_refill_alloc(struct pool_head *pool, unsigned int avail)
 {
        void *ptr = NULL;
        int failed = 0;
@@ -120,7 +123,7 @@ void *pool_refill_alloc(struct pool_head *pool, unsigned int avail)
                        if (failed)
                                return NULL;
                        failed++;
-                       pool_gc2();
+                       pool_gc2(pool);
                        continue;
                }
                if (++pool->allocated > avail)
@@ -136,7 +139,15 @@ void *pool_refill_alloc(struct pool_head *pool, unsigned int avail)
 #endif
        return ptr;
 }
+void *pool_refill_alloc(struct pool_head *pool, unsigned int avail)
+{
+       void *ptr;
 
+       SPIN_LOCK(POOL_LOCK, &pool->lock);
+       ptr = __pool_refill_alloc(pool, avail);
+       SPIN_UNLOCK(POOL_LOCK, &pool->lock);
+       return ptr;
+}
 /*
  * This function frees whatever can be freed in pool <pool>.
  */
@@ -146,6 +157,7 @@ void pool_flush2(struct pool_head *pool)
        if (!pool)
                return;
 
+       SPIN_LOCK(POOL_LOCK, &pool->lock);
        next = pool->free_list;
        while (next) {
                temp = next;
@@ -154,7 +166,7 @@ void pool_flush2(struct pool_head *pool)
                free(temp);
        }
        pool->free_list = next;
-
+       SPIN_UNLOCK(POOL_LOCK, &pool->lock);
        /* here, we should have pool->allocate == pool->used */
 }
 
@@ -162,18 +174,25 @@ void pool_flush2(struct pool_head *pool)
  * This function frees whatever can be freed in all pools, but respecting
  * the minimum thresholds imposed by owners. It takes care of avoiding
  * recursion because it may be called from a signal handler.
+ *
+ * <pool_ctx> is used when pool_gc2 is called to release resources to allocate
+ * an element in __pool_refill_alloc. It is important because <pool_ctx> is
+ * already locked, so we need to skip the lock here.
  */
-void pool_gc2()
+void pool_gc2(struct pool_head *pool_ctx)
 {
        static int recurse;
+       int cur_recurse = 0;
        struct pool_head *entry;
 
-       if (recurse++)
-               goto out;
+       if (recurse || !HA_ATOMIC_CAS(&recurse, &cur_recurse, 1))
+               return;
 
        list_for_each_entry(entry, &pools, list) {
                void *temp, *next;
                //qfprintf(stderr, "Flushing pool %s\n", entry->name);
+               if (entry != pool_ctx)
+                       SPIN_LOCK(POOL_LOCK, &entry->lock);
                next = entry->free_list;
                while (next &&
                       (int)(entry->allocated - entry->used) > (int)entry->minavail) {
@@ -183,9 +202,11 @@ void pool_gc2()
                        free(temp);
                }
                entry->free_list = next;
+               if (entry != pool_ctx)
+                       SPIN_UNLOCK(POOL_LOCK, &entry->lock);
        }
- out:
-       recurse--;
+
+       HA_ATOMIC_STORE(&recurse, 0);
 }
 
 /*
@@ -204,6 +225,7 @@ void *pool_destroy2(struct pool_head *pool)
                pool->users--;
                if (!pool->users) {
                        LIST_DEL(&pool->list);
+                       SPIN_DESTROY(&pool->lock);
                        free(pool);
                }
        }
@@ -220,6 +242,7 @@ void dump_pools_to_trash()
        allocated = used = nbpools = 0;
        chunk_printf(&trash, "Dumping pools usage. Use SIGQUIT to flush them.\n");
        list_for_each_entry(entry, &pools, list) {
+               SPIN_LOCK(POOL_LOCK, &entry->lock);
                chunk_appendf(&trash, "  - Pool %s (%d bytes) : %d allocated (%u bytes), %d used, %d failures, %d users%s\n",
                         entry->name, entry->size, entry->allocated,
                         entry->size * entry->allocated, entry->used, entry->failed,
@@ -228,6 +251,7 @@ void dump_pools_to_trash()
                allocated += entry->allocated * entry->size;
                used += entry->used * entry->size;
                nbpools++;
+               SPIN_UNLOCK(POOL_LOCK, &entry->lock);
        }
        chunk_appendf(&trash, "Total: %d pools, %lu bytes allocated, %lu used.\n",
                 nbpools, allocated, used);
index bcd586888cf740704f7e9ddb66f58827bf67a61c..53f886ec5b0087f92715562768d0291fd71554e6 100644 (file)
@@ -853,7 +853,7 @@ struct task *manage_proxy(struct task *t)
                                 p->id, p->fe_counters.cum_conn, p->be_counters.cum_conn);
                        stop_proxy(p);
                        /* try to free more memory */
-                       pool_gc2();
+                       pool_gc2(NULL);
                }
                else {
                        next = tick_first(next, p->stop_time);
@@ -870,7 +870,7 @@ struct task *manage_proxy(struct task *t)
        if (unlikely(stopping && p->state == PR_STSTOPPED && p->table.current)) {
                if (!p->table.syncing) {
                        stktable_trash_oldest(&p->table, p->table.current);
-                       pool_gc2();
+                       pool_gc2(NULL);
                }
                if (p->table.current) {
                        /* some entries still remain, let's recheck in one second */
index 13d9526522cb9ed1c65f1536afc100f323f85dbc..68f5d252b5ae853a36ff3cf47c15ce4527e7f1e7 100644 (file)
@@ -4793,7 +4793,7 @@ static int ssl_sock_init(struct connection *conn)
                conn->xprt_ctx = SSL_new(objt_server(conn->target)->ssl_ctx.ctx);
                if (!conn->xprt_ctx) {
                        if (may_retry--) {
-                               pool_gc2();
+                               pool_gc2(NULL);
                                goto retry_connect;
                        }
                        conn->err_code = CO_ER_SSL_NO_MEM;
@@ -4805,7 +4805,7 @@ static int ssl_sock_init(struct connection *conn)
                        SSL_free(conn->xprt_ctx);
                        conn->xprt_ctx = NULL;
                        if (may_retry--) {
-                               pool_gc2();
+                               pool_gc2(NULL);
                                goto retry_connect;
                        }
                        conn->err_code = CO_ER_SSL_NO_MEM;
@@ -4817,7 +4817,7 @@ static int ssl_sock_init(struct connection *conn)
                        SSL_free(conn->xprt_ctx);
                        conn->xprt_ctx = NULL;
                        if (may_retry--) {
-                               pool_gc2();
+                               pool_gc2(NULL);
                                goto retry_connect;
                        }
                        conn->err_code = CO_ER_SSL_NO_MEM;
@@ -4847,7 +4847,7 @@ static int ssl_sock_init(struct connection *conn)
                conn->xprt_ctx = SSL_new(objt_listener(conn->target)->bind_conf->initial_ctx);
                if (!conn->xprt_ctx) {
                        if (may_retry--) {
-                               pool_gc2();
+                               pool_gc2(NULL);
                                goto retry_accept;
                        }
                        conn->err_code = CO_ER_SSL_NO_MEM;
@@ -4859,7 +4859,7 @@ static int ssl_sock_init(struct connection *conn)
                        SSL_free(conn->xprt_ctx);
                        conn->xprt_ctx = NULL;
                        if (may_retry--) {
-                               pool_gc2();
+                               pool_gc2(NULL);
                                goto retry_accept;
                        }
                        conn->err_code = CO_ER_SSL_NO_MEM;
@@ -4871,7 +4871,7 @@ static int ssl_sock_init(struct connection *conn)
                        SSL_free(conn->xprt_ctx);
                        conn->xprt_ctx = NULL;
                        if (may_retry--) {
-                               pool_gc2();
+                               pool_gc2(NULL);
                                goto retry_accept;
                        }
                        conn->err_code = CO_ER_SSL_NO_MEM;