blob: 589cb4135905271eb70a2ea3dabeb4f47fa9e57e [file] [log] [blame] [raw]
/*
* Copyright (C) Yichun Zhang (agentzh)
*/
#ifndef DDEBUG
#define DDEBUG 0
#endif
#include "ddebug.h"
#include "ngx_http_lua_shdict.h"
#include "ngx_http_lua_util.h"
#include "ngx_http_lua_api.h"
static int ngx_http_lua_shdict_set(lua_State *L);
static int ngx_http_lua_shdict_safe_set(lua_State *L);
static int ngx_http_lua_shdict_get(lua_State *L);
static int ngx_http_lua_shdict_get_stale(lua_State *L);
static int ngx_http_lua_shdict_get_helper(lua_State *L, int get_stale);
static int ngx_http_lua_shdict_expire(ngx_http_lua_shdict_ctx_t *ctx,
ngx_uint_t n);
static ngx_int_t ngx_http_lua_shdict_lookup(ngx_shm_zone_t *shm_zone,
ngx_uint_t hash, u_char *kdata, size_t klen,
ngx_http_lua_shdict_node_t **sdp);
static int ngx_http_lua_shdict_set_helper(lua_State *L, int flags);
static int ngx_http_lua_shdict_add(lua_State *L);
static int ngx_http_lua_shdict_safe_add(lua_State *L);
static int ngx_http_lua_shdict_replace(lua_State *L);
static int ngx_http_lua_shdict_incr(lua_State *L);
static int ngx_http_lua_shdict_delete(lua_State *L);
static int ngx_http_lua_shdict_flush_all(lua_State *L);
static int ngx_http_lua_shdict_flush_expired(lua_State *L);
static int ngx_http_lua_shdict_get_keys(lua_State *L);
#define NGX_HTTP_LUA_SHDICT_ADD 0x0001
#define NGX_HTTP_LUA_SHDICT_REPLACE 0x0002
#define NGX_HTTP_LUA_SHDICT_SAFE_STORE 0x0004
ngx_int_t
ngx_http_lua_shdict_init_zone(ngx_shm_zone_t *shm_zone, void *data)
{
ngx_http_lua_shdict_ctx_t *octx = data;
size_t len;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_http_lua_main_conf_t *lmcf;
dd("init zone");
ctx = shm_zone->data;
if (octx) {
ctx->sh = octx->sh;
ctx->shpool = octx->shpool;
goto done;
}
ctx->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
if (shm_zone->shm.exists) {
ctx->sh = ctx->shpool->data;
goto done;
}
ctx->sh = ngx_slab_alloc(ctx->shpool, sizeof(ngx_http_lua_shdict_shctx_t));
if (ctx->sh == NULL) {
return NGX_ERROR;
}
ctx->shpool->data = ctx->sh;
ngx_rbtree_init(&ctx->sh->rbtree, &ctx->sh->sentinel,
ngx_http_lua_shdict_rbtree_insert_value);
ngx_queue_init(&ctx->sh->queue);
len = sizeof(" in lua_shared_dict zone \"\"") + shm_zone->shm.name.len;
ctx->shpool->log_ctx = ngx_slab_alloc(ctx->shpool, len);
if (ctx->shpool->log_ctx == NULL) {
return NGX_ERROR;
}
ngx_sprintf(ctx->shpool->log_ctx, " in lua_shared_dict zone \"%V\"%Z",
&shm_zone->shm.name);
done:
dd("get lmcf");
lmcf = ctx->main_conf;
dd("lmcf->lua: %p", lmcf->lua);
lmcf->shm_zones_inited++;
if (lmcf->shm_zones_inited == lmcf->shm_zones->nelts
&& lmcf->init_handler)
{
if (lmcf->init_handler(ctx->log, lmcf, lmcf->lua) != 0) {
/* an error happened */
return NGX_ERROR;
}
}
return NGX_OK;
}
void
ngx_http_lua_shdict_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
ngx_rbtree_node_t **p;
ngx_http_lua_shdict_node_t *sdn, *sdnt;
for ( ;; ) {
if (node->key < temp->key) {
p = &temp->left;
} else if (node->key > temp->key) {
p = &temp->right;
} else { /* node->key == temp->key */
sdn = (ngx_http_lua_shdict_node_t *) &node->color;
sdnt = (ngx_http_lua_shdict_node_t *) &temp->color;
p = ngx_memn2cmp(sdn->data, sdnt->data, sdn->key_len,
sdnt->key_len) < 0 ? &temp->left : &temp->right;
}
if (*p == sentinel) {
break;
}
temp = *p;
}
*p = node;
node->parent = temp;
node->left = sentinel;
node->right = sentinel;
ngx_rbt_red(node);
}
static ngx_int_t
ngx_http_lua_shdict_lookup(ngx_shm_zone_t *shm_zone, ngx_uint_t hash,
u_char *kdata, size_t klen, ngx_http_lua_shdict_node_t **sdp)
{
ngx_int_t rc;
ngx_time_t *tp;
uint64_t now;
int64_t ms;
ngx_rbtree_node_t *node, *sentinel;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_http_lua_shdict_node_t *sd;
ctx = shm_zone->data;
node = ctx->sh->rbtree.root;
sentinel = ctx->sh->rbtree.sentinel;
while (node != sentinel) {
if (hash < node->key) {
node = node->left;
continue;
}
if (hash > node->key) {
node = node->right;
continue;
}
/* hash == node->key */
sd = (ngx_http_lua_shdict_node_t *) &node->color;
rc = ngx_memn2cmp(kdata, sd->data, klen, (size_t) sd->key_len);
if (rc == 0) {
ngx_queue_remove(&sd->queue);
ngx_queue_insert_head(&ctx->sh->queue, &sd->queue);
*sdp = sd;
dd("node expires: %lld", (long long) sd->expires);
if (sd->expires != 0) {
tp = ngx_timeofday();
now = (uint64_t) tp->sec * 1000 + tp->msec;
ms = sd->expires - now;
dd("time to live: %lld", (long long) ms);
if (ms < 0) {
dd("node already expired");
return NGX_DONE;
}
}
return NGX_OK;
}
node = (rc < 0) ? node->left : node->right;
}
*sdp = NULL;
return NGX_DECLINED;
}
static int
ngx_http_lua_shdict_expire(ngx_http_lua_shdict_ctx_t *ctx, ngx_uint_t n)
{
ngx_time_t *tp;
uint64_t now;
ngx_queue_t *q;
int64_t ms;
ngx_rbtree_node_t *node;
ngx_http_lua_shdict_node_t *sd;
int freed = 0;
tp = ngx_timeofday();
now = (uint64_t) tp->sec * 1000 + tp->msec;
/*
* n == 1 deletes one or two expired entries
* n == 0 deletes oldest entry by force
* and one or two zero rate entries
*/
while (n < 3) {
if (ngx_queue_empty(&ctx->sh->queue)) {
return freed;
}
q = ngx_queue_last(&ctx->sh->queue);
sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue);
if (n++ != 0) {
if (sd->expires == 0) {
return freed;
}
ms = sd->expires - now;
if (ms > 0) {
return freed;
}
}
ngx_queue_remove(q);
node = (ngx_rbtree_node_t *)
((u_char *) sd - offsetof(ngx_rbtree_node_t, color));
ngx_rbtree_delete(&ctx->sh->rbtree, node);
ngx_slab_free_locked(ctx->shpool, node);
freed++;
}
return freed;
}
void
ngx_http_lua_inject_shdict_api(ngx_http_lua_main_conf_t *lmcf, lua_State *L)
{
ngx_http_lua_shdict_ctx_t *ctx;
ngx_uint_t i;
ngx_shm_zone_t **zone;
if (lmcf->shm_zones != NULL) {
lua_createtable(L, 0, lmcf->shm_zones->nelts /* nrec */);
/* ngx.shared */
lua_createtable(L, 0 /* narr */, 13 /* nrec */); /* shared mt */
lua_pushcfunction(L, ngx_http_lua_shdict_get);
lua_setfield(L, -2, "get");
lua_pushcfunction(L, ngx_http_lua_shdict_get_stale);
lua_setfield(L, -2, "get_stale");
lua_pushcfunction(L, ngx_http_lua_shdict_set);
lua_setfield(L, -2, "set");
lua_pushcfunction(L, ngx_http_lua_shdict_safe_set);
lua_setfield(L, -2, "safe_set");
lua_pushcfunction(L, ngx_http_lua_shdict_add);
lua_setfield(L, -2, "add");
lua_pushcfunction(L, ngx_http_lua_shdict_safe_add);
lua_setfield(L, -2, "safe_add");
lua_pushcfunction(L, ngx_http_lua_shdict_replace);
lua_setfield(L, -2, "replace");
lua_pushcfunction(L, ngx_http_lua_shdict_incr);
lua_setfield(L, -2, "incr");
lua_pushcfunction(L, ngx_http_lua_shdict_delete);
lua_setfield(L, -2, "delete");
lua_pushcfunction(L, ngx_http_lua_shdict_flush_all);
lua_setfield(L, -2, "flush_all");
lua_pushcfunction(L, ngx_http_lua_shdict_flush_expired);
lua_setfield(L, -2, "flush_expired");
lua_pushcfunction(L, ngx_http_lua_shdict_get_keys);
lua_setfield(L, -2, "get_keys");
lua_pushvalue(L, -1); /* shared mt mt */
lua_setfield(L, -2, "__index"); /* shared mt */
zone = lmcf->shm_zones->elts;
for (i = 0; i < lmcf->shm_zones->nelts; i++) {
ctx = zone[i]->data;
lua_pushlstring(L, (char *) ctx->name.data, ctx->name.len);
/* shared mt key */
lua_pushlightuserdata(L, zone[i]); /* shared mt key ud */
lua_pushvalue(L, -3); /* shared mt key ud mt */
lua_setmetatable(L, -2); /* shared mt key ud */
lua_rawset(L, -4); /* shared mt */
}
lua_pop(L, 1); /* shared */
} else {
lua_newtable(L); /* ngx.shared */
}
lua_setfield(L, -2, "shared");
}
static int
ngx_http_lua_shdict_get(lua_State *L)
{
return ngx_http_lua_shdict_get_helper(L, 0 /* stale */);
}
static int
ngx_http_lua_shdict_get_stale(lua_State *L)
{
return ngx_http_lua_shdict_get_helper(L, 1 /* stale */);
}
static int
ngx_http_lua_shdict_get_helper(lua_State *L, int get_stale)
{
int n;
ngx_str_t name;
ngx_str_t key;
uint32_t hash;
ngx_int_t rc;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_http_lua_shdict_node_t *sd;
ngx_str_t value;
int value_type;
double num;
u_char c;
ngx_shm_zone_t *zone;
uint32_t user_flags = 0;
n = lua_gettop(L);
if (n != 2) {
return luaL_error(L, "expecting exactly two arguments, "
"but only seen %d", n);
}
zone = lua_touserdata(L, 1);
if (zone == NULL) {
return luaL_error(L, "bad \"zone\" argument");
}
ctx = zone->data;
name = ctx->name;
if (lua_isnil(L, 2)) {
lua_pushnil(L);
lua_pushliteral(L, "nil key");
return 2;
}
key.data = (u_char *) luaL_checklstring(L, 2, &key.len);
if (key.len == 0) {
lua_pushnil(L);
lua_pushliteral(L, "empty key");
return 2;
}
if (key.len > 65535) {
lua_pushnil(L);
lua_pushliteral(L, "key too long");
return 2;
}
hash = ngx_crc32_short(key.data, key.len);
#if (NGX_DEBUG)
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"fetching key \"%V\" in shared dict \"%V\"", &key, &name);
#endif /* NGX_DEBUG */
ngx_shmtx_lock(&ctx->shpool->mutex);
#if 1
if (!get_stale) {
ngx_http_lua_shdict_expire(ctx, 1);
}
#endif
rc = ngx_http_lua_shdict_lookup(zone, hash, key.data, key.len, &sd);
dd("shdict lookup returns %d", (int) rc);
if (rc == NGX_DECLINED || (rc == NGX_DONE && !get_stale)) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushnil(L);
return 1;
}
/* rc == NGX_OK || (rc == NGX_DONE && get_stale) */
value_type = sd->value_type;
dd("data: %p", sd->data);
dd("key len: %d", (int) sd->key_len);
value.data = sd->data + sd->key_len;
value.len = (size_t) sd->value_len;
switch (value_type) {
case LUA_TSTRING:
lua_pushlstring(L, (char *) value.data, value.len);
break;
case LUA_TNUMBER:
if (value.len != sizeof(double)) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
return luaL_error(L, "bad lua number value size found for key %s "
"in shared_dict %s: %lu", key.data, name.data,
(unsigned long) value.len);
}
num = *(double *) value.data;
lua_pushnumber(L, num);
break;
case LUA_TBOOLEAN:
if (value.len != sizeof(u_char)) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
return luaL_error(L, "bad lua boolean value size found for key %s "
"in shared_dict %s: %lu", key.data, name.data,
(unsigned long) value.len);
}
c = *value.data;
lua_pushboolean(L, c ? 1 : 0);
break;
default:
ngx_shmtx_unlock(&ctx->shpool->mutex);
return luaL_error(L, "bad value type found for key %s in "
"shared_dict %s: %d", key.data, name.data,
value_type);
}
user_flags = sd->user_flags;
ngx_shmtx_unlock(&ctx->shpool->mutex);
if (get_stale) {
/* always return value, flags, stale */
if (user_flags) {
lua_pushinteger(L, (lua_Integer) user_flags);
} else {
lua_pushnil(L);
}
lua_pushboolean(L, rc == NGX_DONE);
return 3;
}
if (user_flags) {
lua_pushinteger(L, (lua_Integer) user_flags);
return 2;
}
return 1;
}
static int
ngx_http_lua_shdict_delete(lua_State *L)
{
int n;
n = lua_gettop(L);
if (n != 2) {
return luaL_error(L, "expecting 2 arguments, "
"but only seen %d", n);
}
lua_pushnil(L);
return ngx_http_lua_shdict_set_helper(L, 0);
}
static int
ngx_http_lua_shdict_flush_all(lua_State *L)
{
ngx_queue_t *q;
ngx_http_lua_shdict_node_t *sd;
int n;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_shm_zone_t *zone;
n = lua_gettop(L);
if (n != 1) {
return luaL_error(L, "expecting 1 argument, but seen %d", n);
}
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
zone = lua_touserdata(L, 1);
if (zone == NULL) {
return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer");
}
ctx = zone->data;
ngx_shmtx_lock(&ctx->shpool->mutex);
for (q = ngx_queue_head(&ctx->sh->queue);
q != ngx_queue_sentinel(&ctx->sh->queue);
q = ngx_queue_next(q))
{
sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue);
sd->expires = 1;
}
ngx_http_lua_shdict_expire(ctx, 0);
ngx_shmtx_unlock(&ctx->shpool->mutex);
return 0;
}
static int
ngx_http_lua_shdict_flush_expired(lua_State *L)
{
ngx_queue_t *q, *prev;
ngx_http_lua_shdict_node_t *sd;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_shm_zone_t *zone;
ngx_time_t *tp;
int freed = 0;
int attempts = 0;
ngx_rbtree_node_t *node;
uint64_t now;
int n;
n = lua_gettop(L);
if (n != 1 && n != 2) {
return luaL_error(L, "expecting 1 or 2 argument(s), but saw %d", n);
}
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
zone = lua_touserdata(L, 1);
if (zone == NULL) {
return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer");
}
if (n == 2) {
attempts = luaL_checkint(L, 2);
}
ctx = zone->data;
ngx_shmtx_lock(&ctx->shpool->mutex);
if (ngx_queue_empty(&ctx->sh->queue)) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushnumber(L, 0);
return 1;
}
tp = ngx_timeofday();
now = (uint64_t) tp->sec * 1000 + tp->msec;
q = ngx_queue_last(&ctx->sh->queue);
while (q != ngx_queue_sentinel(&ctx->sh->queue)) {
prev = ngx_queue_prev(q);
sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue);
if (sd->expires != 0 && sd->expires <= now) {
ngx_queue_remove(q);
node = (ngx_rbtree_node_t *)
((u_char *) sd - offsetof(ngx_rbtree_node_t, color));
ngx_rbtree_delete(&ctx->sh->rbtree, node);
ngx_slab_free_locked(ctx->shpool, node);
freed++;
if (attempts && freed == attempts) {
break;
}
}
q = prev;
}
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushnumber(L, freed);
return 1;
}
/*
* This trades CPU for memory. This is potentially slow. O(2n)
*/
static int
ngx_http_lua_shdict_get_keys(lua_State *L)
{
ngx_queue_t *q, *prev;
ngx_http_lua_shdict_node_t *sd;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_shm_zone_t *zone;
ngx_time_t *tp;
int total = 0;
int attempts = 1024;
uint64_t now;
int n;
n = lua_gettop(L);
if (n != 1 && n != 2) {
return luaL_error(L, "expecting 1 or 2 argument(s), "
"but saw %d", n);
}
luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
zone = lua_touserdata(L, 1);
if (zone == NULL) {
return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer");
}
if (n == 2) {
attempts = luaL_checkint(L, 2);
}
ctx = zone->data;
ngx_shmtx_lock(&ctx->shpool->mutex);
if (ngx_queue_empty(&ctx->sh->queue)) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_createtable(L, 0, 0);
return 1;
}
tp = ngx_timeofday();
now = (uint64_t) tp->sec * 1000 + tp->msec;
/* first run through: get total number of elements we need to allocate */
q = ngx_queue_last(&ctx->sh->queue);
while (q != ngx_queue_sentinel(&ctx->sh->queue)) {
prev = ngx_queue_prev(q);
sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue);
if (sd->expires == 0 || sd->expires > now) {
total++;
if (attempts && total == attempts) {
break;
}
}
q = prev;
}
lua_createtable(L, total, 0);
/* second run through: add keys to table */
total = 0;
q = ngx_queue_last(&ctx->sh->queue);
while (q != ngx_queue_sentinel(&ctx->sh->queue)) {
prev = ngx_queue_prev(q);
sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue);
if (sd->expires == 0 || sd->expires > now) {
lua_pushlstring(L, (char *) sd->data, sd->key_len);
lua_rawseti(L, -2, ++total);
if (attempts && total == attempts) {
break;
}
}
q = prev;
}
ngx_shmtx_unlock(&ctx->shpool->mutex);
/* table is at top of stack */
return 1;
}
static int
ngx_http_lua_shdict_add(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_ADD);
}
static int
ngx_http_lua_shdict_safe_add(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_ADD
|NGX_HTTP_LUA_SHDICT_SAFE_STORE);
}
static int
ngx_http_lua_shdict_replace(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_REPLACE);
}
static int
ngx_http_lua_shdict_set(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, 0);
}
static int
ngx_http_lua_shdict_safe_set(lua_State *L)
{
return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_SAFE_STORE);
}
static int
ngx_http_lua_shdict_set_helper(lua_State *L, int flags)
{
int i, n;
ngx_str_t key;
uint32_t hash;
ngx_int_t rc;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_http_lua_shdict_node_t *sd;
ngx_str_t value;
int value_type;
double num;
u_char c;
lua_Number exptime = 0;
u_char *p;
ngx_rbtree_node_t *node;
ngx_time_t *tp;
ngx_shm_zone_t *zone;
int forcible = 0;
/* indicates whether to foricibly override other
* valid entries */
int32_t user_flags = 0;
n = lua_gettop(L);
if (n != 3 && n != 4 && n != 5) {
return luaL_error(L, "expecting 3, 4 or 5 arguments, "
"but only seen %d", n);
}
zone = lua_touserdata(L, 1);
if (zone == NULL) {
return luaL_error(L, "bad \"zone\" argument");
}
ctx = zone->data;
if (lua_isnil(L, 2)) {
lua_pushnil(L);
lua_pushliteral(L, "nil key");
return 2;
}
key.data = (u_char *) luaL_checklstring(L, 2, &key.len);
if (key.len == 0) {
lua_pushnil(L);
lua_pushliteral(L, "empty key");
return 2;
}
if (key.len > 65535) {
lua_pushnil(L);
lua_pushliteral(L, "key too long");
return 2;
}
hash = ngx_crc32_short(key.data, key.len);
value_type = lua_type(L, 3);
switch (value_type) {
case LUA_TSTRING:
value.data = (u_char *) lua_tolstring(L, 3, &value.len);
break;
case LUA_TNUMBER:
value.len = sizeof(double);
num = lua_tonumber(L, 3);
value.data = (u_char *) &num;
break;
case LUA_TBOOLEAN:
value.len = sizeof(u_char);
c = lua_toboolean(L, 3) ? 1 : 0;
value.data = &c;
break;
case LUA_TNIL:
if (flags & (NGX_HTTP_LUA_SHDICT_ADD|NGX_HTTP_LUA_SHDICT_REPLACE)) {
lua_pushnil(L);
lua_pushliteral(L, "attempt to add or replace nil values");
return 2;
}
value.len = 0;
value.data = NULL;
break;
default:
lua_pushnil(L);
lua_pushliteral(L, "bad value type");
return 2;
}
if (n >= 4) {
exptime = luaL_checknumber(L, 4);
if (exptime < 0) {
exptime = 0;
}
}
if (n == 5) {
user_flags = (uint32_t) luaL_checkinteger(L, 5);
}
ngx_shmtx_lock(&ctx->shpool->mutex);
#if 1
ngx_http_lua_shdict_expire(ctx, 1);
#endif
rc = ngx_http_lua_shdict_lookup(zone, hash, key.data, key.len, &sd);
dd("shdict lookup returned %d", (int) rc);
if (flags & NGX_HTTP_LUA_SHDICT_REPLACE) {
if (rc == NGX_DECLINED || rc == NGX_DONE) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushboolean(L, 0);
lua_pushliteral(L, "not found");
lua_pushboolean(L, forcible);
return 3;
}
/* rc == NGX_OK */
goto replace;
}
if (flags & NGX_HTTP_LUA_SHDICT_ADD) {
if (rc == NGX_OK) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushboolean(L, 0);
lua_pushliteral(L, "exists");
lua_pushboolean(L, forcible);
return 3;
}
if (rc == NGX_DONE) {
/* exists but expired */
dd("go to replace");
goto replace;
}
/* rc == NGX_DECLINED */
dd("go to insert");
goto insert;
}
if (rc == NGX_OK || rc == NGX_DONE) {
if (value_type == LUA_TNIL) {
goto remove;
}
replace:
if (value.data && value.len == (size_t) sd->value_len) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"lua shared dict set: found old entry and value "
"size matched, reusing it");
ngx_queue_remove(&sd->queue);
ngx_queue_insert_head(&ctx->sh->queue, &sd->queue);
sd->key_len = (u_short) key.len;
if (exptime > 0) {
tp = ngx_timeofday();
sd->expires = (uint64_t) tp->sec * 1000 + tp->msec
+ (uint64_t) (exptime * 1000);
} else {
sd->expires = 0;
}
sd->user_flags = user_flags;
sd->value_len = (uint32_t) value.len;
dd("setting value type to %d", value_type);
sd->value_type = (uint8_t) value_type;
p = ngx_copy(sd->data, key.data, key.len);
ngx_memcpy(p, value.data, value.len);
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushboolean(L, 1);
lua_pushnil(L);
lua_pushboolean(L, forcible);
return 3;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"lua shared dict set: found old entry bug value size "
"NOT matched, removing it first");
remove:
ngx_queue_remove(&sd->queue);
node = (ngx_rbtree_node_t *)
((u_char *) sd - offsetof(ngx_rbtree_node_t, color));
ngx_rbtree_delete(&ctx->sh->rbtree, node);
ngx_slab_free_locked(ctx->shpool, node);
}
insert:
/* rc == NGX_DECLINED or value size unmatch */
if (value.data == NULL) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushboolean(L, 1);
lua_pushnil(L);
lua_pushboolean(L, 0);
return 3;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"lua shared dict set: creating a new entry");
n = offsetof(ngx_rbtree_node_t, color)
+ offsetof(ngx_http_lua_shdict_node_t, data)
+ key.len
+ value.len;
node = ngx_slab_alloc_locked(ctx->shpool, n);
if (node == NULL) {
if (flags & NGX_HTTP_LUA_SHDICT_SAFE_STORE) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushboolean(L, 0);
lua_pushliteral(L, "no memory");
return 2;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"lua shared dict set: overriding non-expired items "
"due to memory shortage for entry \"%V\"", &key);
for (i = 0; i < 30; i++) {
if (ngx_http_lua_shdict_expire(ctx, 0) == 0) {
break;
}
forcible = 1;
node = ngx_slab_alloc_locked(ctx->shpool, n);
if (node != NULL) {
goto allocated;
}
}
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushboolean(L, 0);
lua_pushliteral(L, "no memory");
lua_pushboolean(L, forcible);
return 3;
}
allocated:
sd = (ngx_http_lua_shdict_node_t *) &node->color;
node->key = hash;
sd->key_len = (u_short) key.len;
if (exptime > 0) {
tp = ngx_timeofday();
sd->expires = (uint64_t) tp->sec * 1000 + tp->msec
+ (uint64_t) (exptime * 1000);
} else {
sd->expires = 0;
}
sd->user_flags = user_flags;
sd->value_len = (uint32_t) value.len;
dd("setting value type to %d", value_type);
sd->value_type = (uint8_t) value_type;
p = ngx_copy(sd->data, key.data, key.len);
ngx_memcpy(p, value.data, value.len);
ngx_rbtree_insert(&ctx->sh->rbtree, node);
ngx_queue_insert_head(&ctx->sh->queue, &sd->queue);
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushboolean(L, 1);
lua_pushnil(L);
lua_pushboolean(L, forcible);
return 3;
}
static int
ngx_http_lua_shdict_incr(lua_State *L)
{
int n;
ngx_str_t key;
uint32_t hash;
ngx_int_t rc;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_http_lua_shdict_node_t *sd;
double num;
u_char *p;
ngx_shm_zone_t *zone;
double value;
n = lua_gettop(L);
if (n != 3) {
return luaL_error(L, "expecting 3 arguments, but only seen %d", n);
}
if (lua_type(L, 1) != LUA_TLIGHTUSERDATA) {
return luaL_error(L, "bad \"zone\" argument");
}
zone = lua_touserdata(L, 1);
if (zone == NULL) {
return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer");
}
ctx = zone->data;
if (lua_isnil(L, 2)) {
lua_pushnil(L);
lua_pushliteral(L, "nil key");
return 2;
}
key.data = (u_char *) luaL_checklstring(L, 2, &key.len);
if (key.len == 0) {
lua_pushnil(L);
lua_pushliteral(L, "empty key");
return 2;
}
if (key.len > 65535) {
lua_pushnil(L);
lua_pushliteral(L, "key too long");
return 2;
}
hash = ngx_crc32_short(key.data, key.len);
value = luaL_checknumber(L, 3);
dd("looking up key %.*s in shared dict %.*s", (int) key.len, key.data,
(int) ctx->name.len, ctx->name.data);
ngx_shmtx_lock(&ctx->shpool->mutex);
#if 1
ngx_http_lua_shdict_expire(ctx, 1);
#endif
rc = ngx_http_lua_shdict_lookup(zone, hash, key.data, key.len, &sd);
dd("shdict lookup returned %d", (int) rc);
if (rc == NGX_DECLINED || rc == NGX_DONE) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushnil(L);
lua_pushliteral(L, "not found");
return 2;
}
/* rc == NGX_OK */
if (sd->value_type != LUA_TNUMBER || sd->value_len != sizeof(double)) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushnil(L);
lua_pushliteral(L, "not a number");
return 2;
}
ngx_queue_remove(&sd->queue);
ngx_queue_insert_head(&ctx->sh->queue, &sd->queue);
dd("setting value type to %d", (int) sd->value_type);
p = sd->data + key.len;
num = *(double *) p;
num += value;
ngx_memcpy(p, (double *) &num, sizeof(double));
ngx_shmtx_unlock(&ctx->shpool->mutex);
lua_pushnumber(L, num);
lua_pushnil(L);
return 2;
}
ngx_int_t
ngx_http_lua_shared_dict_get(ngx_shm_zone_t *zone, u_char *key_data,
size_t key_len, ngx_http_lua_value_t *value)
{
u_char *data;
size_t len;
uint32_t hash;
ngx_int_t rc;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_http_lua_shdict_node_t *sd;
if (zone == NULL) {
return NGX_ERROR;
}
hash = ngx_crc32_short(key_data, key_len);
ctx = zone->data;
ngx_shmtx_lock(&ctx->shpool->mutex);
rc = ngx_http_lua_shdict_lookup(zone, hash, key_data, key_len, &sd);
dd("shdict lookup returned %d", (int) rc);
if (rc == NGX_DECLINED || rc == NGX_DONE) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
return rc;
}
/* rc == NGX_OK */
value->type = sd->value_type;
dd("type: %d", (int) value->type);
data = sd->data + sd->key_len;
len = (size_t) sd->value_len;
switch (value->type) {
case LUA_TSTRING:
if (value->value.s.data == NULL || value->value.s.len == 0) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "no string buffer "
"initialized");
return NGX_ERROR;
}
if (len > value->value.s.len) {
len = value->value.s.len;
} else {
value->value.s.len = len;
}
ngx_memcpy(value->value.s.data, data, len);
break;
case LUA_TNUMBER:
if (len != sizeof(double)) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "bad lua number "
"value size found for key %*s: %lu", key_len,
key_data, (unsigned long) len);
ngx_shmtx_unlock(&ctx->shpool->mutex);
return NGX_ERROR;
}
ngx_memcpy(&value->value.b, data, len);
break;
case LUA_TBOOLEAN:
if (len != sizeof(u_char)) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "bad lua boolean "
"value size found for key %*s: %lu", key_len,
key_data, (unsigned long) len);
ngx_shmtx_unlock(&ctx->shpool->mutex);
return NGX_ERROR;
}
value->value.b = *data;
break;
default:
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "bad lua value type "
"found for key %*s: %d", key_len, key_data,
(int) value->type);
ngx_shmtx_unlock(&ctx->shpool->mutex);
return NGX_ERROR;
}
ngx_shmtx_unlock(&ctx->shpool->mutex);
return NGX_OK;
}
ngx_shm_zone_t *
ngx_http_lua_find_zone(u_char *name_data, size_t name_len)
{
ngx_str_t *name;
ngx_uint_t i;
ngx_shm_zone_t *zone;
volatile ngx_list_part_t *part;
part = &ngx_cycle->shared_memory.part;
zone = part->elts;
for (i = 0; /* void */ ; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
zone = part->elts;
i = 0;
}
name = &zone[i].shm.name;
dd("name: [%.*s] %d", (int) name->len, name->data, (int) name->len);
dd("name2: [%.*s] %d", (int) name_len, name_data, (int) name_len);
if (name->len == name_len
&& ngx_strncmp(name->data, name_data, name_len) == 0)
{
return zone;
}
}
return NULL;
}
#ifndef NGX_HTTP_LUA_NO_FFI_API
int
ngx_http_lua_ffi_shdict_store(ngx_shm_zone_t *zone, int op, u_char *key,
size_t key_len, int value_type, u_char *str_value_buf,
size_t str_value_len, double num_value, int exptime, int user_flags,
char **errmsg, int *forcible)
{
int i, n;
u_char c, *p;
uint32_t hash;
ngx_int_t rc;
ngx_time_t *tp;
ngx_rbtree_node_t *node;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_http_lua_shdict_node_t *sd;
if (zone == NULL) {
return NGX_ERROR;
}
dd("exptime: %d", exptime);
ctx = zone->data;
*forcible = 0;
hash = ngx_crc32_short(key, key_len);
switch (value_type) {
case LUA_TSTRING:
/* do nothing */
break;
case LUA_TNUMBER:
dd("num value: %lf", num_value);
str_value_buf = (u_char *) &num_value;
str_value_len = sizeof(double);
break;
case LUA_TBOOLEAN:
c = num_value ? 1 : 0;
str_value_buf = &c;
str_value_len = sizeof(u_char);
break;
case LUA_TNIL:
if (op & (NGX_HTTP_LUA_SHDICT_ADD|NGX_HTTP_LUA_SHDICT_REPLACE)) {
*errmsg = "attempt to add or replace nil values";
return NGX_ERROR;
}
str_value_buf = NULL;
str_value_len = 0;
break;
default:
*errmsg = "unsupported value type";
return NGX_ERROR;
}
ngx_shmtx_lock(&ctx->shpool->mutex);
#if 1
ngx_http_lua_shdict_expire(ctx, 1);
#endif
rc = ngx_http_lua_shdict_lookup(zone, hash, key, key_len, &sd);
dd("lookup returns %d", (int) rc);
if (op & NGX_HTTP_LUA_SHDICT_REPLACE) {
if (rc == NGX_DECLINED || rc == NGX_DONE) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
*errmsg = "not found";
return NGX_DECLINED;
}
/* rc == NGX_OK */
goto replace;
}
if (op & NGX_HTTP_LUA_SHDICT_ADD) {
if (rc == NGX_OK) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
*errmsg = "exists";
return NGX_DECLINED;
}
if (rc == NGX_DONE) {
/* exists but expired */
dd("go to replace");
goto replace;
}
/* rc == NGX_DECLINED */
dd("go to insert");
goto insert;
}
if (rc == NGX_OK || rc == NGX_DONE) {
if (value_type == LUA_TNIL) {
goto remove;
}
replace:
if (str_value_buf && str_value_len == (size_t) sd->value_len) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"lua shared dict set: found old entry and value "
"size matched, reusing it");
ngx_queue_remove(&sd->queue);
ngx_queue_insert_head(&ctx->sh->queue, &sd->queue);
sd->key_len = (u_short) key_len;
if (exptime > 0) {
tp = ngx_timeofday();
sd->expires = (uint64_t) tp->sec * 1000 + tp->msec
+ (uint64_t) exptime;
} else {
sd->expires = 0;
}
sd->user_flags = user_flags;
sd->value_len = (uint32_t) str_value_len;
dd("setting value type to %d", value_type);
sd->value_type = (uint8_t) value_type;
p = ngx_copy(sd->data, key, key_len);
ngx_memcpy(p, str_value_buf, str_value_len);
ngx_shmtx_unlock(&ctx->shpool->mutex);
return NGX_OK;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"lua shared dict set: found old entry bug value size "
"NOT matched, removing it first");
remove:
ngx_queue_remove(&sd->queue);
node = (ngx_rbtree_node_t *)
((u_char *) sd - offsetof(ngx_rbtree_node_t, color));
ngx_rbtree_delete(&ctx->sh->rbtree, node);
ngx_slab_free_locked(ctx->shpool, node);
}
insert:
/* rc == NGX_DECLINED or value size unmatch */
if (str_value_buf == NULL) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
return NGX_OK;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"lua shared dict set: creating a new entry");
n = offsetof(ngx_rbtree_node_t, color)
+ offsetof(ngx_http_lua_shdict_node_t, data)
+ key_len
+ str_value_len;
node = ngx_slab_alloc_locked(ctx->shpool, n);
if (node == NULL) {
if (op & NGX_HTTP_LUA_SHDICT_SAFE_STORE) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
*errmsg = "no memory";
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"lua shared dict set: overriding non-expired items "
"due to memory shortage for entry \"%*s\"", key_len,
key);
for (i = 0; i < 30; i++) {
if (ngx_http_lua_shdict_expire(ctx, 0) == 0) {
break;
}
*forcible = 1;
node = ngx_slab_alloc_locked(ctx->shpool, n);
if (node != NULL) {
goto allocated;
}
}
ngx_shmtx_unlock(&ctx->shpool->mutex);
*errmsg = "no memory";
return NGX_ERROR;
}
allocated:
sd = (ngx_http_lua_shdict_node_t *) &node->color;
node->key = hash;
sd->key_len = (u_short) key_len;
if (exptime > 0) {
tp = ngx_timeofday();
sd->expires = (uint64_t) tp->sec * 1000 + tp->msec
+ (uint64_t) exptime;
} else {
sd->expires = 0;
}
sd->user_flags = user_flags;
sd->value_len = (uint32_t) str_value_len;
dd("setting value type to %d", value_type);
sd->value_type = (uint8_t) value_type;
p = ngx_copy(sd->data, key, key_len);
ngx_memcpy(p, str_value_buf, str_value_len);
ngx_rbtree_insert(&ctx->sh->rbtree, node);
ngx_queue_insert_head(&ctx->sh->queue, &sd->queue);
ngx_shmtx_unlock(&ctx->shpool->mutex);
return NGX_OK;
}
int
ngx_http_lua_ffi_shdict_get(ngx_shm_zone_t *zone, u_char *key,
size_t key_len, int *value_type, u_char **str_value_buf,
size_t *str_value_len, double *num_value, int *user_flags,
int get_stale, int *is_stale)
{
ngx_str_t name;
uint32_t hash;
ngx_int_t rc;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_http_lua_shdict_node_t *sd;
ngx_str_t value;
if (zone == NULL) {
return NGX_ERROR;
}
ctx = zone->data;
name = ctx->name;
hash = ngx_crc32_short(key, key_len);
#if (NGX_DEBUG)
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
"fetching key \"%*s\" in shared dict \"%V\"", key_len,
key, &name);
#endif /* NGX_DEBUG */
ngx_shmtx_lock(&ctx->shpool->mutex);
#if 1
if (!get_stale) {
ngx_http_lua_shdict_expire(ctx, 1);
}
#endif
rc = ngx_http_lua_shdict_lookup(zone, hash, key, key_len, &sd);
dd("shdict lookup returns %d", (int) rc);
if (rc == NGX_DECLINED || (rc == NGX_DONE && !get_stale)) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
*value_type = LUA_TNIL;
return NGX_OK;
}
/* rc == NGX_OK || (rc == NGX_DONE && get_stale) */
*value_type = sd->value_type;
dd("data: %p", sd->data);
dd("key len: %d", (int) sd->key_len);
value.data = sd->data + sd->key_len;
value.len = (size_t) sd->value_len;
if (*str_value_len < (size_t) value.len) {
if (*value_type != LUA_TSTRING) {
return NGX_ERROR;
}
*str_value_buf = malloc(value.len);
if (*str_value_buf == NULL) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
return NGX_ERROR;
}
}
*str_value_len = value.len;
switch (*value_type) {
case LUA_TSTRING:
ngx_memcpy(*str_value_buf, value.data, value.len);
break;
case LUA_TNUMBER:
if (value.len != sizeof(double)) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"bad lua number value size found for key %*s "
"in shared_dict %V: %z", key_len, key,
&name, value.len);
return NGX_ERROR;
}
*num_value = *(double *) value.data;
break;
case LUA_TBOOLEAN:
if (value.len != sizeof(u_char)) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"bad lua boolean value size found for key %*s "
"in shared_dict %V: %z", key_len, key, &name,
value.len);
return NGX_ERROR;
}
ngx_memcpy(*str_value_buf, value.data, value.len);
break;
default:
ngx_shmtx_unlock(&ctx->shpool->mutex);
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"bad value type found for key %*s in "
"shared_dict %V: %d", key_len, key, &name,
*value_type);
}
*user_flags = sd->user_flags;
dd("user flags: %d", *user_flags);
ngx_shmtx_unlock(&ctx->shpool->mutex);
if (get_stale) {
/* always return value, flags, stale */
*is_stale = (rc == NGX_DONE);
return NGX_OK;
}
return NGX_OK;
}
int
ngx_http_lua_ffi_shdict_incr(ngx_shm_zone_t *zone, u_char *key,
size_t key_len, double *value, char **err)
{
uint32_t hash;
ngx_int_t rc;
ngx_http_lua_shdict_ctx_t *ctx;
ngx_http_lua_shdict_node_t *sd;
double num;
u_char *p;
ctx = zone->data;
hash = ngx_crc32_short(key, key_len);
dd("looking up key %.*s in shared dict %.*s", (int) key_len, key,
(int) ctx->name.len, ctx->name.data);
ngx_shmtx_lock(&ctx->shpool->mutex);
#if 1
ngx_http_lua_shdict_expire(ctx, 1);
#endif
rc = ngx_http_lua_shdict_lookup(zone, hash, key, key_len, &sd);
dd("shdict lookup returned %d", (int) rc);
if (rc == NGX_DECLINED || rc == NGX_DONE) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
*err = "not found";
return NGX_ERROR;
}
/* rc == NGX_OK */
if (sd->value_type != LUA_TNUMBER || sd->value_len != sizeof(double)) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
*err = "not a number";
return NGX_ERROR;
}
ngx_queue_remove(&sd->queue);
ngx_queue_insert_head(&ctx->sh->queue, &sd->queue);
dd("setting value type to %d", (int) sd->value_type);
p = sd->data + key_len;
num = *(double *) p;
num += *value;
ngx_memcpy(p, (double *) &num, sizeof(double));
ngx_shmtx_unlock(&ctx->shpool->mutex);
*value = num;
return NGX_OK;
}
#endif /* NGX_HTTP_LUA_NO_FFI_API */
/* vi:set ft=c ts=4 sw=4 et fdm=marker: */