assoc.c C 370 lines View on github.com → Search inside
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */2/*3 * Hash table4 *5 * The hash function used here is by Bob Jenkins, 1996:6 *    <http://burtleburtle.net/bob/hash/doobs.html>7 *       "By Bob Jenkins, 1996.  bob_jenkins@burtleburtle.net.8 *       You may use this code any way you wish, private, educational,9 *       or commercial.  It's free."10 *11 * The rest of the file is licensed under the BSD license.  See LICENSE.12 */1314#include "memcached.h"15#include <sys/stat.h>16#include <sys/socket.h>17#include <sys/resource.h>18#include <signal.h>19#include <fcntl.h>20#include <netinet/in.h>21#include <errno.h>22#include <stdlib.h>23#include <stdio.h>24#include <string.h>25#include <assert.h>26#include <pthread.h>2728static pthread_cond_t maintenance_cond = PTHREAD_COND_INITIALIZER;29static pthread_mutex_t maintenance_lock = PTHREAD_MUTEX_INITIALIZER;3031/* how many powers of 2's worth of buckets we use */32unsigned int hashpower = HASHPOWER_DEFAULT;3334#define hashsize(n) ((uint64_t)1<<(n))35#define hashmask(n) (hashsize(n)-1)3637/* Main hash table. This is where we look except during expansion. */38static item** primary_hashtable = 0;3940/*41 * Previous hash table. During expansion, we look here for keys that haven't42 * been moved over to the primary yet.43 */44static item** old_hashtable = 0;4546/* Flag: Are we in the middle of expanding now? */47static bool expanding = false;4849/*50 * During expansion we migrate values with bucket granularity; this is how51 * far we've gotten so far. Ranges from 0 .. hashsize(hashpower - 1) - 1.52 */53static uint64_t expand_bucket = 0;5455void assoc_init(const int hashtable_init) {56    if (hashtable_init) {57        hashpower = hashtable_init;58    }59    primary_hashtable = calloc(hashsize(hashpower), sizeof(void *));60    if (! primary_hashtable) {61        fprintf(stderr, "Failed to init hashtable.\n");62        exit(EXIT_FAILURE);63    }64    STATS_LOCK();65    stats_state.hash_power_level = hashpower;66    stats_state.hash_bytes = hashsize(hashpower) * sizeof(void *);67    STATS_UNLOCK();68}6970item *assoc_find(const char *key, const size_t nkey, const uint32_t hv) {71    item *it;72    uint64_t oldbucket;7374    if (expanding &&75        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)76    {77        it = old_hashtable[oldbucket];78    } else {79        it = primary_hashtable[hv & hashmask(hashpower)];80    }8182    item *ret = NULL;83#ifdef ENABLE_DTRACE84    int depth = 0;85#endif86    while (it) {87        if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == 0)) {88            ret = it;89            break;90        }91        it = it->h_next;92#ifdef ENABLE_DTRACE93        ++depth;94#endif95    }96    MEMCACHED_ASSOC_FIND(key, nkey, depth);97    return ret;98}99100/* returns the address of the item pointer before the key.  if *item == 0,101   the item wasn't found */102103static item** _hashitem_before (const char *key, const size_t nkey, const uint32_t hv) {104    item **pos;105    uint64_t oldbucket;106107    if (expanding &&108        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)109    {110        pos = &old_hashtable[oldbucket];111    } else {112        pos = &primary_hashtable[hv & hashmask(hashpower)];113    }114115    while (*pos && ((nkey != (*pos)->nkey) || memcmp(key, ITEM_key(*pos), nkey))) {116        pos = &(*pos)->h_next;117    }118    return pos;119}120121/* grows the hashtable to the next power of 2. */122static void assoc_expand(void) {123    old_hashtable = primary_hashtable;124125    primary_hashtable = calloc(hashsize(hashpower + 1), sizeof(void *));126    if (primary_hashtable) {127        if (settings.verbose > 1)128            fprintf(stderr, "Hash table expansion starting\n");129        hashpower++;130        expanding = true;131        expand_bucket = 0;132        STATS_LOCK();133        stats_state.hash_power_level = hashpower;134        stats_state.hash_bytes += hashsize(hashpower) * sizeof(void *);135        stats_state.hash_is_expanding = true;136        STATS_UNLOCK();137    } else {138        primary_hashtable = old_hashtable;139        /* Bad news, but we can keep running. */140    }141}142143void assoc_start_expand(uint64_t curr_items) {144    if (pthread_mutex_trylock(&maintenance_lock) == 0) {145        if (curr_items > (hashsize(hashpower) * 3) / 2 && hashpower < HASHPOWER_MAX) {146            pthread_cond_signal(&maintenance_cond);147        }148        pthread_mutex_unlock(&maintenance_lock);149    }150}151152/* Note: this isn't an assoc_update.  The key must not already exist to call this */153int assoc_insert(item *it, const uint32_t hv) {154    uint64_t oldbucket;155156//    assert(assoc_find(ITEM_key(it), it->nkey) == 0);  /* shouldn't have duplicately named things defined */157158    if (expanding &&159        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)160    {161        it->h_next = old_hashtable[oldbucket];162        old_hashtable[oldbucket] = it;163    } else {164        it->h_next = primary_hashtable[hv & hashmask(hashpower)];165        primary_hashtable[hv & hashmask(hashpower)] = it;166    }167168    MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey);169    return 1;170}171172void assoc_delete(const char *key, const size_t nkey, const uint32_t hv) {173    item **before = _hashitem_before(key, nkey, hv);174175    if (*before) {176        item *nxt;177        /* The DTrace probe cannot be triggered as the last instruction178         * due to possible tail-optimization by the compiler179         */180        MEMCACHED_ASSOC_DELETE(key, nkey);181        nxt = (*before)->h_next;182        (*before)->h_next = 0;   /* probably pointless, but whatever. */183        *before = nxt;184        return;185    }186    /* Note:  we never actually get here.  the callers don't delete things187       they can't find. */188    assert(*before != 0);189}190191192static volatile int do_run_maintenance_thread = 1;193194#define DEFAULT_HASH_BULK_MOVE 1195int hash_bulk_move = DEFAULT_HASH_BULK_MOVE;196197static void *assoc_maintenance_thread(void *arg) {198199    mutex_lock(&maintenance_lock);200    while (do_run_maintenance_thread) {201        int ii = 0;202203        /* There is only one expansion thread, so no need to global lock. */204        for (ii = 0; ii < hash_bulk_move && expanding; ++ii) {205            item *it, *next;206            uint64_t bucket;207            void *item_lock = NULL;208209            /* bucket = hv & hashmask(hashpower) =>the bucket of hash table210             * is the lowest N bits of the hv, and the bucket of item_locks is211             *  also the lowest M bits of hv, and N is greater than M.212             *  So we can process expanding with only one item_lock. cool! */213            if ((item_lock = item_trylock(expand_bucket))) {214                    for (it = old_hashtable[expand_bucket]; NULL != it; it = next) {215                        next = it->h_next;216                        bucket = hash(ITEM_key(it), it->nkey) & hashmask(hashpower);217                        it->h_next = primary_hashtable[bucket];218                        primary_hashtable[bucket] = it;219                    }220221                    old_hashtable[expand_bucket] = NULL;222223                    expand_bucket++;224                    if (expand_bucket == hashsize(hashpower - 1)) {225                        expanding = false;226                        free(old_hashtable);227                        STATS_LOCK();228                        stats_state.hash_bytes -= hashsize(hashpower - 1) * sizeof(void *);229                        stats_state.hash_is_expanding = false;230                        STATS_UNLOCK();231                        if (settings.verbose > 1)232                            fprintf(stderr, "Hash table expansion done\n");233                    }234235            } else {236                usleep(10*1000 - 1);237            }238239            if (item_lock) {240                item_trylock_unlock(item_lock);241                item_lock = NULL;242            }243        }244245        if (!expanding) {246            /* We are done expanding.. just wait for next invocation */247            pthread_cond_wait(&maintenance_cond, &maintenance_lock);248            /* assoc_expand() swaps out the hash table entirely, so we need249             * all threads to not hold any references related to the hash250             * table while this happens.251             * This is instead of a more complex, possibly slower algorithm to252             * allow dynamic hash table expansion without causing significant253             * wait times.254             */255            if (do_run_maintenance_thread) {256                pause_threads(PAUSE_ALL_THREADS);257                assoc_expand();258                pause_threads(RESUME_ALL_THREADS);259            }260        }261    }262    mutex_unlock(&maintenance_lock);263    return NULL;264}265266static pthread_t maintenance_tid;267268int start_assoc_maintenance_thread(void) {269    int ret;270    char *env = getenv("MEMCACHED_HASH_BULK_MOVE");271    if (env != NULL) {272        hash_bulk_move = atoi(env);273        if (hash_bulk_move == 0) {274            hash_bulk_move = DEFAULT_HASH_BULK_MOVE;275        }276    }277278    if ((ret = pthread_create(&maintenance_tid, NULL,279                              assoc_maintenance_thread, NULL)) != 0) {280        fprintf(stderr, "Can't create thread: %s\n", strerror(ret));281        return -1;282    }283    thread_setname(maintenance_tid, "mc-assocmaint");284    return 0;285}286287void stop_assoc_maintenance_thread(void) {288    mutex_lock(&maintenance_lock);289    do_run_maintenance_thread = 0;290    pthread_cond_signal(&maintenance_cond);291    mutex_unlock(&maintenance_lock);292293    /* Wait for the maintenance thread to stop */294    pthread_join(maintenance_tid, NULL);295}296297struct assoc_iterator {298    uint64_t bucket;299    item *it;300    item *next;301    bool bucket_locked;302};303304void *assoc_get_iterator(void) {305    struct assoc_iterator *iter = calloc(1, sizeof(struct assoc_iterator));306    if (iter == NULL) {307        return NULL;308    }309    // this will hang the caller while a hash table expansion is running.310    if (mutex_trylock(&maintenance_lock) == 0) {311        return iter;312    } else {313        free(iter);314        return NULL;315    }316}317318bool assoc_iterate(void *iterp, item **it) {319    struct assoc_iterator *iter = (struct assoc_iterator *) iterp;320    *it = NULL;321    // - if locked bucket and next, update next and return322    if (iter->bucket_locked) {323        if (iter->next != NULL) {324            iter->it = iter->next;325            iter->next = iter->it->h_next;326            *it = iter->it;327        } else {328            // unlock previous bucket, if any329            item_unlock(iter->bucket);330            // iterate the bucket post since it starts at 0.331            iter->bucket++;332            iter->bucket_locked = false;333            *it = NULL;334        }335        return true;336    }337338    // - loop until we hit the end or find something.339    if (iter->bucket != hashsize(hashpower)) {340        // - lock next bucket341        item_lock(iter->bucket);342        iter->bucket_locked = true;343        // - only check the primary hash table since expand is blocked.344        iter->it = primary_hashtable[iter->bucket];345        if (iter->it != NULL) {346            // - set it, next and return347            iter->next = iter->it->h_next;348            *it = iter->it;349        } else {350            // - nothing found in this bucket, try next.351            item_unlock(iter->bucket);352            iter->bucket_locked = false;353            iter->bucket++;354        }355    } else {356        return false;357    }358359    return true;360}361362void assoc_iterate_final(void *iterp) {363    struct assoc_iterator *iter = (struct assoc_iterator *) iterp;364    if (iter->bucket_locked) {365        item_unlock(iter->bucket);366    }367    mutex_unlock(&maintenance_lock);368    free(iter);369}

Code quality findings 5

Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
primary_hashtable = calloc(hashsize(hashpower), sizeof(void *));
Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
for (ii = 0; ii < hash_bulk_move && expanding; ++ii) {
Warning: Possible assignment ('=') used in conditional context instead of comparison ('=='). Verify intent.
warning correctness assignment-in-condition
if ((item_lock = item_trylock(expand_bucket))) {
Warning: Possible assignment ('=') used in conditional context instead of comparison ('=='). Verify intent.
warning correctness assignment-in-condition
if ((ret = pthread_create(&maintenance_tid, NULL,
Info: atoi(), atol(), atof() provide no error checking for invalid input or overflows. Use strtol(), strtoul(), strtod() for more robust conversion.
info correctness atoi-no-error-check
hash_bulk_move = atoi(env);

Security findings 1

Info: Environment variables are external input and should be treated as potentially untrusted. Validate or sanitize values obtained from getenv().
info security getenv-untrusted
char *env = getenv("MEMCACHED_HASH_BULK_MOVE");

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.

Generated by searchcode.com · powered by code_get_file + code_get_findings