499 lines
35 KiB
C
499 lines
35 KiB
C
/*
|
|
* M*LIB - Concurrent memory pool allocator
|
|
*
|
|
* Copyright (c) 2017-2019, Patrick Pelissier
|
|
* All rights reserved.
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* + Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* + Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#ifndef MSTARLIB_CONCURRENT_MEMPOOL_H
|
|
#define MSTARLIB_CONCURRENT_MEMPOOL_H
|
|
|
|
#include "m-core.h"
|
|
#include "m-atomic.h"
|
|
#include "m-genint.h"
|
|
|
|
/* Minimum number of nodes per group of nodes */
|
|
#define C_MEMPOOL_MIN_NODE_PER_GROUP 16
|
|
|
|
#define C_MEMPOOL_DEF(name, type_t) \
|
|
C_MEMPOOL_DEF_SINGLY_LIST(name, type_t) \
|
|
C_MEMPOOL_DEF_LF_QUEUE(name, type_t) \
|
|
C_MEMPOOL_DEF_LFMP_THREAD_MEMPOOL(name, type_t) \
|
|
C_MEMPOOL_DEF_SYSTEM_ALLOC(name, type_t) \
|
|
C_MEMPOOL_DEF_LF_MEMPOOL(name, type_t)
|
|
|
|
/* Classic Singly List without allocation */
|
|
#define C_MEMPOOL_DEF_SINGLY_LIST(name, type_t) \
|
|
\
|
|
typedef struct M_C(name, _slist_node_s) { \
|
|
struct M_C(name, _slist_node_s) *next; \
|
|
type_t data; \
|
|
} M_C(name, _slist_node_t); \
|
|
\
|
|
typedef struct M_C(name, _slist_node_s) *M_C(name, _slist_t)[1]; \
|
|
\
|
|
static inline void \
|
|
M_C(name, _slist_init)(M_C(name, _slist_t) list) \
|
|
{ \
|
|
*list = NULL; \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _slist_push)(M_C(name, _slist_t) list, \
|
|
M_C(name, _slist_node_t) *node) \
|
|
{ \
|
|
node->next = *list; \
|
|
*list = node; \
|
|
} \
|
|
\
|
|
static inline M_C(name, _slist_node_t) * \
|
|
M_C(name, _slist_pop)(M_C(name, _slist_t) list) \
|
|
{ \
|
|
assert (*list != NULL); \
|
|
M_C(name, _slist_node_t) *node = *list; \
|
|
*list = node->next; \
|
|
M_IF_DEBUG(node->next = NULL;) \
|
|
return node; \
|
|
} \
|
|
\
|
|
static inline bool \
|
|
M_C(name, _slist_empty_p)(M_C(name, _slist_t) list) \
|
|
{ \
|
|
return *list == NULL; \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _slist_move)(M_C(name, _slist_t) list, \
|
|
M_C(name, _slist_t) src) \
|
|
{ \
|
|
*list = *src; \
|
|
*src = NULL; \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _slist_clear)(M_C(name, _slist_t) list) \
|
|
{ \
|
|
M_C(name, _slist_node_t) *it = *list, *next; \
|
|
while (it) { \
|
|
next = it->next; \
|
|
M_MEMORY_DEL(it); \
|
|
it = next; \
|
|
} \
|
|
*list = NULL; \
|
|
} \
|
|
|
|
|
|
/* Lock Free free queue list (not generic one) of lists without allocation
|
|
Based on Michael & Scott Lock Free Queue List algorithm.
|
|
Each list is considered empty if there is only one node within.
|
|
Each list has its own unique NIL ptr in order to avoid issues when migrating a node from a Q to another
|
|
*/
|
|
/* TODO: Optimize alignement to reduce memory consumption. */
|
|
#define C_MEMPOOL_DEF_LF_QUEUE(name, type_t) \
|
|
\
|
|
typedef struct M_C(name, _lf_node_s) { \
|
|
_Atomic(struct M_C(name, _lf_node_s) *) next; \
|
|
atomic_ulong cpt; \
|
|
M_C(name, _slist_t) list; \
|
|
} M_C(name, _lf_node_t); \
|
|
\
|
|
typedef struct M_C(name, _lflist_s) { \
|
|
_Atomic(M_C(name, _lf_node_t) *) head; \
|
|
char align1[M_ALIGN_FOR_CACHELINE_EXCLUSION]; \
|
|
_Atomic(M_C(name, _lf_node_t) *) tail; \
|
|
char align2[M_ALIGN_FOR_CACHELINE_EXCLUSION]; \
|
|
M_C(name, _lf_node_t) nil; \
|
|
} M_C(name, _lflist_t)[1]; \
|
|
\
|
|
static inline void \
|
|
M_C(name, _lflist_init)(M_C(name, _lflist_t) list, \
|
|
M_C(name, _lf_node_t) *node) \
|
|
{ \
|
|
atomic_init(&list->head, node); \
|
|
atomic_init(&list->tail, node); \
|
|
atomic_store_explicit(&node->next, &list->nil, memory_order_relaxed); \
|
|
} \
|
|
\
|
|
static inline bool \
|
|
M_C(name, _lflist_empty_p)(M_C(name, _lflist_t) list) \
|
|
{ \
|
|
return atomic_load(&list->tail) == atomic_load(&list->head); \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _lflist_push)(M_C(name, _lflist_t) list, \
|
|
M_C(name, _lf_node_t) *node, m_backoff_t bkoff) \
|
|
{ \
|
|
M_C(name, _lf_node_t) *tail; \
|
|
M_C(name, _lf_node_t) *next; \
|
|
\
|
|
atomic_store_explicit(&node->next, &list->nil, memory_order_relaxed); \
|
|
m_backoff_reset(bkoff); \
|
|
while (true) { \
|
|
tail = atomic_load(&list->tail); \
|
|
next = atomic_load_explicit(&tail->next, memory_order_acquire); \
|
|
if (M_UNLIKELY(next != &list->nil)) { \
|
|
/* Tail was not pointing to the last node \
|
|
Try to swing Tail to the next node */ \
|
|
atomic_compare_exchange_weak_explicit(&list->tail, \
|
|
&tail, next, \
|
|
memory_order_release, \
|
|
memory_order_relaxed); \
|
|
} else { \
|
|
/* Try to link node at the end of the linked list */ \
|
|
if (atomic_compare_exchange_strong_explicit(&tail->next, \
|
|
&next, node, \
|
|
memory_order_release, \
|
|
memory_order_relaxed)) \
|
|
break; \
|
|
m_backoff_wait(bkoff); \
|
|
} \
|
|
} \
|
|
/* Enqueue is done. Try to swing Tail to the inserted node \
|
|
If it fails, someone else will do it or has already did it. */ \
|
|
atomic_compare_exchange_strong_explicit(&list->tail, &tail, node, \
|
|
memory_order_acq_rel, \
|
|
memory_order_relaxed); \
|
|
} \
|
|
\
|
|
static inline M_C(name, _lf_node_t) * \
|
|
M_C(name, _lflist_pop)(M_C(name, _lflist_t) list, m_backoff_t bkoff) \
|
|
{ \
|
|
M_C(name, _lf_node_t) *head; \
|
|
M_C(name, _lf_node_t) *tail; \
|
|
M_C(name, _lf_node_t) *next; \
|
|
\
|
|
m_backoff_reset(bkoff); \
|
|
while (true) { \
|
|
head = atomic_load(&list->head); \
|
|
tail = atomic_load(&list->tail); \
|
|
next = atomic_load(&head->next); \
|
|
/* Are head, tail, and next consistent?*/ \
|
|
if (M_LIKELY(head == \
|
|
atomic_load_explicit(&list->head, memory_order_relaxed))) \
|
|
{ \
|
|
/* Is queue empty or Tail falling behind? */ \
|
|
if (head == tail) { \
|
|
/* Is queue empty? */ \
|
|
if (next == &list->nil) \
|
|
return NULL; \
|
|
/* Tail is falling behind. Try to advance it */ \
|
|
atomic_compare_exchange_strong_explicit(&list->tail, &tail, \
|
|
next, \
|
|
memory_order_release, \
|
|
memory_order_relaxed); \
|
|
} else { \
|
|
/* Try to swing Head to the next node */ \
|
|
if (atomic_compare_exchange_strong_explicit(&list->head, \
|
|
&head, next, \
|
|
memory_order_release, \
|
|
memory_order_relaxed)) { \
|
|
break; \
|
|
} \
|
|
m_backoff_wait(bkoff); \
|
|
} \
|
|
} \
|
|
} \
|
|
/* dequeue returns an element that becomes the new dummy element (the new head), \
|
|
and the former dummy element (the former head) is removed: \
|
|
Since we want a link of free list, and we don't care about the content itsef, \
|
|
provided that the node we return is older than the one we should return, \
|
|
Therefore, we return the previous dummy head. \
|
|
As such, it is not the original MSqueue algorithm. */ \
|
|
M_IF_DEBUG(atomic_store(&head->next, (M_C(name, _lf_node_t) *) 0);) \
|
|
return head; \
|
|
} \
|
|
\
|
|
static inline M_C(name, _lf_node_t) * \
|
|
M_C(name, _lflist_pop_if)(M_C(name, _lflist_t) list, \
|
|
unsigned long ticket, m_backoff_t bkoff) \
|
|
{ \
|
|
M_C(name, _lf_node_t) *head; \
|
|
M_C(name, _lf_node_t) *tail; \
|
|
M_C(name, _lf_node_t) *next; \
|
|
\
|
|
m_backoff_reset(bkoff); \
|
|
while (true) { \
|
|
head = atomic_load(&list->head); \
|
|
tail = atomic_load(&list->tail); \
|
|
next = atomic_load(&head->next); \
|
|
if (M_LIKELY(head == atomic_load_explicit(&list->head, memory_order_relaxed))) \
|
|
{ \
|
|
if (head == tail) { \
|
|
if (next == &list->nil) \
|
|
return NULL; \
|
|
atomic_compare_exchange_strong_explicit(&list->tail, &tail, next, \
|
|
memory_order_release, \
|
|
memory_order_relaxed); \
|
|
} else { \
|
|
/* Test if the node is old enought to be popped */ \
|
|
if (next->cpt >= ticket) \
|
|
return NULL; \
|
|
/* Try to swing Head to the next node */ \
|
|
if (atomic_compare_exchange_strong_explicit(&list->head, \
|
|
&head, next, \
|
|
memory_order_release, \
|
|
memory_order_relaxed)) { \
|
|
break; \
|
|
} \
|
|
m_backoff_wait(bkoff); \
|
|
} \
|
|
} \
|
|
} \
|
|
M_IF_DEBUG(atomic_store(&head->next, (M_C(name, _lf_node_t) *) 0);) \
|
|
return head; \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _lflist_clear)(M_C(name, _lflist_t) list) \
|
|
{ \
|
|
m_backoff_t bkoff; \
|
|
m_backoff_init(bkoff); \
|
|
while (true) { \
|
|
M_C(name, _lf_node_t) *node = M_C(name, _lflist_pop)(list, bkoff); \
|
|
if (node == NULL) break; \
|
|
M_C(name, _lf_node_t) *next = node->next; \
|
|
M_C(name, _slist_clear)(node->list); \
|
|
M_MEMORY_DEL(node); \
|
|
node = next; \
|
|
} \
|
|
/* Dummy node to free too */ \
|
|
M_C(name, _lf_node_t) *dummy; \
|
|
dummy = atomic_load_explicit(&list->head, memory_order_relaxed); \
|
|
M_C(name, _slist_clear)(dummy->list); \
|
|
M_MEMORY_DEL(dummy); \
|
|
} \
|
|
|
|
/* System node allocator: request memory to the system.
|
|
As such it is a non Lock-Free path. */
|
|
#define C_MEMPOOL_DEF_SYSTEM_ALLOC(name, type_t) \
|
|
\
|
|
static inline M_C(name, _lf_node_t) * \
|
|
M_C(name, _alloc_node)(unsigned int initial) \
|
|
{ \
|
|
M_C(name, _lf_node_t) * node; \
|
|
node = M_MEMORY_ALLOC(M_C(name, _lf_node_t)); \
|
|
if (M_UNLIKELY(node == NULL)) { \
|
|
M_MEMORY_FULL(sizeof(M_C(name, _lf_node_t))); \
|
|
} \
|
|
atomic_init(&node->next, (M_C(name, _lf_node_t) *) 0); \
|
|
atomic_init(&node->cpt, 0UL); \
|
|
M_C(name, _slist_init)(node->list); \
|
|
for(unsigned i = 0; i < initial; i++) { \
|
|
M_C(name, _slist_node_t) *n; \
|
|
n = M_MEMORY_ALLOC(M_C(name, _slist_node_t)); \
|
|
if (M_UNLIKELY(n == NULL)) { \
|
|
M_MEMORY_FULL(sizeof(M_C(name, _lf_node_t))); \
|
|
} \
|
|
M_C(name, _slist_push)(node->list, n); \
|
|
} \
|
|
return node; \
|
|
} \
|
|
|
|
#define C_MEMPOOL_DEF_LFMP_THREAD_MEMPOOL(name, type_t) \
|
|
\
|
|
typedef struct M_C(name, _lfmp_thread_t) { \
|
|
atomic_ulong ticket; \
|
|
m_backoff_t bkoff; \
|
|
M_C(name, _slist_t) free; \
|
|
M_C(name, _slist_t) to_be_reclaimed; \
|
|
char align1[M_ALIGN_FOR_CACHELINE_EXCLUSION]; \
|
|
} M_C(name, _lfmp_thread_t); \
|
|
\
|
|
static inline void \
|
|
M_C(name, _lfmp_thread_init)(M_C(name, _lfmp_thread_t) *t) \
|
|
{ \
|
|
atomic_init(&t->ticket, ULONG_MAX); \
|
|
m_backoff_init(t->bkoff); \
|
|
M_C(name, _slist_init)(t->free); \
|
|
M_C(name, _slist_init)(t->to_be_reclaimed); \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _lfmp_thread_clear)(M_C(name, _lfmp_thread_t) *t) \
|
|
{ \
|
|
assert(M_C(name, _slist_empty_p)(t->to_be_reclaimed)); \
|
|
M_C(name, _slist_clear)(t->free); \
|
|
M_C(name, _slist_clear)(t->to_be_reclaimed); \
|
|
m_backoff_clear(t->bkoff); \
|
|
} \
|
|
|
|
/* NOTE: once a node is deleted, its data are kept readable until the future GC */
|
|
#define C_MEMPOOL_DEF_LF_MEMPOOL(name, type_t) \
|
|
\
|
|
typedef struct M_C(name, _s) { \
|
|
atomic_ulong ticket; \
|
|
unsigned initial; \
|
|
unsigned max_thread; \
|
|
genint_t thread_alloc; \
|
|
M_C(name, _lfmp_thread_t) *thread_data; \
|
|
M_C(name, _lflist_t) free; \
|
|
M_C(name, _lflist_t) to_be_reclaimed; \
|
|
M_C(name, _lflist_t) empty; \
|
|
} M_C(name, _t)[1]; \
|
|
\
|
|
typedef int M_C(name, _tid_t); \
|
|
\
|
|
static inline void \
|
|
M_C(name, _init)(M_C(name, _t) mem, unsigned long max_thread, \
|
|
unsigned init_node_count, unsigned init_group_count) \
|
|
{ \
|
|
atomic_init(&mem->ticket, 0UL); \
|
|
genint_init(mem->thread_alloc, max_thread); \
|
|
mem->initial = M_MAX(C_MEMPOOL_MIN_NODE_PER_GROUP, init_node_count); \
|
|
mem->thread_data = M_MEMORY_REALLOC(M_C(name, _lfmp_thread_t), NULL, max_thread); \
|
|
if (mem->thread_data == NULL) { \
|
|
M_MEMORY_FULL(max_thread * sizeof(M_C(name, _lfmp_thread_t))); \
|
|
} \
|
|
for(unsigned i = 0; i < max_thread;i++) { \
|
|
M_C(name, _lfmp_thread_init)(&mem->thread_data[i]); \
|
|
} \
|
|
M_C(name, _lflist_init)(mem->free, M_C(name, _alloc_node)(init_node_count)); \
|
|
M_C(name, _lflist_init)(mem->to_be_reclaimed, M_C(name, _alloc_node)(init_node_count)); \
|
|
M_C(name, _lflist_init)(mem->empty, M_C(name, _alloc_node)(0)); \
|
|
for(unsigned i = 1; i < init_group_count; i++) { \
|
|
M_C(name, _lflist_push)(mem->free, M_C(name, _alloc_node)(init_node_count), \
|
|
mem->thread_data[0].bkoff); \
|
|
M_C(name, _lflist_push)(mem->empty, M_C(name, _alloc_node)(0), \
|
|
mem->thread_data[0].bkoff); \
|
|
} \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _clear)(M_C(name, _t) mem) \
|
|
{ \
|
|
for(unsigned i = 0; i < mem->max_thread;i++) { \
|
|
M_C(name, _lfmp_thread_clear)(&mem->thread_data[i]); \
|
|
} \
|
|
M_C(name, _lflist_clear)(mem->empty); \
|
|
M_C(name, _lflist_clear)(mem->free); \
|
|
assert(M_C(name, _lflist_empty_p)(mem->to_be_reclaimed)); \
|
|
M_C(name, _lflist_clear)(mem->to_be_reclaimed); \
|
|
genint_clear(mem->thread_alloc); \
|
|
} \
|
|
\
|
|
static inline M_C(name, _tid_t) \
|
|
M_C(name, _attach_thread)(M_C(name, _t) mem) \
|
|
{ \
|
|
return genint_pop(mem->thread_alloc); \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _detach_thread)(M_C(name, _t) mem, M_C(name, _tid_t) id) \
|
|
{ \
|
|
assert(M_C(name, _slist_empty_p)(mem->thread_data[id].to_be_reclaimed)); \
|
|
assert(atomic_load(&mem->thread_data[id].ticket) == ULONG_MAX); \
|
|
genint_push(mem->thread_alloc, id); \
|
|
} \
|
|
\
|
|
static inline type_t * \
|
|
M_C(name, _new)(M_C(name, _t) mem, M_C(name, _tid_t) id) \
|
|
{ \
|
|
M_C(name, _slist_node_t) *snode; \
|
|
M_C(name, _lf_node_t) *node; \
|
|
while (true) { \
|
|
/* Fast & likely path where we access the thread pool of nodes */ \
|
|
if (M_LIKELY(!M_C(name, _slist_empty_p)(mem->thread_data[id].free))) { \
|
|
snode = M_C(name, _slist_pop)(mem->thread_data[id].free); \
|
|
return &snode->data; \
|
|
} \
|
|
/* Request a group node to the freelist of groups */ \
|
|
node = M_C(name, _lflist_pop)(mem->free, mem->thread_data[id].bkoff); \
|
|
if (M_UNLIKELY (node == NULL)) { \
|
|
/* Request a new group to the system. Non Lock Free path */ \
|
|
assert(mem->initial > 0); \
|
|
node = M_C(name, _alloc_node)(mem->initial); \
|
|
assert(!M_C(name, _slist_empty_p)(node->list)); \
|
|
} \
|
|
M_C(name, _slist_move)(mem->thread_data[id].free, node->list); \
|
|
/* Push back the empty group */ \
|
|
assert (M_C(name, _slist_empty_p)(node->list)); \
|
|
M_C(name, _lflist_push)(mem->empty, node, mem->thread_data[id].bkoff); \
|
|
} \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _del)(M_C(name, _t) mem, type_t *d, M_C(name, _tid_t) id) \
|
|
{ \
|
|
M_C(name, _slist_node_t) *snode; \
|
|
assert( d != NULL); \
|
|
snode = M_TYPE_FROM_FIELD(M_C(name, _slist_node_t), d, type_t, data); \
|
|
M_C(name, _slist_push)(mem->thread_data[id].to_be_reclaimed, snode); \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _awake)(M_C(name, _t) mem, M_C(name, _tid_t) id) \
|
|
{ \
|
|
unsigned long t = atomic_fetch_add(&mem->ticket, 1UL) + 1; \
|
|
atomic_store(&mem->thread_data[id].ticket, t); \
|
|
assert(M_C(name, _slist_empty_p)(mem->thread_data[id].to_be_reclaimed)); \
|
|
} \
|
|
\
|
|
static inline unsigned long \
|
|
M_C(name, _int_min_ticket)(M_C(name, _t) mem) \
|
|
{ \
|
|
unsigned long min = atomic_load(&mem->thread_data[0].ticket); \
|
|
for(unsigned i = 1; i < mem->max_thread; i++) { \
|
|
unsigned long t = atomic_load(&mem->thread_data[i].ticket); \
|
|
min = M_MIN(t, min); \
|
|
} \
|
|
return min; \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _int_gc_on_sleep)(M_C(name, _t) mem, M_C(name, _tid_t) id) \
|
|
{ \
|
|
const unsigned long min_ticket = M_C(name, _int_min_ticket)(mem); \
|
|
/* Perform a GC of the freelist of nodes */ \
|
|
while (true) { \
|
|
M_C(name, _lf_node_t) *node; \
|
|
node = M_C(name, _lflist_pop_if)(mem->to_be_reclaimed, \
|
|
min_ticket, mem->thread_data[id].bkoff); \
|
|
if (node == NULL) break; \
|
|
M_C(name, _lflist_push)(mem->free, node, mem->thread_data[id].bkoff); \
|
|
} \
|
|
} \
|
|
\
|
|
static inline void \
|
|
M_C(name, _sleep)(M_C(name, _t) mem, M_C(name, _tid_t) id) \
|
|
{ \
|
|
/* Move the local nodes to be reclaimed to the thread into the global pool */ \
|
|
if (!M_C(name, _slist_empty_p)(mem->thread_data[id].to_be_reclaimed)) { \
|
|
M_C(name, _lf_node_t) *node; \
|
|
node = M_C(name, _lflist_pop)(mem->empty, mem->thread_data[id].bkoff); \
|
|
if (M_UNLIKELY (node == NULL)) { \
|
|
node = M_C(name, _alloc_node)(0); \
|
|
} \
|
|
assert(M_C(name, _slist_empty_p)(node->list)); \
|
|
M_C(name, _slist_move)(node->list, mem->thread_data[id].to_be_reclaimed); \
|
|
node->cpt = atomic_load(&mem->ticket); \
|
|
M_C(name, _lflist_push)(mem->to_be_reclaimed, node, mem->thread_data[id].bkoff); \
|
|
} \
|
|
/* Increase life time of the thread */ \
|
|
unsigned long t = atomic_fetch_add(&mem->ticket, 1UL) + 1; \
|
|
atomic_store(&mem->thread_data[id].ticket, t); \
|
|
/* Perform a garbage collect */ \
|
|
M_C(name, _int_gc_on_sleep)(mem, id); \
|
|
/* Sleep the thread */ \
|
|
atomic_store(&mem->thread_data[id].ticket, ULONG_MAX); \
|
|
} \
|
|
|
|
#endif
|