From: Ripduman Sohan <[email protected]> This patch aims to allow multiple threads to make progress concurrently. It does this by modifying the code in 3 main ways:
1. The global cache_lock is relegated to the associative hash store and all accesses to the hashtable are protected by this lock. 2. Introduction of an item (datastructure) LRU lock; used to ensure safe concurrent access to LRU list operations. 3. Most importantly, introduction of an "item mutex" datastructure. Only one thread corresponding to a named item can progress at any one time. Item (3) needs further explaining. By removing the global cache_lock it is possible for independent threads to concurrently perform operations on the same object (as defined by key). So, for example, two threads could potentially try and delete the same object concurrently. There are two ways to ensure such a scenario executes correctly; (1) design the LRU list and datastructures such that reference counting is absolutely atomic and the multiple deletes are idempotent or (2) serialize accesses to the object - this way there are no concurrent manipulations of a given object. While (1) is the most correct and scalable manner of handling concurrent access to a given object the current codebase will require significant changes (including testing) to implement such a design. In the interests of minimising changes to the current codebase I implemented a variant of (2). Only a single thread operating on a named item can make progress in the data store. In practice I don't expect this to be a major problem. But really, someone less lazy should implement (1). Signed-off-by: Ripduman Sohan <[email protected]> --- Makefile.am | 2 +- daemon/memcached.c | 1 + engines/default_engine/assoc.c | 11 ++ engines/default_engine/default_engine.c | 13 +++ engines/default_engine/default_engine.h | 5 + engines/default_engine/items.c | 176 +++++++++++++++++++++++++------ engines/default_engine/items.h | 17 +++ include/memcached/config_parser.h | 4 +- utilities/config_parser.c | 12 ++ 9 files changed, 205 insertions(+), 36 deletions(-) diff --git a/Makefile.am b/Makefile.am index b456398..41b7277 100644 --- a/Makefile.am +++ b/Makefile.am @@ -150,7 +150,7 @@ default_engine_la_SOURCES= engines/default_engine/assoc.c \ engines/default_engine/slabs.c \ engines/default_engine/slabs.h -default_engine_la_CPPFLAGS = $(CPPFLAGS) -I$(top_srcdir)/engines/default_engine +default_engine_la_CPPFLAGS = $(CPPFLAGS) -I$(top_srcdir)/engines/default_engine -I$(top_srcdir)/daemon default_engine_la_DEPENDENCIES= libmemcached_utilities.la default_engine_la_LIBADD= libmemcached_utilities.la $(LIBM) default_engine_la_LDFLAGS= -avoid-version -shared -module -no-undefined diff --git a/daemon/memcached.c b/daemon/memcached.c index 8e0bedd..a2b1519 100644 --- a/daemon/memcached.c +++ b/daemon/memcached.c @@ -7093,6 +7093,7 @@ int main (int argc, char **argv) { " Set this value to the number of cores in" " your machine or less.\n"); } + old_opts += sprintf(old_opts, "num_threads=%u;", settings.num_threads); break; case 'D': settings.prefix_delimiter = optarg[0]; diff --git a/engines/default_engine/assoc.c b/engines/default_engine/assoc.c index 823adbc..dc4c09c 100644 --- a/engines/default_engine/assoc.c +++ b/engines/default_engine/assoc.c @@ -26,6 +26,8 @@ hash_item *assoc_find(struct default_engine *engine, uint32_t hash, const char * hash_item *it; unsigned int oldbucket; + pthread_mutex_lock(&engine->cache_lock); + if (engine->assoc.expanding && (oldbucket = (hash & hashmask(engine->assoc.hashpower - 1))) >= engine->assoc.expand_bucket) { @@ -45,6 +47,7 @@ hash_item *assoc_find(struct default_engine *engine, uint32_t hash, const char * ++depth; } MEMCACHED_ASSOC_FIND(key, nkey, depth); + pthread_mutex_unlock(&engine->cache_lock); return ret; } @@ -115,6 +118,8 @@ int assoc_insert(struct default_engine *engine, uint32_t hash, hash_item *it) { assert(assoc_find(engine, hash, item_get_key(it), it->nkey) == 0); /* shouldn't have duplicately named things defined */ + pthread_mutex_lock(&engine->cache_lock); + if (engine->assoc.expanding && (oldbucket = (hash & hashmask(engine->assoc.hashpower - 1))) >= engine->assoc.expand_bucket) { @@ -131,10 +136,14 @@ int assoc_insert(struct default_engine *engine, uint32_t hash, hash_item *it) { } MEMCACHED_ASSOC_INSERT(item_get_key(it), it->nkey, engine->assoc.hash_items); + pthread_mutex_unlock(&engine->cache_lock); + return 1; } void assoc_delete(struct default_engine *engine, uint32_t hash, const char *key, const size_t nkey) { + pthread_mutex_lock(&engine->cache_lock); + hash_item **before = _hashitem_before(engine, hash, key, nkey); if (*before) { @@ -147,8 +156,10 @@ void assoc_delete(struct default_engine *engine, uint32_t hash, const char *key, nxt = (*before)->h_next; (*before)->h_next = 0; /* probably pointless, but whatever. */ *before = nxt; + pthread_mutex_unlock(&engine->cache_lock); return; } + pthread_mutex_unlock(&engine->cache_lock); /* Note: we never actually get here. the callers don't delete things they can't find. */ assert(*before != 0); diff --git a/engines/default_engine/default_engine.c b/engines/default_engine/default_engine.c index e877ecc..0be4191 100644 --- a/engines/default_engine/default_engine.c +++ b/engines/default_engine/default_engine.c @@ -196,6 +196,9 @@ ENGINE_ERROR_CODE create_instance(uint64_t interface, .slabs = { .lock = PTHREAD_MUTEX_INITIALIZER }, + .items = { + .lock = PTHREAD_MUTEX_INITIALIZER, + }, .cache_lock = PTHREAD_MUTEX_INITIALIZER, .stats = { .lock = PTHREAD_MUTEX_INITIALIZER, @@ -210,6 +213,7 @@ ENGINE_ERROR_CODE create_instance(uint64_t interface, .factor = 1.25, .chunk_size = 48, .item_size_max= 1024 * 1024, + .num_threads = 4, }, .scrubber = { .lock = PTHREAD_MUTEX_INITIALIZER, @@ -274,6 +278,11 @@ static ENGINE_ERROR_CODE default_initialize(ENGINE_HANDLE* handle, return ret; } + ret = items_init(se, se->config.num_threads); + if (ret != ENGINE_SUCCESS) { + return ret; + } + se->server.callback->register_callback(handle, ON_DISCONNECT, default_handle_disconnect, handle); return ENGINE_SUCCESS; @@ -289,6 +298,7 @@ static void default_destroy(ENGINE_HANDLE* handle, const bool force) { pthread_mutex_destroy(&se->slabs.lock); se->initialized = false; free(se->tap_connections.clients); + items_destroy(se); free(se); } } @@ -541,6 +551,9 @@ static ENGINE_ERROR_CODE initalize_configuration(struct default_engine *se, .value.dt_bool = &se->config.vb0 }, { .key = "config_file", .datatype = DT_CONFIGFILE }, + { .key = "num_threads", + .datatype = DT_ULONG, + .value.dt_ulong = &se->config.num_threads }, { .key = NULL} }; diff --git a/engines/default_engine/default_engine.h b/engines/default_engine/default_engine.h index a24e4f5..72ac7fd 100644 --- a/engines/default_engine/default_engine.h +++ b/engines/default_engine/default_engine.h @@ -61,6 +61,7 @@ struct config { size_t item_size_max; bool ignore_vbucket; bool vb0; + uint32_t num_threads; }; MEMCACHED_PUBLIC_API @@ -126,6 +127,10 @@ struct default_engine { */ pthread_mutex_t cache_lock; + /* Used to arbitrate concurrent access/modification of items by + * multiple threads */ + struct cur_op_recs cur_op_recs; + struct config config; struct engine_stats stats; struct engine_scrubber scrubber; diff --git a/engines/default_engine/items.c b/engines/default_engine/items.c index f9145c2..2105931 100644 --- a/engines/default_engine/items.c +++ b/engines/default_engine/items.c @@ -28,6 +28,8 @@ static void do_item_update(struct default_engine *engine, hash_item *it); static int do_item_replace(struct default_engine *engine, hash_item *it, hash_item *new_it); static void item_free(struct default_engine *engine, hash_item *it); +static void acquire_key_operation_mutex(struct default_engine *engine, const char *key, size_t nkey); +static void release_key_operation_mutex(struct default_engine *engine, const char *key, size_t nkey); /* * We only reposition items in the LRU queue if they haven't been repositioned @@ -42,9 +44,9 @@ static void item_free(struct default_engine *engine, hash_item *it); static const int search_items = 50; void item_stats_reset(struct default_engine *engine) { - pthread_mutex_lock(&engine->cache_lock); + pthread_mutex_lock(&engine->items.lock); memset(engine->items.itemstats, 0, sizeof(engine->items.itemstats)); - pthread_mutex_unlock(&engine->cache_lock); + pthread_mutex_unlock(&engine->items.lock); } @@ -101,6 +103,7 @@ hash_item *do_item_alloc(struct default_engine *engine, rel_time_t oldest_live = engine->config.oldest_live; rel_time_t current_time = engine->server.core->get_current_time(); + pthread_mutex_lock(&engine->items.lock); for (search = engine->items.tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) { @@ -124,6 +127,7 @@ hash_item *do_item_alloc(struct default_engine *engine, break; } } + pthread_mutex_unlock(&engine->items.lock); if (it == NULL && (it = slabs_alloc(engine, ntotal, id)) == NULL) { /* @@ -137,7 +141,9 @@ hash_item *do_item_alloc(struct default_engine *engine, */ if (engine->config.evict_to_free == 0) { + pthread_mutex_lock(&engine->items.lock); engine->items.itemstats[id].outofmemory++; + pthread_mutex_unlock(&engine->items.lock); return NULL; } @@ -149,10 +155,13 @@ hash_item *do_item_alloc(struct default_engine *engine, */ if (engine->items.tails[id] == 0) { + pthread_mutex_lock(&engine->items.lock); engine->items.itemstats[id].outofmemory++; + pthread_mutex_lock(&engine->items.lock); return NULL; } + pthread_mutex_lock(&engine->items.lock); for (search = engine->items.tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) { if (search->refcount == 0) { if (search->exptime == 0 || search->exptime > current_time) { @@ -177,8 +186,11 @@ hash_item *do_item_alloc(struct default_engine *engine, break; } } + pthread_mutex_unlock(&engine->items.lock); + it = slabs_alloc(engine, ntotal, id); if (it == 0) { + pthread_mutex_lock(&engine->items.lock); engine->items.itemstats[id].outofmemory++; /* Last ditch effort. There is a very rare bug which causes * refcount leaks. We've fixed most of them, but it still happens, @@ -196,6 +208,7 @@ hash_item *do_item_alloc(struct default_engine *engine, break; } } + pthread_mutex_unlock(&engine->items.lock); it = slabs_alloc(engine, ntotal, id); if (it == 0) { return NULL; @@ -338,9 +351,11 @@ void do_item_update(struct default_engine *engine, hash_item *it) { assert((it->iflag & ITEM_SLABBED) == 0); if ((it->iflag & ITEM_LINKED) != 0) { + pthread_mutex_lock(&engine->items.lock); item_unlink_q(engine, it); it->time = current_time; item_link_q(engine, it); + pthread_mutex_unlock(&engine->items.lock); } } } @@ -351,8 +366,11 @@ int do_item_replace(struct default_engine *engine, item_get_key(new_it), new_it->nkey, new_it->nbytes); assert((it->iflag & ITEM_SLABBED) == 0); + pthread_mutex_lock(&engine->items.lock); do_item_unlink(engine, it); - return do_item_link(engine, new_it); + int rc = do_item_link(engine, new_it); + pthread_mutex_unlock(&engine->items.lock); + return rc; } /*@null@*/ @@ -518,7 +536,9 @@ hash_item *do_item_get(struct default_engine *engine, if (it != NULL && engine->config.oldest_live != 0 && engine->config.oldest_live <= current_time && it->time <= engine->config.oldest_live) { + pthread_mutex_lock(&engine->items.lock); do_item_unlink(engine, it); /* MTSAFE - cache_lock held */ + pthread_mutex_unlock(&engine->items.lock); it = NULL; } @@ -530,7 +550,9 @@ hash_item *do_item_get(struct default_engine *engine, } if (it != NULL && it->exptime != 0 && it->exptime <= current_time) { + pthread_mutex_lock(&engine->items.lock); do_item_unlink(engine, it); /* MTSAFE - cache_lock held */ + pthread_mutex_unlock(&engine->items.lock); it = NULL; } @@ -648,7 +670,9 @@ static ENGINE_ERROR_CODE do_store_item(struct default_engine *engine, if (old_it != NULL) { do_item_replace(engine, old_it, it); } else { + pthread_mutex_lock(&engine->items.lock); do_item_link(engine, it); + pthread_mutex_unlock(&engine->items.lock); } *cas = item_get_cas(it); @@ -731,7 +755,9 @@ static ENGINE_ERROR_CODE do_add_delta(struct default_engine *engine, it->exptime, res, cookie); if (new_it == NULL) { + pthread_mutex_lock(&engine->items.lock); do_item_unlink(engine, it); + pthread_mutex_unlock(&engine->items.lock); return ENGINE_ENOMEM; } memcpy(item_get_data(new_it), buf, res); @@ -752,9 +778,9 @@ hash_item *item_alloc(struct default_engine *engine, const void *key, size_t nkey, int flags, rel_time_t exptime, int nbytes, const void *cookie) { hash_item *it; - pthread_mutex_lock(&engine->cache_lock); + acquire_key_operation_mutex(engine, key, nkey); it = do_item_alloc(engine, key, nkey, flags, exptime, nbytes, cookie); - pthread_mutex_unlock(&engine->cache_lock); + release_key_operation_mutex(engine, key, nkey); return it; } @@ -765,9 +791,9 @@ hash_item *item_alloc(struct default_engine *engine, hash_item *item_get(struct default_engine *engine, const void *key, const size_t nkey) { hash_item *it; - pthread_mutex_lock(&engine->cache_lock); + acquire_key_operation_mutex(engine, key, nkey); it = do_item_get(engine, key, nkey); - pthread_mutex_unlock(&engine->cache_lock); + release_key_operation_mutex(engine, key, nkey); return it; } @@ -776,18 +802,20 @@ hash_item *item_get(struct default_engine *engine, * needed. */ void item_release(struct default_engine *engine, hash_item *item) { - pthread_mutex_lock(&engine->cache_lock); + acquire_key_operation_mutex(engine, item_get_key(item), item->nkey); do_item_release(engine, item); - pthread_mutex_unlock(&engine->cache_lock); + release_key_operation_mutex(engine, item_get_key(item), item->nkey); } /* * Unlinks an item from the LRU and hashtable. */ void item_unlink(struct default_engine *engine, hash_item *item) { - pthread_mutex_lock(&engine->cache_lock); + acquire_key_operation_mutex(engine, item_get_key(item), item->nkey); + pthread_mutex_lock(&engine->items.lock); do_item_unlink(engine, item); - pthread_mutex_unlock(&engine->cache_lock); + pthread_mutex_unlock(&engine->items.lock); + release_key_operation_mutex(engine, item_get_key(item), item->nkey); } static ENGINE_ERROR_CODE do_arithmetic(struct default_engine *engine, @@ -847,11 +875,11 @@ ENGINE_ERROR_CODE arithmetic(struct default_engine *engine, { ENGINE_ERROR_CODE ret; - pthread_mutex_lock(&engine->cache_lock); + acquire_key_operation_mutex(engine, key, nkey); ret = do_arithmetic(engine, cookie, key, nkey, increment, create, delta, initial, exptime, cas, result); - pthread_mutex_unlock(&engine->cache_lock); + release_key_operation_mutex(engine, key, nkey); return ret; } @@ -864,9 +892,9 @@ ENGINE_ERROR_CODE store_item(struct default_engine *engine, const void *cookie) { ENGINE_ERROR_CODE ret; - pthread_mutex_lock(&engine->cache_lock); + acquire_key_operation_mutex(engine, item_get_key(item), item->nkey); ret = do_store_item(engine, item, cas, operation, cookie); - pthread_mutex_unlock(&engine->cache_lock); + release_key_operation_mutex(engine, item_get_key(item), item->nkey); return ret; } @@ -889,9 +917,9 @@ hash_item *touch_item(struct default_engine *engine, { hash_item *ret; - pthread_mutex_lock(&engine->cache_lock); + acquire_key_operation_mutex(engine, key, nkey); ret = do_touch_item(engine, key, nkey, exptime); - pthread_mutex_unlock(&engine->cache_lock); + release_key_operation_mutex(engine, key, nkey); return ret; } @@ -902,8 +930,7 @@ void item_flush_expired(struct default_engine *engine, time_t when) { int i; hash_item *iter, *next; - pthread_mutex_lock(&engine->cache_lock); - + pthread_mutex_lock(&engine->items.lock); if (when == 0) { engine->config.oldest_live = engine->server.core->get_current_time() - 1; } else { @@ -932,7 +959,7 @@ void item_flush_expired(struct default_engine *engine, time_t when) { } } } - pthread_mutex_unlock(&engine->cache_lock); + pthread_mutex_unlock(&engine->items.lock); } /* @@ -944,27 +971,27 @@ char *item_cachedump(struct default_engine *engine, unsigned int *bytes) { char *ret; - pthread_mutex_lock(&engine->cache_lock); + pthread_mutex_lock(&engine->items.lock); ret = do_item_cachedump(slabs_clsid, limit, bytes); - pthread_mutex_unlock(&engine->cache_lock); + pthread_mutex_unlock(&engine->items.lock); return ret; } void item_stats(struct default_engine *engine, ADD_STAT add_stat, const void *cookie) { - pthread_mutex_lock(&engine->cache_lock); + pthread_mutex_lock(&engine->items.lock); do_item_stats(engine, add_stat, cookie); - pthread_mutex_unlock(&engine->cache_lock); + pthread_mutex_unlock(&engine->items.lock); } void item_stats_sizes(struct default_engine *engine, ADD_STAT add_stat, const void *cookie) { - pthread_mutex_lock(&engine->cache_lock); + pthread_mutex_lock(&engine->items.lock); do_item_stats_sizes(engine, add_stat, cookie); - pthread_mutex_unlock(&engine->cache_lock); + pthread_mutex_unlock(&engine->items.lock); } static void do_item_link_cursor(struct default_engine *engine, @@ -1046,9 +1073,9 @@ static void item_scrub_class(struct default_engine *engine, ENGINE_ERROR_CODE ret; bool more; do { - pthread_mutex_lock(&engine->cache_lock); + pthread_mutex_lock(&engine->items.lock); more = do_item_walk_cursor(engine, cursor, 200, item_scrub, NULL, &ret); - pthread_mutex_unlock(&engine->cache_lock); + pthread_mutex_unlock(&engine->items.lock); if (ret != ENGINE_SUCCESS) { break; } @@ -1061,7 +1088,7 @@ static void *item_scubber_main(void *arg) hash_item cursor = { .refcount = 1 }; for (int ii = 0; ii < POWER_LARGEST; ++ii) { - pthread_mutex_lock(&engine->cache_lock); + pthread_mutex_lock(&engine->items.lock); bool skip = false; if (engine->items.heads[ii] == NULL) { skip = true; @@ -1069,7 +1096,7 @@ static void *item_scubber_main(void *arg) // add the item at the tail do_item_link_cursor(engine, &cursor, ii); } - pthread_mutex_unlock(&engine->cache_lock); + pthread_mutex_unlock(&engine->items.lock); if (!skip) { item_scrub_class(engine, &cursor); @@ -1175,9 +1202,9 @@ tap_event_t item_tap_walker(ENGINE_HANDLE* handle, { tap_event_t ret; struct default_engine *engine = (struct default_engine*)handle; - pthread_mutex_lock(&engine->cache_lock); + pthread_mutex_lock(&engine->items.lock); ret = do_item_tap_walker(engine, cookie, itm, es, nes, ttl, flags, seqno, vbucket); - pthread_mutex_unlock(&engine->cache_lock); + pthread_mutex_unlock(&engine->items.lock); return ret; } @@ -1194,15 +1221,96 @@ bool initialize_item_tap_walker(struct default_engine *engine, /* Link the cursor! */ bool linked = false; for (int ii = 0; ii < POWER_LARGEST && !linked; ++ii) { - pthread_mutex_lock(&engine->cache_lock); + pthread_mutex_lock(&engine->items.lock); if (engine->items.heads[ii] != NULL) { // add the item at the tail do_item_link_cursor(engine, &client->cursor, ii); linked = true; } - pthread_mutex_unlock(&engine->cache_lock); + pthread_mutex_unlock(&engine->items.lock); } engine->server.cookie->store_engine_specific(cookie, client); return true; } + +ENGINE_ERROR_CODE items_init(struct default_engine *engine, uint32_t num_threads) +{ + pthread_mutex_init(&engine->cur_op_recs.lock, NULL); + pthread_cond_init(&engine->cur_op_recs.key_wait_cv, NULL); + + engine->cur_op_recs.keys = calloc(num_threads, sizeof(*engine->cur_op_recs.keys)); + if (engine->cur_op_recs.keys == NULL) + return ENGINE_ENOMEM; + + engine->cur_op_recs.num_recs = num_threads; + + return ENGINE_SUCCESS; +} + +static void acquire_key_operation_mutex(struct default_engine *engine, const char *key, size_t nkey) +{ + int i; + int zero_index; + struct cur_op_recs *cur_op_recs = &engine->cur_op_recs; + + pthread_mutex_lock(&cur_op_recs->lock); + +start: + zero_index = cur_op_recs->num_recs; + + for (i = 0; i < cur_op_recs->num_recs; i++) + { + if (cur_op_recs->keys[i].nkey == 0) + zero_index = i; + + if (cur_op_recs->keys[i].nkey == nkey && + memcmp(cur_op_recs->keys[i].key, key, nkey) == 0) { + pthread_cond_wait(&cur_op_recs->key_wait_cv, &cur_op_recs->lock); + goto start; + } + + } + + assert(zero_index != cur_op_recs->num_recs); + + cur_op_recs->keys[zero_index].nkey = nkey; + memcpy(cur_op_recs->keys[zero_index].key, key, nkey); + pthread_mutex_unlock(&cur_op_recs->lock); +} + +static void release_key_operation_mutex(struct default_engine *engine, const char *key, size_t nkey) +{ + int i; + struct cur_op_recs *cur_op_recs = &engine->cur_op_recs; + + pthread_mutex_lock(&cur_op_recs->lock); + + for (i = 0; i < cur_op_recs->num_recs; i++) + { + if (cur_op_recs->keys[i].nkey == nkey && + memcmp(cur_op_recs->keys[i].key, key, nkey) == 0) + { + cur_op_recs->keys[i].nkey = 0; + break; + } + } + + assert(i != cur_op_recs->num_recs); + + pthread_cond_broadcast(&cur_op_recs->key_wait_cv); + pthread_mutex_unlock(&cur_op_recs->lock); +} + +void items_destroy(struct default_engine *engine) +{ + pthread_mutex_destroy(&engine->cur_op_recs.lock); + pthread_cond_destroy(&engine->cur_op_recs.key_wait_cv); + for (int i = 0; i < POWER_LARGEST; i++) + pthread_mutex_destroy(engine->items.lock + i); + + pthread_mutex_destroy(&engine->items.itemstats_lock); + + free(engine->cur_op_recs.keys); + engine->cur_op_recs.keys = NULL; +} diff --git a/engines/default_engine/items.h b/engines/default_engine/items.h index e214eb4..62aa1f9 100644 --- a/engines/default_engine/items.h +++ b/engines/default_engine/items.h @@ -1,6 +1,8 @@ #ifndef ITEMS_H #define ITEMS_H +#include "memcached.h" + /* * You should not try to aquire any of the item locks before calling these * functions. @@ -36,8 +38,20 @@ struct items { hash_item *tails[POWER_LARGEST]; itemstats_t itemstats[POWER_LARGEST]; unsigned int sizes[POWER_LARGEST]; + pthread_mutex_t lock; }; +struct cur_key_rec { + char key[KEY_MAX_LENGTH]; + size_t nkey; +}; + +struct cur_op_recs { + pthread_mutex_t lock; + pthread_cond_t key_wait_cv; + int num_recs; + struct cur_key_rec *keys; /* Dynamically allocated at runtime */ +}; /** * Allocate and initialize a new item structure @@ -188,5 +202,8 @@ tap_event_t item_tap_walker(ENGINE_HANDLE* handle, bool initialize_item_tap_walker(struct default_engine *engine, const void* cookie); +ENGINE_ERROR_CODE items_init(struct default_engine *engine, uint32_t num_threads); + +void items_destroy(struct default_engine *engine); #endif diff --git a/include/memcached/config_parser.h b/include/memcached/config_parser.h index 9ea69e7..d874b58 100644 --- a/include/memcached/config_parser.h +++ b/include/memcached/config_parser.h @@ -17,7 +17,8 @@ enum config_datatype { DT_FLOAT, DT_BOOL, DT_STRING, - DT_CONFIGFILE + DT_CONFIGFILE, + DT_ULONG }; /** @@ -28,6 +29,7 @@ union config_value { float *dt_float; bool *dt_bool; char **dt_string; + uint32_t *dt_ulong; }; /** diff --git a/utilities/config_parser.c b/utilities/config_parser.c index 9e8a10f..9c9039f 100644 --- a/utilities/config_parser.c +++ b/utilities/config_parser.c @@ -174,6 +174,18 @@ int parse_config(const char *str, struct config_item *items, FILE *error) { } } break; + case DT_ULONG: + { + uint32_t val; + + if (safe_strtoul(value, &val)) { + *items[ii].value.dt_ulong = val; + items[ii].found = true; + } else { + ret = -1; + } + } + break; default: /* You need to fix your code!!! */ abort(); -- 1.7.1
