static ngx_int_t
ngx_buffer_cache_init(ngx_shm_zone_t *shm_zone, void *data)
{
	ngx_buffer_cache_sh_t *sh;
	ngx_buffer_cache_t *ocache = data;
	ngx_buffer_cache_t *cache;
	u_char* p;

	cache = shm_zone->data;

	if (ocache)
	{
		cache->sh = ocache->sh;
		cache->shpool = ocache->shpool;
		return NGX_OK;
	}

	cache->shpool = (ngx_slab_pool_t *)shm_zone->shm.addr;

	if (shm_zone->shm.exists) 
	{
		cache->sh = cache->shpool->data;
		return NGX_OK;
	}

	// start following the ngx_slab_pool_t that was allocated at the beginning of the chunk
	p = shm_zone->shm.addr + sizeof(ngx_slab_pool_t);

	// initialize the log context
	cache->shpool->log_ctx = p;
	p = ngx_sprintf(cache->shpool->log_ctx, " in buffer cache \"%V\"%Z", &shm_zone->shm.name);

	// allocate the shared cache state
	p = ngx_align_ptr(p, sizeof(void *));
	sh = (ngx_buffer_cache_sh_t*)p;
	p += sizeof(*sh);
	cache->sh = sh;

	cache->shpool->data = sh;

	// initialize fixed cache fields
	p = ngx_align_ptr(p, sizeof(void *));
	sh->entries_start = (ngx_buffer_cache_entry_t*)p;
	sh->buffers_end = shm_zone->shm.addr + shm_zone->shm.size;
	sh->access_time = 0;

	// reset the stats
	ngx_memzero(&sh->stats, sizeof(sh->stats));

	// reset the cache status
	ngx_buffer_cache_reset(sh);
	sh->reset = 0;

	return NGX_OK;
}
ngx_flag_t
ngx_buffer_cache_store_gather(
	ngx_buffer_cache_t* cache, 
	u_char* key, 
	ngx_str_t* buffers,
	size_t buffer_count)
{
	ngx_buffer_cache_entry_t* entry;
	ngx_buffer_cache_sh_t *sh = cache->sh;
	ngx_str_t* cur_buffer;
	ngx_str_t* last_buffer;
	size_t buffer_size;
	uint32_t hash;
	uint32_t evictions;
	u_char* target_buffer;

	hash = ngx_crc32_short(key, BUFFER_CACHE_KEY_SIZE);

	ngx_shmtx_lock(&cache->shpool->mutex);

	if (sh->reset)
	{
		// a previous store operation was killed in progress, need to reset the cache
		// since the data structures may be corrupt. we can only reset the cache after
		// the access time expires since other processes may still be reading from / 
		// writing to the cache
		if (ngx_time() < sh->access_time + CACHE_LOCK_EXPIRATION)
		{
			ngx_shmtx_unlock(&cache->shpool->mutex);
			return 0;
		}

		// reset the cache, leave the reset flag enabled
		ngx_buffer_cache_reset(sh);

		// update stats
		sh->stats.reset++;
	}
	else
	{
		// remove expired entries
		if (cache->expiration)
		{
			for (evictions = MAX_EVICTIONS_PER_STORE; evictions > 0; evictions--)
			{
				if (!ngx_buffer_cache_free_oldest_entry(sh, cache->expiration))
				{
					break;
				}
			}
		}

		// make sure the entry does not already exist
		entry = ngx_buffer_cache_rbtree_lookup(&sh->rbtree, key, hash);
		if (entry != NULL)
		{
			sh->stats.store_exists++;
			ngx_shmtx_unlock(&cache->shpool->mutex);
			return 0;
		}

		// enable the reset flag before we start making any changes
		sh->reset = 1;
	}

	// allocate a new entry
	entry = ngx_buffer_cache_get_free_entry(sh);
	if (entry == NULL)
	{
		goto error;
	}

	// calculate the buffer size
	last_buffer = buffers + buffer_count;
	buffer_size = 0;
	for (cur_buffer = buffers; cur_buffer < last_buffer; cur_buffer++)
	{
		buffer_size += cur_buffer->len;
	}

	// allocate a buffer to hold the data
	target_buffer = ngx_buffer_cache_get_free_buffer(sh, buffer_size);
	if (target_buffer == NULL)
	{
		goto error;
	}

	// initialize the entry
	entry->state = CES_ALLOCATED;
	entry->node.key = hash;
	memcpy(entry->key, key, BUFFER_CACHE_KEY_SIZE);
	entry->start_offset = target_buffer;
	entry->buffer_size = buffer_size;

	// update the write position
	sh->buffers_write = target_buffer;

	// move from free_queue to used_queue
	ngx_queue_remove(&entry->queue_node);
	ngx_queue_insert_tail(&sh->used_queue, &entry->queue_node);

	// insert to rbtree
	ngx_rbtree_insert(&sh->rbtree, &entry->node);

	// update stats
	sh->stats.store_ok++;
	sh->stats.store_bytes += buffer_size;

	// Note: the memcpy is performed after releasing the lock to avoid holding the lock for a long time
	//		setting the access time of the entry and cache prevents it from being freed
	sh->access_time = entry->access_time = ngx_time();
	entry->write_time = ngx_time();

	sh->reset = 0;
	ngx_shmtx_unlock(&cache->shpool->mutex);

	for (cur_buffer = buffers; cur_buffer < last_buffer; cur_buffer++)
	{
		target_buffer = ngx_copy(target_buffer, cur_buffer->data, cur_buffer->len);
	}

	// Note: no need to obtain the lock since state is ngx_atomic_t
	entry->state = CES_READY;

	return 1;

error:
	sh->stats.store_err++;
	sh->reset = 0;
	ngx_shmtx_unlock(&cache->shpool->mutex);
	return 0;
}