/** 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; }
/** 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); 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; }
/** 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; }
static hash_item *do_item_get(struct demo_engine *engine, const char *key, const size_t nkey, bool LRU_reposition) { rel_time_t current_time = engine->server.core->get_current_time(); hash_item *it = dm_assoc_find(engine, engine->server.core->hash(key, nkey, 0), key, nkey); if (it != NULL) { if (do_item_isvalid(engine, it, current_time)==false) { do_item_unlink(engine, it, ITEM_UNLINK_INVALID); it = NULL; } } if (it != NULL) { ITEM_REFCOUNT_INCR(it); DEBUG_REFCNT(it, '+'); } if (engine->config.verbose > 2) { if (it == NULL) { logger->log(EXTENSION_LOG_INFO, NULL, "> NOT FOUND %s\n", key); } else { logger->log(EXTENSION_LOG_INFO, NULL, "> FOUND KEY %s\n", (const char*)dm_item_get_key(it)); } } return it; }
/* * Unlinks an item from the LRU and hashtable. */ void item_unlink(item *item) { uint32_t hv; hv = hash(ITEM_key(item), item->nkey, 0); item_lock(hv); do_item_unlink(item, hv); item_unlock(hv); }
/* * adds a delta value to a numeric item. * * c connection requesting the operation * it item to adjust * incr true to increment value, false to decrement * delta amount to adjust value by * @param ritem The resulting item after adding the delta. Only valid if * ENGINE_SUCCESS is returned. Caller is responsible for calling * do_item_release() on this when finished with it. * * returns a response code to send back to the client. */ static ENGINE_ERROR_CODE do_add_delta(struct default_engine *engine, hash_item *it, const bool incr, const int64_t delta, item** ritem, uint64_t *result, const void *cookie) { const char *ptr; uint64_t value; char buf[80]; int res; if (it->nbytes >= (sizeof(buf) - 1)) { return ENGINE_EINVAL; } ptr = item_get_data(it); memcpy(buf, ptr, it->nbytes); buf[it->nbytes] = '\0'; if (!safe_strtoull(buf, &value)) { return ENGINE_EINVAL; } if (incr) { value += delta; } else { if ((uint64_t)delta > value) { value = 0; } else { value -= delta; } } *result = value; res = snprintf(buf, sizeof(buf), "%" PRIu64, value); if (res < 0 || res >= sizeof(buf)) { return ENGINE_EINVAL; } if (it->refcount == 1 && res <= (int)it->nbytes) { /* we can do inline replacement */ memcpy(item_get_data(it), buf, res); memset(item_get_data(it) + res, ' ', it->nbytes - res); item_set_cas(NULL, NULL, it, get_cas_id()); *ritem = it; } else { hash_item *new_it = do_item_alloc(engine, item_get_key(it), it->flags, it->exptime, res, cookie, it->datatype); if (new_it == NULL) { do_item_unlink(engine, it); return ENGINE_ENOMEM; } memcpy(item_get_data(new_it), buf, res); do_item_replace(engine, it, new_it); *ritem = new_it; } return ENGINE_SUCCESS; }
/* * Flushes expired items after a flush_all call */ void item_flush_expired(struct default_engine *engine) { cb_mutex_enter(&engine->items.lock); rel_time_t now = engine->server.core->get_current_time(); if (now > engine->config.oldest_live) { engine->config.oldest_live = now - 1; } for (int ii = 0; ii < POWER_LARGEST; ii++) { hash_item *iter, *next; /* * The LRU is sorted in decreasing time order, and an item's * timestamp is never newer than its last access time, so we * only need to walk back until we hit an item older than the * oldest_live time. * The oldest_live checking will auto-expire the remaining items. */ for (iter = engine->items.heads[ii]; iter != NULL; iter = next) { if (iter->time >= engine->config.oldest_live) { next = iter->next; if ((iter->iflag & ITEM_SLABBED) == 0) { do_item_unlink(engine, iter); } } else { /* We've hit the first old item. Continue to the next queue. */ break; } } } cb_mutex_exit(&engine->items.lock); }
static ENGINE_ERROR_CODE item_scrub(struct default_engine *engine, hash_item *item, void *cookie) { rel_time_t current_time = engine->server.core->get_current_time(); (void)cookie; engine->scrubber.visited++; /* scrubber is used for generic bucket deletion and scrub_cmd all expired or orphaned items are unlinked */ if (engine->scrubber.force_delete && item->refcount > 0) { // warn that someone isn't releasing items before deleting their bucket. EXTENSION_LOGGER_DESCRIPTOR* logger; logger = (void*)engine->server.extension->get_extension(EXTENSION_LOGGER); logger->log(EXTENSION_LOG_WARNING, NULL, "Bucket (%d) deletion is removing an item with refcount %d", engine->bucket_id, item->refcount); } if (engine->scrubber.force_delete || (item->refcount == 0 && (item->exptime != 0 && item->exptime < current_time))) { do_item_unlink(engine, item); engine->scrubber.cleaned++; } return ENGINE_SUCCESS; }
int do_item_replace(item *it, item *new_it) { MEMCACHED_ITEM_REPLACE(ITEM_key(it), it->nkey, it->nbytes, ITEM_key(new_it), new_it->nkey, new_it->nbytes); assert((it->it_flags & ITEM_SLABBED) == 0); do_item_unlink(it); return do_item_link(new_it); }
int do_item_replace(item *it, item *new_it, const uint32_t hv) { new_it->it_flags &= ~ITEM_FAKE_MISSED; MEMCACHED_ITEM_REPLACE(ITEM_key(it), it->nkey, it->nbytes, ITEM_key(new_it), new_it->nkey, new_it->nbytes); assert((it->it_flags & ITEM_SLABBED) == 0); do_item_unlink(it, hv); return do_item_link(new_it, hv); }
/** 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; }
void LRU_list::item_unlink(base_item* item) { uint32_t hv; hv = HashTable::hash(item->data, item->nkey); //哈希局部锁 hashtable.hash_lock(hv); do_item_unlink(item, hv); hashtable.hash_unlock(hv); }
int do_item_replace(item *it, item *new_it, const uint32_t hv) { syslog(LOG_INFO, "[%s:%s:%d]", __FILE__, __func__, __LINE__); MEMCACHED_ITEM_REPLACE(ITEM_key(it), it->nkey, it->nbytes, ITEM_key(new_it), new_it->nkey, new_it->nbytes); assert((it->it_flags & ITEM_SLABBED) == 0); do_item_unlink(it, hv); return do_item_link(new_it, hv); }
int do_item_replace(struct default_engine *engine, hash_item *it, hash_item *new_it) { MEMCACHED_ITEM_REPLACE(item_get_key(it), it->nkey, it->nbytes, item_get_key(new_it), new_it->nkey, new_it->nbytes); assert((it->iflag & ITEM_SLABBED) == 0); do_item_unlink(engine, it); return do_item_link(engine, new_it); }
//用新的item替换老的item int do_item_replace(item *it, item *new_it, const uint32_t hv) { MEMCACHED_ITEM_REPLACE(ITEM_key(it), it->nkey, it->nbytes, ITEM_key(new_it), new_it->nkey, new_it->nbytes); assert((it->it_flags & ITEM_SLABBED) == 0);//判断it是已经分配过的,如果未分配,则断言失败 do_item_unlink(it, hv);//删除原来的item return do_item_link(new_it, hv);//重新添加新的item }
/** 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; }
static ENGINE_ERROR_CODE do_item_dcp_step(struct default_engine *engine, struct dcp_connection *connection, const void *cookie, struct dcp_message_producers *producers) { ENGINE_ERROR_CODE ret = ENGINE_DISCONNECT; while (connection->it == NULL) { if (!do_item_walk_cursor(engine, &connection->cursor, 1, item_dcp_iterfunc, connection, &ret)) { /* find next slab class to look at.. */ bool linked = false; int ii; for (ii = connection->cursor.slabs_clsid + 1; ii < POWER_LARGEST && !linked; ++ii) { if (engine->items.heads[ii] != NULL) { /* add the item at the tail */ do_item_link_cursor(engine, &connection->cursor, ii); linked = true; } } if (!linked) { break; } } } if (connection->it != NULL) { rel_time_t current_time = engine->server.core->get_current_time(); rel_time_t exptime = connection->it->exptime; if (exptime != 0 && exptime < current_time) { const hash_key* key = item_get_key(connection->it); ret = producers->expiration(cookie, connection->opaque, hash_key_get_client_key(key), hash_key_get_client_key_len(key), item_get_cas(connection->it), 0, 0, 0, NULL, 0); if (ret == ENGINE_SUCCESS) { do_item_unlink(engine, connection->it); do_item_release(engine, connection->it); } } else { ret = producers->mutation(cookie, connection->opaque, connection->it, 0, 0, 0, 0, NULL, 0, 0); } if (ret == ENGINE_SUCCESS) { connection->it = NULL; } } else { return ENGINE_DISCONNECT; } return ret; }
static void do_item_replace(struct demo_engine *engine, hash_item *it, hash_item *new_it) { MEMCACHED_ITEM_REPLACE(dm_item_get_key(it), it->nkey, it->nbytes, dm_item_get_key(new_it), new_it->nkey, new_it->nbytes); do_item_unlink(engine, it, ITEM_UNLINK_REPLACE); /* Cache item replacement does not drop the prefix item even if it's empty. * So, the below do_item_link function always return SUCCESS. */ (void)do_item_link(engine, new_it); }
static ENGINE_ERROR_CODE item_scrub(struct default_engine *engine, hash_item *item, void *cookie) { (void)cookie; engine->scrubber.visited++; rel_time_t current_time = engine->server.core->get_current_time(); if (item->refcount == 0 && (item->exptime != 0 && item->exptime < current_time)) { do_item_unlink(engine, item); engine->scrubber.cleaned++; } return ENGINE_SUCCESS; }
void do_item_stats(ADD_STAT add_stats, void *c) { int i; for (i = 0; i < LARGEST_ID; i++) { if (tails[i] != NULL) { const char *fmt = "items:%d:%s"; char key_str[STAT_KEY_LEN]; char val_str[STAT_VAL_LEN]; int klen = 0, vlen = 0; int search = 50; while (search > 0 && tails[i] != NULL && ((settings.oldest_live != 0 && /* Item flushd */ settings.oldest_live <= current_time && tails[i]->time <= settings.oldest_live) || (tails[i]->exptime != 0 && /* and not expired */ tails[i]->exptime < current_time))) { --search; if (tails[i]->refcount == 0) { do_item_unlink(tails[i]); } else { break; } } if (tails[i] == NULL) { /* We removed all of the items in this slab class */ continue; } APPEND_NUM_FMT_STAT(fmt, i, "number", "%u", sizes[i]); APPEND_NUM_FMT_STAT(fmt, i, "age", "%u", tails[i]->time); APPEND_NUM_FMT_STAT(fmt, i, "evicted", "%u", itemstats[i].evicted); APPEND_NUM_FMT_STAT(fmt, i, "evicted_nonzero", "%u", itemstats[i].evicted_nonzero); APPEND_NUM_FMT_STAT(fmt, i, "evicted_time", "%u", itemstats[i].evicted_time); APPEND_NUM_FMT_STAT(fmt, i, "outofmemory", "%u", itemstats[i].outofmemory); APPEND_NUM_FMT_STAT(fmt, i, "tailrepairs", "%u", itemstats[i].tailrepairs);; APPEND_NUM_FMT_STAT(fmt, i, "reclaimed", "%u", itemstats[i].reclaimed);; } } /* getting here means both ascii and binary terminators fit */ add_stats(NULL, 0, NULL, 0, c); }
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; }
static void do_item_stats(struct default_engine *engine, ADD_STAT add_stats, const void *c) { int i; rel_time_t current_time = engine->server.core->get_current_time(); for (i = 0; i < POWER_LARGEST; i++) { if (engine->items.tails[i] != NULL) { int search = search_items; while (search > 0 && engine->items.tails[i] != NULL && ((engine->config.oldest_live != 0 && /* Item flushd */ engine->config.oldest_live <= current_time && engine->items.tails[i]->time <= engine->config.oldest_live) || (engine->items.tails[i]->exptime != 0 && /* and not expired */ engine->items.tails[i]->exptime < current_time))) { --search; if (engine->items.tails[i]->refcount == 0) { do_item_unlink(engine, engine->items.tails[i]); } else { break; } } if (engine->items.tails[i] == NULL) { /* We removed all of the items in this slab class */ continue; } const char *prefix = "items"; add_statistics(c, add_stats, prefix, i, "number", "%u", engine->items.sizes[i]); add_statistics(c, add_stats, prefix, i, "age", "%u", engine->items.tails[i]->time); add_statistics(c, add_stats, prefix, i, "evicted", "%u", engine->items.itemstats[i].evicted); add_statistics(c, add_stats, prefix, i, "evicted_nonzero", "%u", engine->items.itemstats[i].evicted_nonzero); add_statistics(c, add_stats, prefix, i, "evicted_time", "%u", engine->items.itemstats[i].evicted_time); add_statistics(c, add_stats, prefix, i, "outofmemory", "%u", engine->items.itemstats[i].outofmemory); add_statistics(c, add_stats, prefix, i, "tailrepairs", "%u", engine->items.itemstats[i].tailrepairs);; add_statistics(c, add_stats, prefix, i, "reclaimed", "%u", engine->items.itemstats[i].reclaimed);; } } }
static ENGINE_ERROR_CODE do_item_delete(struct demo_engine *engine, const void* key, const size_t nkey, uint64_t cas) { ENGINE_ERROR_CODE ret; hash_item *it = do_item_get(engine, key, nkey, true); if (it == NULL) { ret = ENGINE_KEY_ENOENT; } else { if (cas == 0 || cas == dm_item_get_cas(it)) { do_item_unlink(engine, it, ITEM_UNLINK_NORMAL); ret = ENGINE_SUCCESS; } else { ret = ENGINE_KEY_EEXISTS; } do_item_release(engine, it); } return ret; }
/* expires items that are more recent than the oldest_live setting. */ void do_item_flush_expired(void) { int i; item *iter, *next; if (settings.oldest_live == 0) return; for (i = 0; i < LARGEST_ID; i++) { /* The LRU is sorted in decreasing time order, and an item's timestamp * is never newer than its last access time, so we only need to walk * back until we hit an item older than the oldest_live time. * The oldest_live checking will auto-expire the remaining items. */ for (iter = heads[i]; iter != NULL; iter = next) { if (iter->time >= settings.oldest_live) { next = iter->next; if ((iter->it_flags & ITEM_SLABBED) == 0) { do_item_unlink(iter); } } else { /* We've hit the first old item. Continue to the next queue. */ break; } } } }
/*@null@*/ item *do_item_alloc(char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes) { uint8_t nsuffix; item *it; char suffix[40]; size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix); unsigned int id = slabs_clsid(ntotal); if (id == 0) return 0; it = slabs_alloc(ntotal); if (it == 0) { int tries = 50; item *search; /* If requested to not push old items out of cache when memory runs out, * we're out of luck at this point... */ if (settings.evict_to_free == 0) return NULL; /* * try to get one off the right LRU * don't necessariuly unlink the tail because it may be locked: refcount>0 * search up from tail an item with refcount==0 and unlink it; give up after 50 * tries */ if (id > LARGEST_ID) return NULL; if (tails[id] == 0) return NULL; for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) { if (search->refcount == 0) { if (search->exptime == 0 || search->exptime > current_time) { STATS_LOCK(); stats.evictions++; STATS_UNLOCK(); } do_item_unlink(search); break; } } it = slabs_alloc(ntotal); if (it == 0) return NULL; } assert(it->slabs_clsid == 0); it->slabs_clsid = id; assert(it != heads[it->slabs_clsid]); it->next = it->prev = it->h_next = 0; it->refcount = 1; /* the caller will have a reference */ DEBUG_REFCNT(it, '*'); it->it_flags = 0; it->nkey = nkey; it->nbytes = nbytes; strcpy(ITEM_key(it), key); it->exptime = exptime; memcpy(ITEM_suffix(it), suffix, (size_t)nsuffix); it->nsuffix = nsuffix; return it; }
int do_item_replace(item *it, item *new_it) { assert((it->it_flags & ITEM_SLABBED) == 0); do_item_unlink(it); return do_item_link(new_it); }
/*@null@*/ item *do_item_alloc(char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes) { uint8_t nsuffix; item *it = NULL; char suffix[40]; size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix); if (settings.use_cas) { ntotal += sizeof(uint64_t); } unsigned int id = slabs_clsid(ntotal); if (id == 0) return 0; /* do a quick check if we have any expired items in the tail.. */ int tries = 50; item *search; for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) { if (search->refcount == 0 && (search->exptime != 0 && search->exptime < current_time)) { it = search; /* I don't want to actually free the object, just steal * the item to avoid to grab the slab mutex twice ;-) */ it->refcount = 1; do_item_unlink(it); /* Initialize the item block: */ it->slabs_clsid = 0; it->refcount = 0; break; } } if (it == NULL && (it = slabs_alloc(ntotal, id)) == NULL) { /* ** Could not find an expired item at the tail, and memory allocation ** failed. Try to evict some items! */ tries = 50; /* If requested to not push old items out of cache when memory runs out, * we're out of luck at this point... */ if (settings.evict_to_free == 0) { itemstats[id].outofmemory++; return NULL; } /* * try to get one off the right LRU * don't necessariuly unlink the tail because it may be locked: refcount>0 * search up from tail an item with refcount==0 and unlink it; give up after 50 * tries */ if (tails[id] == 0) { itemstats[id].outofmemory++; return NULL; } for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) { if (search->refcount == 0) { if (search->exptime == 0 || search->exptime > current_time) { itemstats[id].evicted++; itemstats[id].evicted_time = current_time - search->time; STATS_LOCK(); stats.evictions++; STATS_UNLOCK(); } do_item_unlink(search); break; } } it = slabs_alloc(ntotal, id); if (it == 0) { 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, * and it may happen in the future. * We can reasonably assume no item can stay locked for more than * three hours, so if we find one in the tail which is that old, * free it anyway. */ tries = 50; for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) { if (search->refcount != 0 && search->time + TAIL_REPAIR_TIME < current_time) { itemstats[id].tailrepairs++; search->refcount = 0; do_item_unlink(search); break; } } it = slabs_alloc(ntotal, id); if (it == 0) { return NULL; } } } assert(it->slabs_clsid == 0); it->slabs_clsid = id; assert(it != heads[it->slabs_clsid]); it->next = it->prev = it->h_next = 0; it->refcount = 1; /* the caller will have a reference */ DEBUG_REFCNT(it, '*'); it->it_flags = settings.use_cas ? ITEM_CAS : 0; it->nkey = nkey; it->nbytes = nbytes; memcpy(ITEM_key(it), key, nkey); it->exptime = exptime; memcpy(ITEM_suffix(it), suffix, (size_t)nsuffix); it->nsuffix = nsuffix; return it; }
/* * 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; }