From 12098b900be205074160716c4d241fac2c458e7c Mon Sep 17 00:00:00 2001 From: Konstantin Osipov <kostja@tarantool.org> Date: Wed, 13 Nov 2013 17:23:02 +0400 Subject: [PATCH] Add a simple lock-free LIFO implementation to small allocators library. --- include/lib/small/lf_lifo.h | 115 ++++++++++++++++++++++++++++++++++++ test/unit/CMakeLists.txt | 1 + test/unit/lf_lifo.c | 65 ++++++++++++++++++++ test/unit/lf_lifo.result | 1 + 4 files changed, 182 insertions(+) create mode 100644 include/lib/small/lf_lifo.h create mode 100644 test/unit/lf_lifo.c create mode 100644 test/unit/lf_lifo.result diff --git a/include/lib/small/lf_lifo.h b/include/lib/small/lf_lifo.h new file mode 100644 index 0000000000..a725c57d12 --- /dev/null +++ b/include/lib/small/lf_lifo.h @@ -0,0 +1,115 @@ +#ifndef INCLUDES_TARANTOOL_LF_LIFO_H +#define INCLUDES_TARANTOOL_LF_LIFO_H +/* + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. 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 <COPYRIGHT HOLDER> ``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 + * <COPYRIGHT HOLDER> OR 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. + */ +#include <assert.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> + +/** + * Provide wrappers around gcc built-ins for now. + * These built-ins work with all numeric types - may not + * be the case when another implementation is used. + */ +#define atomic_cas(a, b, c) __sync_val_compare_and_swap(a, b, c) +#define atomic_add(a, b) __sync_add_and_fetch(a, b) + +/** + * A very primitive implementation of lock-free + * LIFO (last in first out, AKA stack, AKA single-linked + * list with head-only add and remove). + * + * It is only usable to store free pages of a memory allocator + * or similar, since it assumes that all addresses are aligned, + * and lower 16 bits of address can be used as a counter-based + * solution for ABA problem. + */ +struct lf_lifo { + void *next; +}; + +static inline unsigned short +aba_value(void *a) +{ + return (intptr_t) a & 0xffff; +} + +static inline struct lf_lifo * +lf_lifo(void *a) +{ + return (struct lf_lifo *) ((intptr_t) a & ~0xffff); +} + +static inline void +lf_lifo_init(struct lf_lifo *head) +{ + head->next = NULL; +} + +struct lf_lifo * +lf_lifo_push(struct lf_lifo *head, void *elem) +{ + assert(lf_lifo(elem) == elem); /* Aligned address. */ + do { + void *tail = head->next; + lf_lifo(elem)->next = tail; + /* + * Sic: add 1 thus let ABA value overflow, *then* + * coerce to unsigned short + */ + void *newhead = elem + aba_value(tail + 1); + if (atomic_cas(&head->next, tail, newhead) == tail) + return head; + } while (true); +} + +void * +lf_lifo_pop(struct lf_lifo *head) +{ + do { + void *tail = head->next; + struct lf_lifo *elem = lf_lifo(tail); + if (elem == NULL) + return NULL; + /* + * Discard the old tail's aba value, then save + * the old head's value in the tail. + * This way head's aba value grows monotonically + * regardless of the exact sequence of push/pop + * operations. + */ + void *newhead = ((void *) lf_lifo(elem->next) + + aba_value(tail)); + if (atomic_cas(&head->next, tail, newhead) == tail) + return elem; + } while (true); +} + +#endif /* INCLUDES_TARANTOOL_LF_LIFO_H */ diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 99b5aa00b1..21a0fd62c1 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -25,3 +25,4 @@ add_executable(mempool.test mempool.c) target_link_libraries(mempool.test small) add_executable(small_alloc.test small_alloc.c) target_link_libraries(small_alloc.test small) +add_executable(lf_lifo.test lf_lifo.c) diff --git a/test/unit/lf_lifo.c b/test/unit/lf_lifo.c new file mode 100644 index 0000000000..1d42a4fa4d --- /dev/null +++ b/test/unit/lf_lifo.c @@ -0,0 +1,65 @@ +#include "lib/small/lf_lifo.h" +#include "unit.h" +#include <sys/mman.h> + +static void * +mmap_aligned(size_t size) +{ + assert((size & (size - 1)) == 0); + void *map = mmap(NULL, 2 * size, + PROT_READ | PROT_WRITE, MAP_PRIVATE | + MAP_ANONYMOUS, -1, 0); + + /* Align the mapped address around slab size. */ + size_t offset = (intptr_t) map & (size - 1); + + if (offset != 0) { + munmap(map, size - offset); + map += size - offset; + munmap(map + size, offset); + } else { + /* The address is returned aligned. */ + munmap(map + size, size); + } + return map; +} + +#define MAP_SIZE 0x10000 + +int main() +{ + struct lf_lifo head; + void *val1 = mmap_aligned(MAP_SIZE); + void *val2 = mmap_aligned(MAP_SIZE); + void *val3 = mmap_aligned(MAP_SIZE); + lf_lifo_init(&head); + + fail_unless(lf_lifo_pop(&head) == NULL); + fail_unless(lf_lifo_pop(lf_lifo_push(&head, val1)) == val1); + fail_unless(lf_lifo_pop(lf_lifo_push(&head, val1)) == val1); + lf_lifo_push(lf_lifo_push(lf_lifo_push(&head, val1), val2), val3); + fail_unless(lf_lifo_pop(&head) == val3); + fail_unless(lf_lifo_pop(&head) == val2); + fail_unless(lf_lifo_pop(&head) == val1); + fail_unless(lf_lifo_pop(&head) == NULL); + + lf_lifo_init(&head); + + /* Test overflow of ABA counter. */ + + int i = 0; + do { + lf_lifo_push(&head, val1); + fail_unless(lf_lifo_pop(&head) == val1); + fail_unless(lf_lifo_pop(&head) == NULL); + i++; + } while (head.next != 0); + + munmap(val1, MAP_SIZE); + munmap(val2, MAP_SIZE); + munmap(val3, MAP_SIZE); + + printf("success\n"); + + return 0; +} diff --git a/test/unit/lf_lifo.result b/test/unit/lf_lifo.result new file mode 100644 index 0000000000..2e9ba477f8 --- /dev/null +++ b/test/unit/lf_lifo.result @@ -0,0 +1 @@ +success -- GitLab