/** wrapper around assoc_find which does the lazy expiration logic */ hash_item *do_item_get(struct default_engine *engine, const hash_key *key) { rel_time_t current_time = engine->server.core->get_current_time(); hash_item *it = assoc_find(engine, crc32c(hash_key_get_key(key), hash_key_get_key_len(key), 0), key); int was_found = 0; if (engine->config.verbose > 2) { EXTENSION_LOGGER_DESCRIPTOR *logger; logger = (void*)engine->server.extension->get_extension(EXTENSION_LOGGER); if (it == NULL) { logger->log(EXTENSION_LOG_DEBUG, NULL, "> NOT FOUND in bucket %d, %s", hash_key_get_bucket_index(key), hash_key_get_client_key(key)); } else { logger->log(EXTENSION_LOG_DEBUG, NULL, "> FOUND KEY in bucket %d, %s", hash_key_get_bucket_index(item_get_key(it)), hash_key_get_client_key(item_get_key(it))); was_found++; } } if (it != NULL && engine->config.oldest_live != 0 && engine->config.oldest_live <= current_time && it->time <= engine->config.oldest_live) { do_item_unlink(engine, it); /* MTSAFE - items.lock held */ it = NULL; } if (it == NULL && was_found) { EXTENSION_LOGGER_DESCRIPTOR *logger; logger = (void*)engine->server.extension->get_extension(EXTENSION_LOGGER); logger->log(EXTENSION_LOG_DEBUG, NULL, " -nuked by flush"); was_found--; } if (it != NULL && it->exptime != 0 && it->exptime <= current_time) { do_item_unlink(engine, it); /* MTSAFE - items.lock held */ it = NULL; } if (it == NULL && was_found) { EXTENSION_LOGGER_DESCRIPTOR *logger; logger = (void*)engine->server.extension->get_extension(EXTENSION_LOGGER); logger->log(EXTENSION_LOG_DEBUG, NULL, " -nuked by expire"); was_found--; } if (it != NULL) { it->refcount++; DEBUG_REFCNT(it, '+'); do_item_update(engine, it); } return it; }
/** wrapper around assoc_find which does the lazy expiration/deletion logic */ item *do_item_get_notedeleted(const char *key, const size_t nkey, bool *delete_locked) { item *it = assoc_find(key, nkey); int was_found = 0; if (delete_locked) *delete_locked = false; if (settings.verbose > 2) { if (it == NULL) { fprintf(stderr, "> NOT FOUND %s", key); } else { fprintf(stderr, "> FOUND KEY %s", ITEM_key(it)); was_found++; } } if (it != NULL && (it->it_flags & ITEM_DELETED)) { /* it's flagged as delete-locked. let's see if that condition is past due, and the 5-second delete_timer just hasn't gotten to it yet... */ if (!item_delete_lock_over(it)) { if (delete_locked) *delete_locked = true; it = NULL; } } if (it == NULL && was_found) { fprintf(stderr, " -nuked by delete lock"); was_found--; } if (it != NULL && settings.oldest_live != 0 && settings.oldest_live <= current_time && it->time <= settings.oldest_live) { do_item_unlink(it); /* MTSAFE - cache_lock held */ it = NULL; } if (it == NULL && was_found) { fprintf(stderr, " -nuked by flush"); was_found--; } if (it != NULL && it->exptime != 0 && it->exptime <= current_time) { do_item_unlink(it); /* MTSAFE - cache_lock held */ it = NULL; } if (it == NULL && was_found) { fprintf(stderr, " -nuked by expire"); was_found--; } if (it != NULL) { it->refcount++; DEBUG_REFCNT(it, '+'); } if (settings.verbose > 2) fprintf(stderr, "\n"); return it; }
/* Note: this isn't an assoc_update. The key must not already exist to call this */ int assoc_insert(struct default_engine *engine, uint32_t hash, hash_item *it) { struct assoc *assoc = &engine->assoc; uint32_t bucket = GET_HASH_BUCKET(hash, assoc->hashmask); uint32_t tabidx; assert(assoc_find(engine, hash, item_get_key(it), it->nkey) == 0); /* shouldn't have duplicately named things defined */ if (assoc->infotable[bucket].curpower != assoc->rootpower && assoc->infotable[bucket].refcount == 0) { redistribute(engine, bucket); } tabidx = GET_HASH_TABIDX(hash, assoc->hashpower, hashmask(assoc->infotable[bucket].curpower)); // inserting actual hash_item to appropriate assoc_t it->h_next = assoc->roottable[tabidx].hashtable[bucket]; assoc->roottable[tabidx].hashtable[bucket] = it; assoc->hash_items++; if (assoc->hash_items > (hashsize(assoc->hashpower + assoc->rootpower) * 3) / 2) { assoc_expand(engine); } MEMCACHED_ASSOC_INSERT(item_get_key(it), it->nkey, assoc->hash_items); return 1; }
/* Note: this isn't an assoc_update. The key must not already exist to call this */ int assoc_insert(struct default_engine *engine, uint32_t hash, hash_item *it) { unsigned int oldbucket; assert(assoc_find(engine, hash, item_get_key(it), it->nkey) == 0); /* shouldn't have duplicately named things defined */ // inserting actual hash_item to appropriate assoc_t if (engine->assoc.expanding && (oldbucket = (hash & hashmask(engine->assoc.hashpower - 1))) >= engine->assoc.expand_bucket) { it->h_next = engine->assoc.old_hashtable[oldbucket]; engine->assoc.old_hashtable[oldbucket] = it; } else { it->h_next = engine->assoc.primary_hashtable[hash & hashmask(engine->assoc.hashpower)]; engine->assoc.primary_hashtable[hash & hashmask(engine->assoc.hashpower)] = it; } engine->assoc.hash_items++; if (! engine->assoc.expanding && engine->assoc.hash_items > (hashsize(engine->assoc.hashpower) * 3) / 2) { assoc_expand(engine); } MEMCACHED_ASSOC_INSERT(item_get_key(it), it->nkey, engine->assoc.hash_items); return 1; }
/** wrapper around assoc_find which does the lazy expiration/deletion logic */ item *do_item_get_notedeleted(const char *key, const size_t nkey, bool *delete_locked) { item *it = assoc_find(key, nkey); if (delete_locked) *delete_locked = false; if (it != NULL && (it->it_flags & ITEM_DELETED)) { /* it's flagged as delete-locked. let's see if that condition is past due, and the 5-second delete_timer just hasn't gotten to it yet... */ if (!item_delete_lock_over(it)) { if (delete_locked) *delete_locked = true; it = NULL; } } if (it != NULL && settings.oldest_live != 0 && settings.oldest_live <= current_time && it->time <= settings.oldest_live) { do_item_unlink(it); /* MTSAFE - cache_lock held */ it = NULL; } if (it != NULL && it->exptime != 0 && it->exptime <= current_time) { do_item_unlink(it); /* MTSAFE - cache_lock held */ it = NULL; } if (it != NULL) { it->refcount++; DEBUG_REFCNT(it, '+'); } return it; }
void slabs_alloc_test(void) { unsigned int total_chunk = 0; const char *key = "charliezhao"; size_t nkey = strlen(key) + 1; item *ptr = (item *)slabs_alloc(1024, slabs_clsid(1024), &total_chunk); strcpy(ITEM_key(ptr), key); ptr->nkey = nkey; strcpy(ITEM_data(ptr), "xuechaozhao"); uint32_t hv = jenkins_hash(key, strlen(key)); assoc_insert(ptr, hv); for(int i = 0; i <= 10922; ++i) { void *ptr = slabs_alloc(96, slabs_clsid(96), &total_chunk); if(ptr == NULL) { fprintf(stderr, "i: %7d slabs_alloc fail\n", i); break; } else { slabs_free(ptr, 96, slabs_clsid(96)); } } item *ptr2 = assoc_find(key, nkey, hv); fprintf(stdout, "key:%20s value:%20s\n", ITEM_key(ptr2), ITEM_data(ptr2)); }
/* Note: this isn't an assoc_update. The key must not already exist to call this */ int assoc_insert(item *it) { uint32_t hv; unsigned int oldbucket; assert(assoc_find(ITEM_key(it), it->nkey) == 0); /* shouldn't have duplicately named things defined */ hv = hash(ITEM_key(it), it->nkey, 0); if (expanding && (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket) { it->h_next = old_hashtable[oldbucket]; old_hashtable[oldbucket] = it; } else { it->h_next = primary_hashtable[hv & hashmask(hashpower)]; primary_hashtable[hv & hashmask(hashpower)] = it; } hash_items++; if (! expanding && hash_items > (hashsize(hashpower) * 3) / 2) { assoc_expand(); } MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey, hash_items); return 1; }
/** wrapper around assoc_find which does the lazy expiration logic */ item *do_item_get(const char *key, const size_t nkey, const uint32_t hv) { mutex_lock(&cache_lock); item *it = assoc_find(key, nkey, hv); if (it != NULL) { refcount_incr(&it->refcount); /* Optimization for slab reassignment. prevents popular items from * jamming in busy wait. Can only do this here to satisfy lock order * of item_lock, cache_lock, slabs_lock. */ if (slab_rebalance_signal && ((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) { do_item_unlink_nolock(it, hv); do_item_remove(it); it = NULL; } } pthread_mutex_unlock(&cache_lock); int was_found = 0; if (settings.verbose > 2) { if (it == NULL) { fprintf(stderr, "> NOT FOUND %s", key); } else { fprintf(stderr, "> FOUND KEY %s", ITEM_key(it)); was_found++; } } if (it != NULL) { if (settings.oldest_live != 0 && settings.oldest_live <= current_time && it->time <= settings.oldest_live) { do_item_unlink(it, hv); do_item_remove(it); it = NULL; if (was_found) { fprintf(stderr, " -nuked by flush"); } } else if (it->exptime != 0 && it->exptime <= current_time) { if (it->exptime + 10 < current_time) { do_item_unlink(it, hv); do_item_remove(it); if (was_found) { fprintf(stderr, " -nuked by expire"); } } else { /* re-active just expired items, to anti miss-storm */ it->exptime = current_time + 10; } it = NULL; } else { it->it_flags |= ITEM_FETCHED; DEBUG_REFCNT(it, '+'); } } if (settings.verbose > 2) fprintf(stderr, "\n"); return it; }
/** returns an item whether or not it's delete-locked or expired. */ item *do_item_get_nocheck(const char *key, const size_t nkey) { item *it = assoc_find(key, nkey); if (it) { it->refcount++; DEBUG_REFCNT(it, '+'); } return it; }
/* * Returns an item whether or not it's been marked as expired or deleted. */ item *mt_item_get_nocheck(char *key, size_t nkey) { item *it; pthread_mutex_lock(&cache_lock); it = assoc_find(key, nkey); it->refcount++; pthread_mutex_unlock(&cache_lock); return it; }
void complete_nread(conn *c) { item *it = c->item; int comm = c->item_comm; item *old_it; time_t now = time(0); stats.set_cmds++; while(1) { if (strncmp(ITEM_data(it) + it->nbytes - 2, "\r\n", 2) != 0) { out_string(c, "CLIENT_ERROR bad data chunk"); break; } old_it = assoc_find(ITEM_key(it)); if (old_it && settings.oldest_live && old_it->time <= settings.oldest_live) { item_unlink(old_it); old_it = 0; } if (old_it && old_it->exptime && old_it->exptime < now) { item_unlink(old_it); old_it = 0; } if (old_it && comm==NREAD_ADD) { item_update(old_it); out_string(c, "NOT_STORED"); break; } if (!old_it && comm == NREAD_REPLACE) { out_string(c, "NOT_STORED"); break; } if (old_it && (old_it->it_flags & ITEM_DELETED) && (comm == NREAD_REPLACE || comm == NREAD_ADD)) { out_string(c, "NOT_STORED"); break; } if (old_it) { item_replace(old_it, it); } else item_link(it); c->item = 0; out_string(c, "STORED"); return; } item_free(it); c->item = 0; return; }
static rstatus_t stats_pool_copy_recover(struct context *ctx, struct stats_pool *stp_src, struct hash_table **sit) { uint32_t i, j, k; struct stats *st = ctx->stats; struct array *sum = &st->sum; struct stats_pool *stp_dst; struct stats_metric *stm_src, *stm_dst; struct stats_server *sts_src, *sts_dst; char *key; uint64_t sidx; for (i = 0;i < array_n(sum); i++) { /* get pool from array sum */ stp_dst = array_get(sum, i); /* find the pool */ if (string_compare(&stp_src->name, &stp_dst->name) == 0) { /* recover the metric array */ for (j = 0;j < array_n(&stp_src->metric);j++) { stm_src = array_get(&stp_src->metric, j); stm_dst = array_get(&stp_dst->metric, j); stats_metric_copy(stm_dst, stm_src); } /* recover the server array */ for (j = 0;j < array_n(&stp_dst->server);j++) { /* recover sts_src data to sts_dst */ sts_dst = array_get(&stp_dst->server, j); log_debug(LOG_VVVERB, "try recover server %s", sts_dst->name.data); /* find the server in index hashtable */ key = (char *)sts_dst->name.data; sidx = (uint64_t)assoc_find(*sit, key, strlen(key)); if (sidx > 0) { sidx = sidx - 1; /* get server data in array */ sts_src = array_get(&stp_src->server, (uint32_t)sidx); ASSERT(string_compare(&sts_dst->name, &sts_src->name) == 0); log_debug(LOG_VERB, "recovering server stats %s", sts_dst->name.data); /* recover the old data */ for (k = 0;k < array_n(&sts_src->metric) - 4; k++) { stm_src = array_get(&sts_src->metric, k); stm_dst = array_get(&sts_dst->metric, k); stats_metric_copy(stm_dst, stm_src); } } } } } return NC_OK; }
static struct item* _item_get(const char *key, uint16_t nkey) { struct item *it; it = assoc_find(key, nkey); if (it == NULL) return NULL; if (it->exptime != 0 && it->exptime <= time_now()) { _item_unlink(it); return NULL; } item_acquire_refcount(it); _item_touch(it); return it; }
int item_test() { int maxi = 0; //test set. for(int i = 0; i < 10; i++) { char key[1024]; memset(key, 0, 1024); sprintf(key, "charlie_%d", i); const size_t nkey = strlen(key) + 1; const int flags = 0; const time_t exptime = 0; const int nbytes = 1024; uint32_t cur_hv = jenkins_hash((void *)key, nkey); item *it = do_item_alloc((const char *)key, nkey, flags, exptime, nbytes, cur_hv); if(it == NULL) { fprintf(stderr, "\033[31malloc fail\033[0m"); maxi = i; break; } char val[1024]; sprintf(val, "%d", i); memcpy(ITEM_data(it), (void *)&val, strlen(val)+1); } //test get. for(int i = 0; i < 10; ++i) { char key[1024]; memset(key, 0, 1024); sprintf(key, "charlie_%d", i); const size_t nkey = strlen(key) + 1; uint32_t cur_hv = jenkins_hash((void *)key, nkey); item *it = assoc_find(key, nkey, cur_hv); if(it == NULL) { fprintf(stderr, "\033[31mget fail\033[0m"); return -1; } int val = 0; memcpy((void *)&val, ITEM_data(it), sizeof(val)); if(i&0x1) { fprintf(stdout, "del key:%s value:%d\n", ITEM_key(it), val); do_item_unlink(it, cur_hv); lru_traverse(NULL); } } return 0; }
/** wrapper around assoc_find which does the lazy expiration logic */ item *do_item_get(const char *key, const size_t nkey) { item *it = assoc_find(key, nkey); if (it != NULL && settings.oldest_live != 0 && settings.oldest_live <= current_time && it->time <= settings.oldest_live) { do_item_unlink(it); /* MTSAFE - cache_lock held */ it = NULL; } if (it != NULL && it->exptime != 0 && it->exptime <= current_time) { do_item_unlink(it); /* MTSAFE - cache_lock held */ it = NULL; } if (it != NULL) { it->refcount++; DEBUG_REFCNT(it, '+'); } return it; }
/** wrapper around assoc_find which does the lazy expiration logic */ item *do_item_get(const char *key, const size_t nkey) { item *it = assoc_find(key, nkey); int was_found = 0; if (settings.verbose > 2) { if (it == NULL) { fprintf(stderr, "> NOT FOUND %s", key); } else { fprintf(stderr, "> FOUND KEY %s", ITEM_key(it)); was_found++; } } if (it != NULL && settings.oldest_live != 0 && settings.oldest_live <= current_time && it->time <= settings.oldest_live) { do_item_unlink(it); /* MTSAFE - cache_lock held */ it = NULL; } if (it == NULL && was_found) { fprintf(stderr, " -nuked by flush"); was_found--; } if (it != NULL && it->exptime != 0 && it->exptime <= current_time) { do_item_unlink(it); /* MTSAFE - cache_lock held */ it = NULL; } if (it == NULL && was_found) { fprintf(stderr, " -nuked by expire"); was_found--; } if (it != NULL) { it->refcount++; DEBUG_REFCNT(it, '+'); } if (settings.verbose > 2) fprintf(stderr, "\n"); return it; }
/* Note: this isn't an assoc_update. The key must not already exist to call this */ int assoc_insert(struct persistent_engine *engine, uint32_t hash, hash_item *it) { unsigned int oldbucket; assert(assoc_find(engine, hash, item_get_key(&it->item), it->item.nkey) == 0); /* shouldn't have duplicately named things defined */ if (engine->assoc.expanding && (oldbucket = (hash & hashmask(engine->assoc.hashpower - 1))) >= engine->assoc.expand_bucket) { it->h_next = engine->assoc.old_hashtable[oldbucket]; engine->assoc.old_hashtable[oldbucket] = it; } else { it->h_next = engine->assoc.primary_hashtable[hash & hashmask(engine->assoc.hashpower)]; engine->assoc.primary_hashtable[hash & hashmask(engine->assoc.hashpower)] = it; } engine->assoc.hash_items++; if (! engine->assoc.expanding && engine->assoc.hash_items > (hashsize(engine->assoc.hashpower) * 3) / 2) { assoc_expand(engine); } return 1; }
int replication_cmd(conn *c, Q_ITEM *q) { item *it; switch (q->type) { case REPLICATION_REP: if(it = assoc_find(q->key, strlen(q->key))) return(replication_rep(c, it)); else return(replication_del(c, q->key)); case REPLICATION_DEL: return(replication_del(c, q->key)); case REPLICATION_DEFER_DEL: return(replication_defer_del(c, q->key, q->time)); case REPLICATION_FLUSH_ALL: return(replication_flush_all(c, 0)); case REPLICATION_DEFER_FLUSH_ALL: return(replication_flush_all(c, q->time)); case REPLICATION_MARUGOTO_END: return(replication_marugoto_end(c)); default: fprintf(stderr,"replication: got unknown command:%d\n", q->type); return(0); } }
/** wrapper around assoc_find which does the lazy expiration logic */ item *do_item_get(const char *key, const size_t nkey, const uint32_t hv) { //mutex_lock(&cache_lock); // 查找 key 是否存在 item *it = assoc_find(key, nkey, hv); if (it != NULL) { refcount_incr(&it->refcount); /* Optimization for slab reassignment. prevents popular items from * jamming in busy wait. Can only do this here to satisfy lock order * of item_lock, cache_lock, slabs_lock. */ if (slab_rebalance_signal && ((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) { do_item_unlink_nolock(it, hv); do_item_remove(it); it = NULL; } } //mutex_unlock(&cache_lock); int was_found = 0; if (settings.verbose > 2) { if (it == NULL) { fprintf(stderr, "> NOT FOUND %s", key); } else { fprintf(stderr, "> FOUND KEY %s", ITEM_key(it)); was_found++; } } if (it != NULL) { //settings.oldest_live初始化值为0 //检测用户是否使用过flush_all命令,删除所有item //it->time <= settings.oldest_live就说明用户在使用flush_all命令的时候 //就已经存在该item了。那么该item是要删除的 if (settings.oldest_live != 0 && settings.oldest_live <= current_time && it->time <= settings.oldest_live) { do_item_unlink(it, hv); do_item_remove(it); it = NULL; if (was_found) { fprintf(stderr, " -nuked by flush"); } } //该item已经过期失效了 else if (it->exptime != 0 && it->exptime <= current_time) { do_item_unlink(it, hv); do_item_remove(it); it = NULL; if (was_found) { fprintf(stderr, " -nuked by expire"); } } else { it->it_flags |= ITEM_FETCHED; DEBUG_REFCNT(it, '+'); } } if (settings.verbose > 2) fprintf(stderr, "\n"); return it; }
void process_command(conn *c, char *command) { int comm = 0; int incr = 0; /* * for commands set/add/replace, we build an item and read the data * directly into it, then continue in nread_complete(). */ if (settings.verbose > 1) fprintf(stderr, "<%d %s\n", c->sfd, command); /* All incoming commands will require a response, so we cork at the beginning, and uncork at the very end (usually by means of out_string) */ set_cork(c, 1); if ((strncmp(command, "add ", 4) == 0 && (comm = NREAD_ADD)) || (strncmp(command, "set ", 4) == 0 && (comm = NREAD_SET)) || (strncmp(command, "replace ", 8) == 0 && (comm = NREAD_REPLACE))) { char key[251]; int flags; time_t expire; int len, res; item *it; res = sscanf(command, "%*s %250s %u %ld %d\n", key, &flags, &expire, &len); if (res!=4 || strlen(key)==0 ) { out_string(c, "CLIENT_ERROR bad command line format"); return; } expire = realtime(expire); it = item_alloc(key, flags, expire, len+2); if (it == 0) { out_string(c, "SERVER_ERROR out of memory"); /* swallow the data line */ c->write_and_go = conn_swallow; c->sbytes = len+2; return; } c->item_comm = comm; c->item = it; c->rcurr = ITEM_data(it); c->rlbytes = it->nbytes; c->state = conn_nread; return; } if ((strncmp(command, "incr ", 5) == 0 && (incr = 1)) || (strncmp(command, "decr ", 5) == 0)) { char temp[32]; unsigned int value; item *it; unsigned int delta; char key[251]; int res; char *ptr; time_t now = time(0); res = sscanf(command, "%*s %250s %u\n", key, &delta); if (res!=2 || strlen(key)==0 ) { out_string(c, "CLIENT_ERROR bad command line format"); return; } it = assoc_find(key); if (it && (it->it_flags & ITEM_DELETED)) { it = 0; } if (it && it->exptime && it->exptime < now) { item_unlink(it); it = 0; } if (!it) { out_string(c, "NOT_FOUND"); return; } ptr = ITEM_data(it); while (*ptr && (*ptr<'0' && *ptr>'9')) ptr++; value = atoi(ptr); if (incr) value+=delta; else { if (delta >= value) value = 0; else value-=delta; } sprintf(temp, "%u", value); res = strlen(temp); if (res + 2 > it->nbytes) { /* need to realloc */ item *new_it; new_it = item_alloc(ITEM_key(it), it->flags, it->exptime, res + 2 ); if (new_it == 0) { out_string(c, "SERVER_ERROR out of memory"); return; } memcpy(ITEM_data(new_it), temp, res); memcpy(ITEM_data(new_it) + res, "\r\n", 2); item_replace(it, new_it); } else { /* replace in-place */ memcpy(ITEM_data(it), temp, res); memset(ITEM_data(it) + res, ' ', it->nbytes-res-2); } out_string(c, temp); return; } if (strncmp(command, "get ", 4) == 0) { char *start = command + 4; char key[251]; int next; int i = 0; item *it; time_t now = time(0); while(sscanf(start, " %250s%n", key, &next) >= 1) { start+=next; stats.get_cmds++; it = assoc_find(key); if (it && (it->it_flags & ITEM_DELETED)) { it = 0; } if (settings.oldest_live && it && it->time <= settings.oldest_live) { item_unlink(it); it = 0; } if (it && it->exptime && it->exptime < now) { item_unlink(it); it = 0; } if (it) { if (i >= c->isize) { item **new_list = realloc(c->ilist, sizeof(item *)*c->isize*2); if (new_list) { c->isize *= 2; c->ilist = new_list; } else break; } stats.get_hits++; it->refcount++; item_update(it); *(c->ilist + i) = it; i++; } else stats.get_misses++; } c->icurr = c->ilist; c->ileft = i; if (c->ileft) { c->ipart = 0; c->state = conn_mwrite; c->ibytes = 0; return; } else { out_string(c, "END"); return; } } if (strncmp(command, "delete ", 7) == 0) { char key[251]; item *it; int res; time_t exptime = 0; res = sscanf(command, "%*s %250s %ld", key, &exptime); it = assoc_find(key); if (!it) { out_string(c, "NOT_FOUND"); return; } if (exptime == 0) { item_unlink(it); out_string(c, "DELETED"); return; } if (delcurr >= deltotal) { item **new_delete = realloc(todelete, sizeof(item *) * deltotal * 2); if (new_delete) { todelete = new_delete; deltotal *= 2; } else { /* * can't delete it immediately, user wants a delay, * but we ran out of memory for the delete queue */ out_string(c, "SERVER_ERROR out of memory"); return; } } exptime = realtime(exptime); it->refcount++; /* use its expiration time as its deletion time now */ it->exptime = exptime; it->it_flags |= ITEM_DELETED; todelete[delcurr++] = it; out_string(c, "DELETED"); return; } if (strncmp(command, "stats", 5) == 0) { process_stat(c, command); return; } if (strcmp(command, "flush_all") == 0) { settings.oldest_live = time(0); out_string(c, "OK"); return; } if (strcmp(command, "version") == 0) { out_string(c, "VERSION " VERSION); return; } if (strcmp(command, "quit") == 0) { c->state = conn_closing; return; } if (strncmp(command, "slabs reassign ", 15) == 0) { int src, dst; char *start = command+15; if (sscanf(start, "%u %u\r\n", &src, &dst) == 2) { int rv = slabs_reassign(src, dst); if (rv == 1) { out_string(c, "DONE"); return; } if (rv == 0) { out_string(c, "CANT"); return; } if (rv == -1) { out_string(c, "BUSY"); return; } } out_string(c, "CLIENT_ERROR bogus command"); return; } out_string(c, "ERROR"); return; }
/* allocate all memory with small chunks. allocate one more object. it should * free up the oldest object. release all objects. this covers case 1 for the * small item alloc in flat_storage_lru_evict(..). */ static int all_small_chunks_test(int verbose) { typedef struct { item* it; char key[KEY_MAX_LENGTH]; uint8_t klen; } all_small_chunks_key_t; size_t num_objects = fsi.large_free_list_sz * SMALL_CHUNKS_PER_LARGE_CHUNK; all_small_chunks_key_t* small_items = malloc(sizeof(all_small_chunks_key_t) * num_objects); item* lru_trigger; size_t max_key_size = SMALL_TITLE_CHUNK_DATA_SZ; size_t i; char key[KEY_MAX_LENGTH]; size_t klen; size_t large_free_list_sz = fsi.large_free_list_sz, small_free_list_sz = fsi.small_free_list_sz; V_PRINTF(1, " * %s\n", __FUNCTION__); TASSERT(fsi.large_free_list_sz != 0); TASSERT(fsi.small_free_list_sz == 0); for (i = 0; i < num_objects; i ++) { V_PRINTF(2, "\r * allocating object %lu", i); V_FLUSH(2); do { small_items[i].klen = make_random_key(small_items[i].key, max_key_size, true); } while (assoc_find(small_items[i].key, small_items[i].klen)); small_items[i].it = do_item_alloc(small_items[i].key, small_items[i].klen, FLAGS, 0, 0, addr); TASSERT(small_items[i].it); TASSERT(is_item_large_chunk(small_items[i].it) == false); do_item_link(small_items[i].it, small_items[i].key); } V_PRINTF(2, "\n"); TASSERT(fsi.large_free_list_sz == 0 && fsi.small_free_list_sz == 0); V_LPRINTF(2, "alloc before deref\n"); do { klen = make_random_key(key, max_key_size, true); } while (assoc_find(key, klen)); lru_trigger = do_item_alloc(key, klen, FLAGS, 0, 0, addr); TASSERT(lru_trigger == NULL); V_LPRINTF(2, "dereferencing objects\n"); for (i = 0; i < num_objects; i ++) { do_item_deref(small_items[i].it); } V_LPRINTF(2, "alloc after deref\n"); lru_trigger = do_item_alloc(key, klen, FLAGS, 0, 0, addr); TASSERT(lru_trigger != NULL); V_LPRINTF(2, "search for evicted object\n"); TASSERT(assoc_find(small_items[0].key, small_items[0].klen) == NULL); V_LPRINTF(2, "ensuring that objects that shouldn't be evicted are still present\n"); for (i = 1; i < num_objects; i ++) { TASSERT(assoc_find(small_items[i].key, small_items[i].klen)); } V_LPRINTF(2, "cleanup objects\n"); for (i = 1; i < num_objects; i ++) { do_item_unlink(small_items[i].it, UNLINK_NORMAL, small_items[i].key); } do_item_deref(lru_trigger); TASSERT(fsi.large_free_list_sz == large_free_list_sz && fsi.small_free_list_sz == small_free_list_sz); return 0; }
/* * allocate all memory with small and large chunks. link them such that the * small items are the oldest. allocate one large object that can be covered by * the release of one large item. this covers part of case 4 for the large item * alloc in flat_storage_lru_evict(..). */ static int mixed_items_release_one_large_item_test(int verbose) { typedef struct { item* it; char key[KEY_MAX_LENGTH]; uint8_t klen; } mixed_items_release_one_small_item_t; size_t num_small_objects = (fsi.large_free_list_sz / 2) * SMALL_CHUNKS_PER_LARGE_CHUNK; /* this is not the same as fsi.large_free_list_sz / 2 due to rounding. */ size_t num_large_objects = fsi.large_free_list_sz - (fsi.large_free_list_sz / 2); mixed_items_release_one_small_item_t* large_items = malloc(sizeof(mixed_items_release_one_small_item_t) * num_large_objects); mixed_items_release_one_small_item_t* small_items = malloc(sizeof(mixed_items_release_one_small_item_t) * num_small_objects); item* lru_trigger; size_t max_small_key_size = SMALL_TITLE_CHUNK_DATA_SZ; size_t min_size_for_large_chunk = ( sizeof( ((small_title_chunk_t*) 0)->data ) ) + ( (SMALL_CHUNKS_PER_LARGE_CHUNK - 1) * sizeof( ((small_body_chunk_t*) 0)->data ) ) + 1; size_t i; char key[KEY_MAX_LENGTH]; size_t klen; size_t large_free_list_sz = fsi.large_free_list_sz, small_free_list_sz = fsi.small_free_list_sz; V_PRINTF(1, " * %s\n", __FUNCTION__); TASSERT(fsi.large_free_list_sz != 0); TASSERT(fsi.small_free_list_sz == 0); for (i = 0; i < num_large_objects; i ++) { V_PRINTF(2, "\r * allocating large object %lu", i); V_FLUSH(2); do { large_items[i].klen = make_random_key(large_items[i].key, KEY_MAX_LENGTH, true); } while (assoc_find(large_items[i].key, large_items[i].klen)); large_items[i].it = do_item_alloc(large_items[i].key, large_items[i].klen, FLAGS, 0, min_size_for_large_chunk - large_items[i].klen, addr); TASSERT(large_items[i].it); TASSERT(is_item_large_chunk(large_items[i].it)); do_item_link(large_items[i].it, large_items[i].key); } V_PRINTF(2, "\n"); for (i = 0; i < num_small_objects; i ++) { V_PRINTF(2, "\r * allocating small object %lu", i); V_FLUSH(2); do { small_items[i].klen = make_random_key(small_items[i].key, max_small_key_size, true); } while (assoc_find(small_items[i].key, small_items[i].klen)); small_items[i].it = do_item_alloc(small_items[i].key, small_items[i].klen, FLAGS, 0, 0, addr); TASSERT(small_items[i].it); TASSERT(is_item_large_chunk(small_items[i].it) == 0); do_item_link(small_items[i].it, small_items[i].key); } V_PRINTF(2, "\n"); TASSERT(fsi.large_free_list_sz == 0 && fsi.small_free_list_sz == 0); V_LPRINTF(2, "alloc before deref\n"); do { klen = make_random_key(key, max_small_key_size, true); } while (assoc_find(key, klen)); lru_trigger = do_item_alloc(key, klen, FLAGS, 0, 0, addr); TASSERT(lru_trigger == NULL); V_LPRINTF(2, "dereferencing objects\n"); for (i = 0; i < num_small_objects; i ++) { do_item_deref(small_items[i].it); } for (i = 0; i < num_large_objects; i ++) { do_item_deref(large_items[i].it); } V_LPRINTF(2, "alloc after deref\n"); lru_trigger = do_item_alloc(key, klen, FLAGS, 0, min_size_for_large_chunk - klen, addr); TASSERT(lru_trigger != NULL); V_LPRINTF(2, "search for evicted object\n"); TASSERT(assoc_find(large_items[0].key, large_items[0].klen) == NULL); V_LPRINTF(2, "ensuring that objects that shouldn't be evicted are still present\n"); for (i = 0; i < num_small_objects; i ++) { TASSERT(assoc_find(small_items[i].key, small_items[i].klen)); } for (i = 1; i < num_large_objects; i ++) { TASSERT(assoc_find(large_items[i].key, large_items[i].klen)); } V_LPRINTF(2, "cleanup objects\n"); for (i = 0; i < num_small_objects; i ++) { do_item_unlink(small_items[i].it, UNLINK_NORMAL, small_items[i].key); } for (i = 1; i < num_large_objects; i ++) { do_item_unlink(large_items[i].it, UNLINK_NORMAL, large_items[i].key); } do_item_deref(lru_trigger); TASSERT(fsi.large_free_list_sz == large_free_list_sz && fsi.small_free_list_sz == small_free_list_sz); return 0; }
/* * allocate all memory with small and large chunks. link them such the * allocation of a large object will start evicting small chunks but then stop * because the large chunk LRU has an older item. this covers part of case 3 * and part of case 4 for the small item alloc in flat_storage_lru_evict(..). */ static int mixed_items_release_small_and_large_items_scan_stop_test(int verbose) { typedef struct { item* it; char key[KEY_MAX_LENGTH]; uint8_t klen; } mixed_items_release_one_small_item_t; size_t num_small_objects = (fsi.large_free_list_sz / 2) * SMALL_CHUNKS_PER_LARGE_CHUNK; /* this is not the same as fsi.large_free_list_sz / 2 due to rounding. */ size_t num_large_objects = fsi.large_free_list_sz - (fsi.large_free_list_sz / 2); mixed_items_release_one_small_item_t* large_items = malloc(sizeof(mixed_items_release_one_small_item_t) * num_large_objects); mixed_items_release_one_small_item_t* small_items = malloc(sizeof(mixed_items_release_one_small_item_t) * num_small_objects); item* lru_trigger; size_t max_small_key_size = SMALL_TITLE_CHUNK_DATA_SZ; size_t min_size_for_large_chunk = ( sizeof( ((small_title_chunk_t*) 0)->data ) ) + ( (SMALL_CHUNKS_PER_LARGE_CHUNK - 1) * sizeof( ((small_body_chunk_t*) 0)->data ) ) + 1; size_t i; char key[KEY_MAX_LENGTH]; size_t klen; size_t large_free_list_sz = fsi.large_free_list_sz, small_free_list_sz = fsi.small_free_list_sz; V_PRINTF(1, " * %s\n", __FUNCTION__); TASSERT(fsi.large_free_list_sz != 0); TASSERT(fsi.small_free_list_sz == 0); for (i = 0; i < num_small_objects; i ++) { V_PRINTF(2, "\r * allocating small object %lu", i); V_FLUSH(2); do { small_items[i].klen = make_random_key(small_items[i].key, max_small_key_size, true); } while (assoc_find(small_items[i].key, small_items[i].klen)); small_items[i].it = do_item_alloc(small_items[i].key, small_items[i].klen, FLAGS, 0, 0, addr); TASSERT(small_items[i].it); TASSERT(is_item_large_chunk(small_items[i].it) == 0); do_item_link(small_items[i].it, small_items[i].key); } V_PRINTF(2, "\n"); for (i = 0; i < num_large_objects; i ++) { V_PRINTF(2, "\r * allocating large object %lu", i); V_FLUSH(2); do { large_items[i].klen = make_random_key(large_items[i].key, KEY_MAX_LENGTH, true); } while (assoc_find(large_items[i].key, large_items[i].klen)); large_items[i].it = do_item_alloc(large_items[i].key, large_items[i].klen, FLAGS, 0, min_size_for_large_chunk - large_items[i].klen, addr); TASSERT(large_items[i].it); TASSERT(is_item_large_chunk(large_items[i].it)); do_item_link(large_items[i].it, large_items[i].key); } V_PRINTF(2, "\n"); TASSERT(fsi.large_free_list_sz == 0 && fsi.small_free_list_sz == 0); V_LPRINTF(2, "update items\n"); /* update the objects we want to clobber *first*. but since ties go to the * large item, we need to bump the time stamp to ensure the small item is * released first. */ current_time += ITEM_UPDATE_INTERVAL + 1; /* initial bump to ensure that * LRU reordering takes place. */ do_item_update(small_items[0].it); current_time += 1; do_item_update(large_items[0].it); /* bump the timestamp and add the remaining items. */ current_time += 1; for (i = 1; i < num_small_objects; i ++) { do_item_update(small_items[i].it); } for (i = 1; i < num_large_objects; i ++) { do_item_update(large_items[i].it); } V_LPRINTF(2, "dereferencing objects\n"); for (i = 0; i < num_small_objects; i ++) { do_item_deref(small_items[i].it); } for (i = 0; i < num_large_objects; i ++) { do_item_deref(large_items[i].it); } V_LPRINTF(2, "alloc after deref\n"); do { klen = make_random_key(key, max_small_key_size, true); } while (assoc_find(key, klen)); lru_trigger = do_item_alloc(key, klen, FLAGS, 0, LARGE_TITLE_CHUNK_DATA_SZ - klen, addr); TASSERT(lru_trigger != NULL); TASSERT(is_item_large_chunk(lru_trigger)); V_LPRINTF(2, "search for evicted objects\n"); TASSERT(assoc_find(small_items[0].key, small_items[0].klen) == NULL); TASSERT(assoc_find(large_items[0].key, large_items[0].klen) == NULL); V_LPRINTF(2, "ensuring that objects that shouldn't be evicted are still present\n"); for (i = 1; i < num_small_objects; i ++) { TASSERT(assoc_find(small_items[i].key, small_items[i].klen)); } for (i = 1; i < num_large_objects; i ++) { TASSERT(assoc_find(large_items[i].key, large_items[i].klen)); } V_LPRINTF(2, "cleanup objects\n"); for (i = 1; i < num_small_objects; i ++) { do_item_unlink(small_items[i].it, UNLINK_NORMAL, small_items[i].key); } for (i = 1; i < num_large_objects; i ++) { do_item_unlink(large_items[i].it, UNLINK_NORMAL, large_items[i].key); } do_item_deref(lru_trigger); TASSERT(fsi.large_free_list_sz == large_free_list_sz && fsi.small_free_list_sz == small_free_list_sz); return 0; }
/* * allocate nearly all memory with small items (all memory - * SMALL_CHUNKS_PER_LARGE_CHUNK - 1). then set it up such that there is only * one item eligible to be freed (i.e., by removing the remaining items from the * LRU. allocate one large object. this will require the migration of one * single chunk item at the LRU head. this covers part of case 1 for the small * item alloc in flat_storage_lru_evict(..). */ static int all_small_items_migrate_small_single_chunk_item_at_lru_head_test(int verbose) { typedef struct { item* it; char key[KEY_MAX_LENGTH]; uint8_t klen; } test_keys_t; size_t num_objects = fsi.large_free_list_sz * SMALL_CHUNKS_PER_LARGE_CHUNK; test_keys_t* items = malloc(sizeof(test_keys_t) * num_objects); item* lru_trigger; size_t max_small_key_size = SMALL_TITLE_CHUNK_DATA_SZ; size_t min_size_for_large_chunk = ( sizeof( ((small_title_chunk_t*) 0)->data ) ) + ( (SMALL_CHUNKS_PER_LARGE_CHUNK - 1) * sizeof( ((small_body_chunk_t*) 0)->data ) ) + 1; size_t i, count; char key[KEY_MAX_LENGTH]; size_t klen; size_t large_free_list_sz = fsi.large_free_list_sz, small_free_list_sz = fsi.small_free_list_sz; V_PRINTF(1, " * %s\n", __FUNCTION__); TASSERT(fsi.large_free_list_sz != 0); TASSERT(fsi.small_free_list_sz == 0); for (i = 0, count = 0; fsi.large_free_list_sz || fsi.small_free_list_sz > SMALL_CHUNKS_PER_LARGE_CHUNK - 1; i ++, count ++) { V_PRINTF(2, "\r * allocating small object %lu", i); V_FLUSH(2); assert(i < num_objects); do { items[i].klen = make_random_key(items[i].key, max_small_key_size, true); } while (assoc_find(items[i].key, items[i].klen)); items[i].it = do_item_alloc(items[i].key, items[i].klen, FLAGS, 0, 0, addr); TASSERT(items[i].it); TASSERT(is_item_large_chunk(items[i].it) == 0); do_item_link(items[i].it, items[i].key); } V_PRINTF(2, "\n"); TASSERT(fsi.large_free_list_sz == 0); TASSERT(fsi.small_free_list_sz == SMALL_CHUNKS_PER_LARGE_CHUNK - 1); // remove all but one item from the LRU. and release our reference to the // item we don't remove from the LRU. for (i = 0; i < count - 1; i ++) { do_item_unlink(items[i].it, UNLINK_NORMAL, items[i].key); } do_item_deref(items[count - 1].it); TASSERT(fsi.lru_head == items[count - 1].it); TASSERT(fsi.large_free_list_sz == 0); TASSERT(fsi.small_free_list_sz == SMALL_CHUNKS_PER_LARGE_CHUNK - 1); V_LPRINTF(2, "alloc\n"); do { klen = make_random_key(key, max_small_key_size, true); } while (assoc_find(key, klen)); lru_trigger = do_item_alloc(key, klen, FLAGS, 0, min_size_for_large_chunk - klen, addr); TASSERT(lru_trigger != NULL); V_LPRINTF(2, "search for evicted object\n"); TASSERT(assoc_find(items[count - 1].key, items[count - 1].klen) == NULL); V_LPRINTF(2, "cleanup objects\n"); for (i = 0; i < count - 1; i ++) { do_item_deref(items[i].it); } do_item_deref(lru_trigger); TASSERT(fsi.large_free_list_sz == large_free_list_sz && fsi.small_free_list_sz == small_free_list_sz); return 0; }
/* * this is a negative test to ensure the proper behavior when we don't have * sufficient resources. in this case, we have sufficient small items on the * LRU, and enough of them have refcount == 0, but all the parent broken chunks * have refcount > 0. */ static int insufficient_available_large_broken_chunks(int verbose) { typedef struct { item* it; char key[KEY_MAX_LENGTH]; uint8_t klen; } all_small_chunks_key_t; size_t num_objects = fsi.large_free_list_sz * SMALL_CHUNKS_PER_LARGE_CHUNK; all_small_chunks_key_t* small_items = malloc(sizeof(all_small_chunks_key_t) * num_objects); item* lru_trigger; size_t max_key_size = SMALL_TITLE_CHUNK_DATA_SZ; size_t min_size_for_large_chunk = ( sizeof( ((small_title_chunk_t*) 0)->data ) ) + ( (SMALL_CHUNKS_PER_LARGE_CHUNK - 1) * sizeof( ((small_body_chunk_t*) 0)->data ) ) + 1; size_t i; char key[KEY_MAX_LENGTH]; size_t klen; size_t large_free_list_sz = fsi.large_free_list_sz, small_free_list_sz = fsi.small_free_list_sz; V_PRINTF(1, " * %s\n", __FUNCTION__); TASSERT(fsi.large_free_list_sz != 0); TASSERT(fsi.small_free_list_sz == 0); for (i = 0; i < num_objects; i ++) { V_PRINTF(2, "\r * allocating object %lu", i); V_FLUSH(2); do { small_items[i].klen = make_random_key(small_items[i].key, max_key_size, true); } while (assoc_find(small_items[i].key, small_items[i].klen)); small_items[i].it = do_item_alloc(small_items[i].key, small_items[i].klen, FLAGS, 0, 0, addr); TASSERT(small_items[i].it); TASSERT(is_item_large_chunk(small_items[i].it) == false); do_item_link(small_items[i].it, small_items[i].key); } V_PRINTF(2, "\n"); TASSERT(fsi.large_free_list_sz == 0 && fsi.small_free_list_sz == 0); V_LPRINTF(2, "alloc before deref\n"); do { klen = make_random_key(key, max_key_size, true); } while (assoc_find(key, klen)); lru_trigger = do_item_alloc(key, klen, FLAGS, 0, min_size_for_large_chunk - klen, addr); TASSERT(lru_trigger == NULL); V_LPRINTF(2, "dereferencing objects\n"); for (i = 0; i < num_objects; i += 2) { do_item_deref(small_items[i].it); } V_LPRINTF(2, "alloc after deref\n"); lru_trigger = do_item_alloc(key, klen, FLAGS, 0, min_size_for_large_chunk - klen, addr); TASSERT(lru_trigger == NULL); V_LPRINTF(2, "ensuring that objects that shouldn't be evicted are still present\n"); for (i = 0; i < num_objects; i ++) { bool should_be_found; /* we free everything we encounter that has no refcount until we hit the * LRU_SEARCH_DEPTH, at which time we cease searching. */ if (i % 2 == 0 && i < (LRU_SEARCH_DEPTH * 2)) { should_be_found = false; } else { should_be_found = true; } TASSERT((assoc_find(small_items[i].key, small_items[i].klen) ? (true) : (false)) == should_be_found); } V_LPRINTF(2, "cleanup objects\n"); for (i = 0; i < num_objects; i ++) { /* we dereference all the odd numbered items */ if ((i % 2) != 0) { do_item_deref(small_items[i].it); } /* we unlink everything that's still in the LRU. */ if (i % 2 == 0 && i < (LRU_SEARCH_DEPTH * 2)) { ; } else { do_item_unlink(small_items[i].it, UNLINK_NORMAL, small_items[i].key); } } TASSERT(fsi.large_free_list_sz == large_free_list_sz && fsi.small_free_list_sz == small_free_list_sz); return 0; }
/* * allocate all memory with small items. allocate one large object that can be * covered by the release of small items, but also requires the migration of * single chunk items. this covers part of case 1 for the large item alloc in * flat_storage_lru_evict(..). */ static int all_small_items_migrate_small_single_chunk_items_test(int verbose) { typedef struct { item* it; char key[KEY_MAX_LENGTH]; uint8_t klen; } test_keys_t; size_t num_objects = fsi.large_free_list_sz * SMALL_CHUNKS_PER_LARGE_CHUNK; test_keys_t* items = malloc(sizeof(test_keys_t) * num_objects); item* lru_trigger; size_t max_small_key_size = SMALL_TITLE_CHUNK_DATA_SZ; size_t min_size_for_large_chunk = ( sizeof( ((small_title_chunk_t*) 0)->data ) ) + ( (SMALL_CHUNKS_PER_LARGE_CHUNK - 1) * sizeof( ((small_body_chunk_t*) 0)->data ) ) + 1; size_t i; char key[KEY_MAX_LENGTH]; size_t klen; size_t large_free_list_sz = fsi.large_free_list_sz, small_free_list_sz = fsi.small_free_list_sz; V_PRINTF(1, " * %s\n", __FUNCTION__); TASSERT(fsi.large_free_list_sz != 0); TASSERT(fsi.small_free_list_sz == 0); for (i = 0; i < num_objects; i ++) { V_PRINTF(2, "\r * allocating small object %lu", i); V_FLUSH(2); do { items[i].klen = make_random_key(items[i].key, max_small_key_size, true); } while (assoc_find(items[i].key, items[i].klen)); items[i].it = do_item_alloc(items[i].key, items[i].klen, FLAGS, 0, 0, addr); TASSERT(items[i].it); TASSERT(is_item_large_chunk(items[i].it) == 0); do_item_link(items[i].it, items[i].key); } V_PRINTF(2, "\n"); TASSERT(fsi.large_free_list_sz == 0 && fsi.small_free_list_sz == 0); /* access items we don't want to move. */ current_time += ITEM_UPDATE_INTERVAL + 1; /* touch every other item. the ones that are not touched in (0, * SMALL_CHUNKS_PER_LARGE_CHUNK * 2) will be evicted. */ for (i = 0; i < SMALL_CHUNKS_PER_LARGE_CHUNK * 2; i += 2) { do_item_update(items[i].it); } /* touch remaining items */ for (i = SMALL_CHUNKS_PER_LARGE_CHUNK * 2; i < num_objects; i ++) { do_item_update(items[i].it); } V_LPRINTF(2, "dereferencing objects\n"); for (i = 0; i < num_objects; i ++) { do_item_deref(items[i].it); } V_LPRINTF(2, "alloc after deref\n"); do { klen = make_random_key(key, max_small_key_size, true); } while (assoc_find(key, klen)); lru_trigger = do_item_alloc(key, klen, FLAGS, 0, min_size_for_large_chunk - klen, addr); TASSERT(lru_trigger != NULL); V_LPRINTF(2, "search for evicted object\n"); for (i = 1; i < SMALL_CHUNKS_PER_LARGE_CHUNK * 2; i += 2) { TASSERT(assoc_find(items[i].key, items[i].klen) == NULL); } V_LPRINTF(2, "ensuring that objects that shouldn't be evicted are still present\n"); for (i = 0; i < SMALL_CHUNKS_PER_LARGE_CHUNK * 2; i += 2) { /* these may have been moved. */ TASSERT((items[i].it = assoc_find(items[i].key, items[i].klen))); } for (i = SMALL_CHUNKS_PER_LARGE_CHUNK * 2; i < num_objects; i ++) { TASSERT(assoc_find(items[i].key, items[i].klen)); } V_LPRINTF(2, "cleanup objects\n"); for (i = 0; i < SMALL_CHUNKS_PER_LARGE_CHUNK * 2; i += 2) { do_item_unlink(items[i].it, UNLINK_NORMAL, items[i].key); } for (i = SMALL_CHUNKS_PER_LARGE_CHUNK * 2; i < num_objects; i ++) { do_item_unlink(items[i].it, UNLINK_NORMAL, items[i].key); } do_item_deref(lru_trigger); TASSERT(fsi.large_free_list_sz == large_free_list_sz && fsi.small_free_list_sz == small_free_list_sz); return 0; }
/** wrapper around assoc_find which does the lazy expiration logic */ item *do_item_get(const char *key, const size_t nkey, const uint32_t hv) { //mutex_lock(&cache_lock); item *it = assoc_find(key, nkey, hv); if (it != NULL) { refcount_incr(&it->refcount); /* Optimization for slab reassignment. prevents popular items from * jamming in busy wait. Can only do this here to satisfy lock order * of item_lock, cache_lock, slabs_lock. */ if (slab_rebalance_signal && ((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) { do_item_unlink_nolock(it, hv); do_item_remove(it); it = NULL; } } //mutex_unlock(&cache_lock); int was_found = 0; if (settings.verbose > 2) { int ii; if (it == NULL) { fprintf(stderr, "> NOT FOUND "); } else { fprintf(stderr, "> FOUND KEY "); was_found++; } for (ii = 0; ii < nkey; ++ii) { fprintf(stderr, "%c", key[ii]); } } if (it != NULL) { if (settings.oldest_live != 0 && settings.oldest_live <= current_time && it->time <= settings.oldest_live) { do_item_unlink(it, hv); do_item_remove(it); it = NULL; if (was_found) { fprintf(stderr, " -nuked by flush"); } } else if (it->exptime != 0 && it->exptime <= current_time) { do_item_unlink(it, hv); do_item_remove(it); it = NULL; if (was_found) { fprintf(stderr, " -nuked by expire"); } } else { it->it_flags |= ITEM_FETCHED; DEBUG_REFCNT(it, '+'); if (settings.anti_stampede > 0 && !(it->it_flags & ITEM_FAKE_MISSED) && it->exptime != 0 && it->exptime < current_time + settings.anti_stampede && it->exptime <= current_time + rand() % settings.anti_stampede) { it->it_flags |= ITEM_FAKE_MISSED; if (settings.verbose > 2) fprintf(stderr," - FAKE MISS (stampede protection)"); refcount_decr(&it->refcount); it = NULL; } } } if (settings.verbose > 2) fprintf(stderr, "\n"); return it; }
void process_command(conn *c, char *command) { int comm = 0; /* * for commands set/add/replace, we build an item and read the data * directly into it, then continue in nread_complete(). */ if ((strncmp(command, "add ", 4) == 0 && (comm = NREAD_ADD)) || (strncmp(command, "set ", 4) == 0 && (comm = NREAD_SET)) || (strncmp(command, "replace ", 8) == 0 && (comm = NREAD_REPLACE))) { char s_comm[10]; char key[256]; int flags; time_t expire; int len, res; item *it; res = sscanf(command, "%s %s %u %u %d\n", s_comm, key, &flags, &expire, &len); if (res!=5 || strlen(key)==0 ) { out_string(c, "CLIENT_ERROR bad command line format"); return; } it = item_alloc(key, flags, expire, len+2); if (it == 0) { out_string(c, "SERVER_ERROR out of memory"); c->write_and_close = 1; return; } c->item_comm = comm; c->item = it; c->rcurr = it->data; c->rlbytes = it->nbytes; c->state = conn_nread; return; } if (strncmp(command, "get ", 4) == 0) { char *start = command + 4; char key[256]; int next; int i = 0; item *it; time_t now = time(0); while(sscanf(start, " %s%n", key, &next) >= 1) { start+=next; stats.get_cmds++; it = (item *)assoc_find(key); if (it && (it->it_flags & ITEM_DELETED)) { it = 0; } if (it && it->exptime && it->exptime < now) { item_unlink(it); it = 0; } if (it) { stats.get_hits++; it->usecount++; item_update(it); *(c->ilist + i) = it; i++; if (i > c->isize) { c->isize *= 2; c->ilist = realloc(c->ilist, sizeof(item *)*c->isize); } } else stats.get_misses++; } c->icurr = c->ilist; c->ileft = i; if (c->ileft) { c->ipart = 0; c->state = conn_mwrite; c->ibytes = 0; return; } else { out_string(c, "END"); return; } } if (strncmp(command, "delete ", 7) == 0) { char key [256]; char *start = command+7; item *it; sscanf(start, " %s", key); it = assoc_find(key); if (!it) { out_string(c, "NOT_FOUND"); return; } else { it->usecount++; /* use its expiration time as its deletion time now */ it->exptime = time(0) + 4; it->it_flags |= ITEM_DELETED; todelete[delcurr++] = it; if (delcurr >= deltotal) { deltotal *= 2; todelete = realloc(todelete, sizeof(item *)*deltotal); } } out_string(c, "DELETED"); return; } if (strncmp(command, "stats", 5) == 0) { process_stat(c, command); return; } if (strcmp(command, "version") == 0) { out_string(c, "VERSION 2.0"); return; } out_string(c, "ERROR"); return; }
static void storage_compact_readback(void *storage, logger *l, bool drop_unread, char *readback_buf, uint32_t page_id, uint64_t page_version, uint64_t read_size) { uint64_t offset = 0; unsigned int rescues = 0; unsigned int lost = 0; unsigned int skipped = 0; while (offset < read_size) { item *hdr_it = NULL; item_hdr *hdr = NULL; item *it = (item *)(readback_buf+offset); unsigned int ntotal; // probably zeroed out junk at the end of the wbuf if (it->nkey == 0) { break; } ntotal = ITEM_ntotal(it); uint32_t hv = (uint32_t)it->time; item_lock(hv); // We don't have a conn and don't need to do most of do_item_get hdr_it = assoc_find(ITEM_key(it), it->nkey, hv); if (hdr_it != NULL) { bool do_write = false; refcount_incr(hdr_it); // Check validity but don't bother removing it. if ((hdr_it->it_flags & ITEM_HDR) && !item_is_flushed(hdr_it) && (hdr_it->exptime == 0 || hdr_it->exptime > current_time)) { hdr = (item_hdr *)ITEM_data(hdr_it); if (hdr->page_id == page_id && hdr->page_version == page_version) { // Item header is still completely valid. extstore_delete(storage, page_id, page_version, 1, ntotal); // drop inactive items. if (drop_unread && GET_LRU(hdr_it->slabs_clsid) == COLD_LRU) { do_write = false; skipped++; } else { do_write = true; } } } if (do_write) { bool do_update = false; int tries; obj_io io; io.len = ntotal; io.mode = OBJ_IO_WRITE; for (tries = 10; tries > 0; tries--) { if (extstore_write_request(storage, PAGE_BUCKET_COMPACT, PAGE_BUCKET_COMPACT, &io) == 0) { memcpy(io.buf, it, io.len); extstore_write(storage, &io); do_update = true; break; } else { usleep(1000); } } if (do_update) { if (it->refcount == 2) { hdr->page_version = io.page_version; hdr->page_id = io.page_id; hdr->offset = io.offset; rescues++; } else { lost++; // TODO: re-alloc and replace header. } } else { lost++; } } do_item_remove(hdr_it); } item_unlock(hv); offset += ntotal; if (read_size - offset < sizeof(struct _stritem)) break; } STATS_LOCK(); stats.extstore_compact_lost += lost; stats.extstore_compact_rescues += rescues; stats.extstore_compact_skipped += skipped; STATS_UNLOCK(); LOGGER_LOG(l, LOG_SYSEVENTS, LOGGER_COMPACT_READ_END, NULL, page_id, offset, rescues, lost, skipped); }
/** wrapper around assoc_find which does the lazy expiration logic */ item *do_item_get(const char *key, const size_t nkey, const uint32_t hv, conn *c) { item *it = assoc_find(key, nkey, hv); if (it != NULL) { refcount_incr(&it->refcount); /* Optimization for slab reassignment. prevents popular items from * jamming in busy wait. Can only do this here to satisfy lock order * of item_lock, slabs_lock. */ /* This was made unsafe by removal of the cache_lock: * slab_rebalance_signal and slab_rebal.* are modified in a separate * thread under slabs_lock. If slab_rebalance_signal = 1, slab_start = * NULL (0), but slab_end is still equal to some value, this would end * up unlinking every item fetched. * This is either an acceptable loss, or if slab_rebalance_signal is * true, slab_start/slab_end should be put behind the slabs_lock. * Which would cause a huge potential slowdown. * Could also use a specific lock for slab_rebal.* and * slab_rebalance_signal (shorter lock?) */ /*if (slab_rebalance_signal && ((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) { do_item_unlink(it, hv); do_item_remove(it); it = NULL; }*/ } int was_found = 0; if (settings.verbose > 2) { int ii; if (it == NULL) { fprintf(stderr, "> NOT FOUND "); } else { fprintf(stderr, "> FOUND KEY "); } for (ii = 0; ii < nkey; ++ii) { fprintf(stderr, "%c", key[ii]); } } if (it != NULL) { was_found = 1; if (item_is_flushed(it)) { do_item_unlink(it, hv); do_item_remove(it); it = NULL; pthread_mutex_lock(&c->thread->stats.mutex); c->thread->stats.get_flushed++; pthread_mutex_unlock(&c->thread->stats.mutex); if (settings.verbose > 2) { fprintf(stderr, " -nuked by flush"); } was_found = 2; } else if (it->exptime != 0 && it->exptime <= current_time) { do_item_unlink(it, hv); do_item_remove(it); it = NULL; pthread_mutex_lock(&c->thread->stats.mutex); c->thread->stats.get_expired++; pthread_mutex_unlock(&c->thread->stats.mutex); if (settings.verbose > 2) { fprintf(stderr, " -nuked by expire"); } was_found = 3; } else { it->it_flags |= ITEM_FETCHED|ITEM_ACTIVE; DEBUG_REFCNT(it, '+'); } } if (settings.verbose > 2) fprintf(stderr, "\n"); /* For now this is in addition to the above verbose logging. */ LOGGER_LOG(c->thread->l, LOG_FETCHERS, LOGGER_ITEM_GET, NULL, was_found, key, nkey); return it; }