From e41501456c84b11877460f9421d6a66e7e8bb754 Mon Sep 17 00:00:00 2001 From: Alexandr <a.lyapunov@corp.mail.ru> Date: Wed, 29 Oct 2014 11:41:20 +0300 Subject: [PATCH] added forgotten files to quota patch --- src/lib/small/quota.h | 183 +++++++++++++++++++++++++++++++++++++++++ test/unit/quota.cc | 95 +++++++++++++++++++++ test/unit/quota.result | 6 ++ 3 files changed, 284 insertions(+) create mode 100644 src/lib/small/quota.h create mode 100644 test/unit/quota.cc create mode 100644 test/unit/quota.result diff --git a/src/lib/small/quota.h b/src/lib/small/quota.h new file mode 100644 index 0000000000..cf9faf2c64 --- /dev/null +++ b/src/lib/small/quota.h @@ -0,0 +1,183 @@ +#ifndef INCLUDES_TARANTOOL_SMALL_QUOTA_H +#define INCLUDES_TARANTOOL_SMALL_QUOTA_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 <stddef.h> +#include <stdlib.h> +#include <stdint.h> +#include <assert.h> + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +enum { + QUOTA_MAX_ALLOC = 0xFFFFFFFFull * 1024u, + QUOTA_GRANULARITY = 1024 +}; + +/** A basic limit on memory usage */ +struct quota { + /* high order dword is total available memory and low order dword + * - currently used memory both in QUOTA_GRANULARITY units + */ + uint64_t value; +}; + +/** + * Initialize quota with gived memory limit + */ +static inline void +quota_init(struct quota *quota, size_t total) +{ + uint64_t new_total_in_granul = (total + (QUOTA_GRANULARITY - 1)) + / QUOTA_GRANULARITY; + quota->value = new_total_in_granul << 32; +} + +/** + * 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. + * Private use only. + */ +#define atomic_cas(a, b, c) __sync_val_compare_and_swap(a, b, c) + +/** + * Get current quota limit + */ +static inline size_t +quota_get_total(const struct quota *quota) +{ + return (quota->value >> 32) * QUOTA_GRANULARITY; +} + +/** + * Get current quota usage + */ +static inline size_t +quota_get_used(const struct quota *quota) +{ + return (quota->value & 0xFFFFFFFFu) * QUOTA_GRANULARITY; +} + +static inline void +quota_get_total_and_used(struct quota *quota, size_t *total, size_t *used) +{ + uint64_t value = quota->value; + *total = (value >> 32) * QUOTA_GRANULARITY; + *used = (value & 0xFFFFFFFFu) * QUOTA_GRANULARITY; +} + +/** + * Set quota memory limit. + * returns 0 on success + * returns -1 on error - if it's not possible to decrease limit + * due to greater current usage + */ +static inline int +quota_set(struct quota *quota, size_t new_total) +{ + uint32_t new_total_in_granul = (new_total + (QUOTA_GRANULARITY - 1)) + / QUOTA_GRANULARITY; + while (1) { + uint64_t old_value = quota->value; + /* uint32_t cur_total = old_value >> 32; */ + uint32_t cur_used = old_value & 0xFFFFFFFFu; + if (new_total_in_granul < cur_used) + return -1; + uint64_t new_value = (((uint64_t)new_total_in_granul) << 32) + | cur_used; + if (atomic_cas("a->value, old_value, new_value) == old_value) + break; + } + return 0; +} + +/** + * Use up a quota + * returns 0 on success + * returns -1 on error - if quota limit reached + */ +static inline int +quota_use(struct quota *quota, size_t size) +{ + uint32_t size_in_granul = (size + (QUOTA_GRANULARITY - 1)) + / QUOTA_GRANULARITY; + assert(size_in_granul); + while (1) { + uint64_t old_value = quota->value; + uint32_t cur_total = old_value >> 32; + uint32_t old_used = old_value & 0xFFFFFFFFu; + + uint32_t new_used = old_used + size_in_granul; + assert(new_used > old_used); + + if (new_used > cur_total) + return -1; + + uint64_t new_value = (((uint64_t)cur_total) << 32) + | new_used; + + if (atomic_cas("a->value, old_value, new_value) == old_value) + break; + } + return 0; +} + +/** Release used memory */ +static inline void +quota_release(struct quota *quota, size_t size) +{ + uint32_t size_in_granul = (size + (QUOTA_GRANULARITY - 1)) + / QUOTA_GRANULARITY; + assert(size_in_granul); + while (1) { + uint64_t old_value = quota->value; + uint32_t cur_total = old_value >> 32; + uint32_t old_used = old_value & 0xFFFFFFFFu; + + assert(size_in_granul <= old_used); + uint32_t new_used = old_used - size_in_granul; + + uint64_t new_value = (((uint64_t)cur_total) << 32) + | new_used; + + if (atomic_cas("a->value, old_value, new_value) == old_value) + break; + } +} + +#undef atomic_cas + +#if defined(__cplusplus) +} /* extern "C" { */ +#endif /* defined(__cplusplus) */ +#endif /* INCLUDES_TARANTOOL_SMALL_QUOTA_H */ diff --git a/test/unit/quota.cc b/test/unit/quota.cc new file mode 100644 index 0000000000..301d2b5c77 --- /dev/null +++ b/test/unit/quota.cc @@ -0,0 +1,95 @@ +#include "small/quota.h" + +#include <pthread.h> +#include "test.h" + +struct quota quota; + +const size_t THREAD_CNT = 10; +const size_t RUN_CNT = 128 * 1024; + +struct thread_data { + long use_change; + long last_lim_set; + long use_change_success; + long lim_change_success; +}; + +pthread_t threads[THREAD_CNT]; +thread_data datum[THREAD_CNT]; + +void *thread_routine(void *vparam) +{ + struct thread_data *data = (struct thread_data *)vparam; + size_t check_fail_count = 0; + bool allocated = false; + size_t allocated_size = 0; + for (size_t i = 0; i < RUN_CNT; i++) { + { + size_t total, used; + quota_get_total_and_used("a, &total, &used); + if (used > total) + check_fail_count++; + } + long rnd = ((rand() & 0xFFFFFFF) + 1) * QUOTA_GRANULARITY; + int succ = quota_set("a, (size_t)rnd); + if (succ == 0) { + data->last_lim_set = rnd; + data->lim_change_success++; + } + if (allocated) { + quota_release("a, allocated_size); + allocated = false; + data->use_change = 0; + data->use_change_success++; + } else { + allocated_size = (((long)rand()) & 0xFFFFFFl) + 1; + if (quota_use("a, allocated_size) == 0) { + allocated = true; + data->use_change = allocated_size; + data->use_change_success++; + } + } + } + return (void *)check_fail_count; +} + +int +main(int n, char **a) +{ + (void)n; + (void)a; + quota_init("a, 0); + + plan(5); + + for (size_t i = 0; i < THREAD_CNT; i++) { + pthread_create(threads + i, 0, thread_routine, (void *)(datum + i)); + } + + size_t check_fail_count = 0; + for (size_t i = 0; i < THREAD_CNT; i++) { + void *ret; + check_fail_count += (size_t)pthread_join(threads[i], &ret); + } + + bool one_set_successed = false; + size_t total_alloc = 0; + long set_success_count = 0; + long use_success_count = 0; + for (size_t i = 0; i < THREAD_CNT; i++) { + if (datum[i].last_lim_set == quota_get_total("a)) + one_set_successed = true; + total_alloc += ((datum[i].use_change + QUOTA_GRANULARITY - 1) / QUOTA_GRANULARITY) * QUOTA_GRANULARITY; + use_success_count += datum[i].use_change_success; + set_success_count += datum[i].lim_change_success; + } + + ok(check_fail_count == 0, "no fails detected"); + ok(one_set_successed, "one of thread limit set is final"); + ok(total_alloc == quota_get_used("a), "total alloc match"); + ok(use_success_count > THREAD_CNT * RUN_CNT * .9, "uses are mosly successful"); + ok(set_success_count > THREAD_CNT * RUN_CNT * .9, "sets are mosly successful"); + + return check_plan(); +} diff --git a/test/unit/quota.result b/test/unit/quota.result new file mode 100644 index 0000000000..84fddb5dab --- /dev/null +++ b/test/unit/quota.result @@ -0,0 +1,6 @@ +1..5 +ok 1 - no fails detected +ok 2 - one of thread limit set is final +ok 3 - total alloc match +ok 4 - uses are mosly successful +ok 5 - sets are mosly successful -- GitLab