From a1df0686afd552a73b7e481d477e1b678247758e Mon Sep 17 00:00:00 2001 From: splitice Date: Sat, 5 Apr 2014 21:13:01 +1100 Subject: [PATCH 01/16] ngx.shared.DICT.incr optional expire argument --- src/ngx_http_lua_shdict.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index 13fdbbee23..c3f3f9bc23 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1132,17 +1132,26 @@ ngx_http_lua_shdict_incr(lua_State *L) u_char *p; ngx_shm_zone_t *zone; double value; + lua_Number exptime = -1; + ngx_time_t *tp; n = lua_gettop(L); - if (n != 3) { - return luaL_error(L, "expecting 3 arguments, but only seen %d", n); + if (n < 3) { + return luaL_error(L, "expecting atleast 3 arguments, but only seen %d", n); } if (lua_type(L, 1) != LUA_TLIGHTUSERDATA) { return luaL_error(L, "bad \"zone\" argument"); } + if (n >= 4) { + exptime = luaL_checknumber(L, 4); + if (exptime < 0) { + exptime = -1; + } + } + zone = lua_touserdata(L, 1); if (zone == NULL) { return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer"); @@ -1205,6 +1214,19 @@ ngx_http_lua_shdict_incr(lua_State *L) return 2; } + if (exptime > 0) { + dd("setting expire time to %d", exptime); + + tp = ngx_timeofday(); + sd->expires = (uint64_t)tp->sec * 1000 + tp->msec + + (uint64_t)(exptime * 1000); + + } + else if (exptime == 0) { + dd("setting key to never expire"); + sd->expires = 0; + } + ngx_queue_remove(&sd->queue); ngx_queue_insert_head(&ctx->sh->queue, &sd->queue); From a315d790452a07a6af731bb0b40eb39e3e9ae077 Mon Sep 17 00:00:00 2001 From: splitice Date: Sat, 5 Apr 2014 21:16:30 +1100 Subject: [PATCH 02/16] tabs fixed, replaced with spaces --- src/ngx_http_lua_shdict.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index c3f3f9bc23..85517bb07b 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1132,8 +1132,8 @@ ngx_http_lua_shdict_incr(lua_State *L) u_char *p; ngx_shm_zone_t *zone; double value; - lua_Number exptime = -1; - ngx_time_t *tp; + lua_Number exptime = -1; + ngx_time_t *tp; n = lua_gettop(L); @@ -1145,12 +1145,12 @@ ngx_http_lua_shdict_incr(lua_State *L) return luaL_error(L, "bad \"zone\" argument"); } - if (n >= 4) { - exptime = luaL_checknumber(L, 4); - if (exptime < 0) { - exptime = -1; - } - } + if (n >= 4) { + exptime = luaL_checknumber(L, 4); + if (exptime < 0) { + exptime = -1; + } + } zone = lua_touserdata(L, 1); if (zone == NULL) { @@ -1214,18 +1214,18 @@ ngx_http_lua_shdict_incr(lua_State *L) return 2; } - if (exptime > 0) { - dd("setting expire time to %d", exptime); + if (exptime > 0) { + dd("setting expire time to %d", exptime); - tp = ngx_timeofday(); - sd->expires = (uint64_t)tp->sec * 1000 + tp->msec - + (uint64_t)(exptime * 1000); + tp = ngx_timeofday(); + sd->expires = (uint64_t)tp->sec * 1000 + tp->msec + + (uint64_t)(exptime * 1000); - } - else if (exptime == 0) { - dd("setting key to never expire"); - sd->expires = 0; - } + } + else if (exptime == 0) { + dd("setting key to never expire"); + sd->expires = 0; + } ngx_queue_remove(&sd->queue); ngx_queue_insert_head(&ctx->sh->queue, &sd->queue); From c11df99c3c9d26d74e6e8340bd1a6187ee5ada44 Mon Sep 17 00:00:00 2001 From: splitice Date: Thu, 2 Apr 2015 01:56:48 +1100 Subject: [PATCH 03/16] Update code to meet nginx coding standards --- src/ngx_http_lua_shdict.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index 7ea34d6e24..94431194df 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1141,12 +1141,18 @@ ngx_http_lua_shdict_incr(lua_State *L) ngx_shm_zone_t *zone; double value; lua_Number exptime = -1; + /* indicates whether to set the entries + * exptime property, -1 meaning do not set */ ngx_time_t *tp; n = lua_gettop(L); if (n < 3) { - return luaL_error(L, "expecting atleast 3 arguments, but only seen %d", n); + return luaL_error(L, "expecting at least 3 arguments, but only seen %d", n); + } + + if (n > 4) { + return luaL_error(L, "expecting no more than 4 arguments, but %d seen", n); } if (lua_type(L, 1) != LUA_TLIGHTUSERDATA) { @@ -1229,8 +1235,7 @@ ngx_http_lua_shdict_incr(lua_State *L) sd->expires = (uint64_t)tp->sec * 1000 + tp->msec + (uint64_t)(exptime * 1000); - } - else if (exptime == 0) { + } else if (exptime == 0) { dd("setting key to never expire"); sd->expires = 0; } From b4c12d6c79192ad8a516e30f5663209e73f06246 Mon Sep 17 00:00:00 2001 From: splitice Date: Thu, 2 Apr 2015 01:58:25 +1100 Subject: [PATCH 04/16] Documentation updated to include incr's exptime parameter --- doc/HttpLuaModule.wiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/HttpLuaModule.wiki b/doc/HttpLuaModule.wiki index b08133f34f..2cbf679b3d 100644 --- a/doc/HttpLuaModule.wiki +++ b/doc/HttpLuaModule.wiki @@ -4538,7 +4538,7 @@ This feature was first introduced in the v0.3.1rc22 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.incr == -'''syntax:''' ''newval, err = ngx.shared.DICT:incr(key, value)'' +'''syntax:''' ''newval, err = ngx.shared.DICT:incr(key, value, exptime?)'' '''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*'' From 762668804be304f9259330907cbd5e88faeaf587 Mon Sep 17 00:00:00 2001 From: splitice Date: Thu, 2 Apr 2015 13:54:33 +1100 Subject: [PATCH 05/16] Fixed stray tab --- src/ngx_http_lua_shdict.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index 94431194df..45dd457578 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1150,8 +1150,8 @@ ngx_http_lua_shdict_incr(lua_State *L) if (n < 3) { return luaL_error(L, "expecting at least 3 arguments, but only seen %d", n); } - - if (n > 4) { + + if (n > 4) { return luaL_error(L, "expecting no more than 4 arguments, but %d seen", n); } From 3caef80aaaa01598a25fadaf2933edacaa456b27 Mon Sep 17 00:00:00 2001 From: splitice Date: Thu, 2 Apr 2015 14:02:55 +1100 Subject: [PATCH 06/16] nil handling --- src/ngx_http_lua_shdict.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index 45dd457578..af990fad34 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1142,7 +1142,7 @@ ngx_http_lua_shdict_incr(lua_State *L) double value; lua_Number exptime = -1; /* indicates whether to set the entries - * exptime property, -1 meaning do not set */ + * exptime property, <0 meaning do not set */ ngx_time_t *tp; n = lua_gettop(L); @@ -1160,9 +1160,11 @@ ngx_http_lua_shdict_incr(lua_State *L) } if (n >= 4) { - exptime = luaL_checknumber(L, 4); - if (exptime < 0) { - exptime = -1; + if (!lua_isnil(L, 4)) { + exptime = luaL_checknumber(L, 4); + if (exptime < 0) { + return luaL_error(L, "bad \"exptime\" argument"); + } } } From e4f79de6a5e3ed6717f01e1de0750fb66f3e8632 Mon Sep 17 00:00:00 2001 From: splitice Date: Sun, 13 Sep 2015 18:47:58 +1000 Subject: [PATCH 07/16] fix commited conflict --- src/ngx_http_lua_shdict.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index 962d44af71..d732805277 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1191,8 +1191,7 @@ ngx_http_lua_shdict_incr(lua_State *L) if (lua_type(L, 1) != LUA_TTABLE) { return luaL_error(L, "bad \"zone\" argument"); } - -<<<<<<< HEAD + if (n >= 4) { if (!lua_isnil(L, 4)) { exptime = luaL_checknumber(L, 4); @@ -1202,10 +1201,7 @@ ngx_http_lua_shdict_incr(lua_State *L) } } - zone = lua_touserdata(L, 1); -======= zone = ngx_http_lua_shdict_get_zone(L, 1); ->>>>>>> openresty/master if (zone == NULL) { return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer"); } From 0fea525b67b6f21d3ed9f037da1023143a89367a Mon Sep 17 00:00:00 2001 From: splitice Date: Mon, 28 Dec 2015 21:39:44 +1100 Subject: [PATCH 08/16] add exptime argument to ffi method with the same semantics as :incr in lua --- src/ngx_http_lua_shdict.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index eaeaa96d71..63ae4b4917 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1819,7 +1819,7 @@ ngx_http_lua_ffi_shdict_get(ngx_shm_zone_t *zone, u_char *key, int ngx_http_lua_ffi_shdict_incr(ngx_shm_zone_t *zone, u_char *key, - size_t key_len, double *value, char **err) + size_t key_len, double *value, int exptime, char **err) { uint32_t hash; ngx_int_t rc; @@ -1868,6 +1868,18 @@ ngx_http_lua_ffi_shdict_incr(ngx_shm_zone_t *zone, u_char *key, ngx_memcpy(p, (double *) &num, sizeof(double)); + if (exptime > 0) { + dd("setting expire time to %d", exptime); + + tp = ngx_timeofday(); + sd->expires = (uint64_t)tp->sec * 1000 + tp->msec + + (uint64_t)(exptime * 1000); + + } else if (exptime == 0) { + dd("setting key to never expire"); + sd->expires = 0; + } + ngx_shmtx_unlock(&ctx->shpool->mutex); *value = num; From 4e618cf5476c18aa7835a0fa72090629a5140c96 Mon Sep 17 00:00:00 2001 From: splitice Date: Mon, 28 Dec 2015 22:28:36 +1100 Subject: [PATCH 09/16] add tp local variable was undefined --- src/ngx_http_lua_shdict.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index 2c95a64604..350a21e1d9 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1827,6 +1827,7 @@ ngx_http_lua_ffi_shdict_incr(ngx_shm_zone_t *zone, u_char *key, ngx_http_lua_shdict_node_t *sd; double num; u_char *p; + ngx_time_t *tp; ctx = zone->data; hash = ngx_crc32_short(key, key_len); From 81907d75e9ef6fd4df1957d7c75849866e3d42cc Mon Sep 17 00:00:00 2001 From: doujiang24 Date: Wed, 14 Oct 2015 18:35:25 +0800 Subject: [PATCH 10/16] feature: add a new api ngx.shared.DICT.cas --- README.markdown | 56 ++++++ doc/HttpLuaModule.wiki | 51 +++++ src/ngx_http_lua_shdict.c | 171 ++++++++++++++++- t/133-shdict-cas.t | 379 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 653 insertions(+), 4 deletions(-) create mode 100644 t/133-shdict-cas.t diff --git a/README.markdown b/README.markdown index 8a254c89a1..f2298393dc 100644 --- a/README.markdown +++ b/README.markdown @@ -2903,6 +2903,7 @@ Nginx API for Lua * [ngx.shared.DICT.add](#ngxshareddictadd) * [ngx.shared.DICT.safe_add](#ngxshareddictsafe_add) * [ngx.shared.DICT.replace](#ngxshareddictreplace) +* [ngx.shared.DICT.cas](#ngxshareddictcas) * [ngx.shared.DICT.delete](#ngxshareddictdelete) * [ngx.shared.DICT.incr](#ngxshareddictincr) * [ngx.shared.DICT.flush_all](#ngxshareddictflush_all) @@ -5892,6 +5893,7 @@ The resulting object `dict` has the following methods: * [add](#ngxshareddictadd) * [safe_add](#ngxshareddictsafe_add) * [replace](#ngxshareddictreplace) +* [cas](#ngxshareddictcas) * [delete](#ngxshareddictdelete) * [incr](#ngxshareddictincr) * [flush_all](#ngxshareddictflush_all) @@ -6110,6 +6112,60 @@ See also [ngx.shared.DICT](#ngxshareddict). [Back to TOC](#nginx-api-for-lua) +ngx.shared.DICT.cas +------------------- +**syntax:** *success, err, forcible, current_value?, current_flags? = ngx.shared.DICT:cas(key, value, exptime, flags, old_value, old_flags?)* + +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.** + +Just like the [replace](#ngxshareddictreplace) method, but only stores the key-value pair into the dictionary [ngx.shared.DICT](#ngxshareddict) if the `old_value` argument and `old_flags` argument *does* match the value and flags in the dictionary [ngx.shared.DICT](#ngxshareddict) (or one of them). + +The `old_value` argument can be `nil` only when `old_flags` argument is specified, only `flags` will be checked. + +If `old_flags` argument is not specified, only `value` will be checked. + +The optional `old_flags` argument can be `nil`, and it means `0`. + +If they do *not* match, the `success` return value will be `false` and the `err` return value will be `"not matched"`. The `current_value` return value and `current_flags` return value will be the current `value` and current `flags` in the dictionary [ngx.shared.DICT](#ngxshareddict), just like [get](#ngxshareddictget) does. + +And below is an example: + +```lua + + local cats = ngx.shared.cats + cats:set("foo", 1, 1) + + local old_value, old_flags = cats:get("foo") + + while true do + local newvalue = calculate(old_value) -- some logic + local newflags = (old_flags or 0) + 1 + + local success, err, forcibly, current_value, current_flags = cats:cas("foo", newvalue, 0, newflags, old_value, old_flags) + if success then + break + + elseif err == "not matched" then + old_value = current_value + old_flags = current_flags + + elseif err == "not found" then + -- add or some other handle + cats:add("foo", newvalue, 0, newflags) + break + + else + -- "no memory" or some other error + -- just log or some other handle + break + end + end +``` + +See also [ngx.shared.DICT](#ngxshareddict). + +[Back to TOC](#nginx-api-for-lua) + ngx.shared.DICT.delete ---------------------- **syntax:** *ngx.shared.DICT:delete(key)* diff --git a/doc/HttpLuaModule.wiki b/doc/HttpLuaModule.wiki index d43ecc3426..4cfb299f56 100644 --- a/doc/HttpLuaModule.wiki +++ b/doc/HttpLuaModule.wiki @@ -4930,6 +4930,7 @@ The resulting object dict has the following methods: * [[#ngx.shared.DICT.add|add]] * [[#ngx.shared.DICT.safe_add|safe_add]] * [[#ngx.shared.DICT.replace|replace]] +* [[#ngx.shared.DICT.cas|cas]] * [[#ngx.shared.DICT.delete|delete]] * [[#ngx.shared.DICT.incr|incr]] * [[#ngx.shared.DICT.flush_all|flush_all]] @@ -5119,6 +5120,56 @@ This feature was first introduced in the v0.3.1rc22 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. +== ngx.shared.DICT.cas == +'''syntax:''' ''success, err, forcible, current_value?, current_flags? = ngx.shared.DICT:cas(key, value, exptime, flags, old_value, old_flags?)'' + +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*'' + +Just like the [[#ngx.shared.DICT.replace|replace]] method, but only stores the key-value pair into the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]] if the old_value argument and old_flags argument ''does'' match the value and flags in the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]] (or one of them). + +The old_value argument can be nil only when old_flags argument is specified, only flags will be checked. + +If old_flags argument is not specified, only value will be checked. + +The optional old_flags argument can be nil, and it means 0. + +If they do ''not'' match, the success return value will be false and the err return value will be "not matched". The current_value return value and current_flags return value will be the current value and current flags in the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]], just like [[#ngx.shared.DICT.get|get]] does. + +And below is an example: + + + local cats = ngx.shared.cats + cats:set("foo", 1, 1) + + local old_value, old_flags = cats:get("foo") + + while true do + local newvalue = calculate(old_value) -- some logic + local newflags = (old_flags or 0) + 1 + + local success, err, forcibly, current_value, current_flags = cats:cas("foo", newvalue, 0, newflags, old_value, old_flags) + if success then + break + + elseif err == "not matched" then + old_value = current_value + old_flags = current_flags + + elseif err == "not found" then + -- add or some other handle + cats:add("foo", newvalue, 0, newflags) + break + + else + -- "no memory" or some other error + -- just log or some other handle + break + end + end + + +See also [[#ngx.shared.DICT|ngx.shared.DICT]]. + == ngx.shared.DICT.delete == '''syntax:''' ''ngx.shared.DICT:delete(key)'' diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index 43f5f9e8e7..b1277d4c16 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -29,6 +29,7 @@ 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_cas(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); @@ -43,6 +44,7 @@ static ngx_inline ngx_shm_zone_t *ngx_http_lua_shdict_get_zone(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 +#define NGX_HTTP_LUA_SHDICT_CHECK 0x0008 enum { @@ -339,6 +341,9 @@ ngx_http_lua_inject_shdict_api(ngx_http_lua_main_conf_t *lmcf, lua_State *L) lua_pushcfunction(L, ngx_http_lua_shdict_replace); lua_setfield(L, -2, "replace"); + lua_pushcfunction(L, ngx_http_lua_shdict_cas); + lua_setfield(L, -2, "cas"); + lua_pushcfunction(L, ngx_http_lua_shdict_incr); lua_setfield(L, -2, "incr"); @@ -834,6 +839,14 @@ ngx_http_lua_shdict_replace(lua_State *L) } +static int +ngx_http_lua_shdict_cas(lua_State *L) +{ + return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_REPLACE + |NGX_HTTP_LUA_SHDICT_CHECK); +} + + static int ngx_http_lua_shdict_set(lua_State *L) { @@ -852,6 +865,7 @@ static int ngx_http_lua_shdict_set_helper(lua_State *L, int flags) { int i, n; + ngx_str_t name; ngx_str_t key; uint32_t hash; ngx_int_t rc; @@ -869,11 +883,28 @@ ngx_http_lua_shdict_set_helper(lua_State *L, int flags) int forcible = 0; /* indicates whether to foricibly override other * valid entries */ - int32_t user_flags = 0; + uint32_t user_flags = 0; + ngx_str_t old_value; + double old_num; + u_char old_c; + int old_value_type; + uint32_t old_user_flags = 0; n = lua_gettop(L); - if (n != 3 && n != 4 && n != 5) { + if (flags & NGX_HTTP_LUA_SHDICT_CHECK) { + if (n != 6 && n != 7) { + return luaL_error(L, "expecting 6 or 7 arguments, " + "but only seen %d", n); + } + + if (n == 6 && lua_type(L, 6) == LUA_TNIL) { + lua_pushnil(L); + lua_pushliteral(L, "old_value is nil and no old_flags"); + return 2; + } + + } else if (n != 3 && n != 4 && n != 5) { return luaL_error(L, "expecting 3, 4 or 5 arguments, " "but only seen %d", n); } @@ -931,7 +962,9 @@ ngx_http_lua_shdict_set_helper(lua_State *L, int flags) break; case LUA_TNIL: - if (flags & (NGX_HTTP_LUA_SHDICT_ADD|NGX_HTTP_LUA_SHDICT_REPLACE)) { + if (flags & (NGX_HTTP_LUA_SHDICT_ADD|NGX_HTTP_LUA_SHDICT_REPLACE) + && ! (flags & NGX_HTTP_LUA_SHDICT_CHECK)) + { lua_pushnil(L); lua_pushliteral(L, "attempt to add or replace nil values"); return 2; @@ -953,7 +986,7 @@ ngx_http_lua_shdict_set_helper(lua_State *L, int flags) } } - if (n == 5) { + if (n >= 5) { user_flags = (uint32_t) luaL_checkinteger(L, 5); } @@ -980,6 +1013,62 @@ ngx_http_lua_shdict_set_helper(lua_State *L, int flags) /* rc == NGX_OK */ + if (flags & NGX_HTTP_LUA_SHDICT_CHECK) { + /* check current value flags */ + old_value_type = lua_type(L, 6); + + if (old_value_type != LUA_TNIL) { + + if (sd->value_type != old_value_type) { + dd("value type check failed"); + goto check_failed; + } + + switch (old_value_type) { + case LUA_TSTRING: + old_value.data = (u_char *) lua_tolstring(L, 6, + &old_value.len); + break; + + case LUA_TNUMBER: + old_value.len = sizeof(double); + old_num = lua_tonumber(L, 6); + old_value.data = (u_char *) &old_num; + break; + + case LUA_TBOOLEAN: + old_value.len = sizeof(u_char); + old_c = lua_toboolean(L, 6) ? 1 : 0; + old_value.data = &old_c; + break; + } + + if (old_value.len != sd->value_len) { + dd("value len check failed"); + goto check_failed; + } + + if (ngx_memn2cmp(old_value.data, sd->data + sd->key_len, + old_value.len, sd->value_len) != 0) + { + dd("value data check failed"); + goto check_failed; + } + } + + if (n == 7) { + + if (lua_type(L, 7) != LUA_TNIL) { + old_user_flags = (uint32_t) luaL_checkinteger(L, 7); + } + + if (sd->user_flags != old_user_flags) { + dd("user_flags check failed"); + goto check_failed; + } + } + } + goto replace; } @@ -1166,6 +1255,80 @@ ngx_http_lua_shdict_set_helper(lua_State *L, int flags) lua_pushnil(L); lua_pushboolean(L, forcible); return 3; + +check_failed: + + name = ctx->name; + + lua_pushboolean(L, 0); + lua_pushliteral(L, "not matched"); + lua_pushboolean(L, forcible); + + 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); + } + + ngx_memcpy(&num, value.data, sizeof(double)); + + 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 (user_flags) { + lua_pushinteger(L, (lua_Integer) user_flags); + return 5; + } + + return 4; } diff --git a/t/133-shdict-cas.t b/t/133-shdict-cas.t new file mode 100644 index 0000000000..ab1d1e599f --- /dev/null +++ b/t/133-shdict-cas.t @@ -0,0 +1,379 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib 'lib'; +use Test::Nginx::Socket::Lua; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +#repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +#no_diff(); +no_long_string(); +#master_on(); +#workers(2); + +run_tests(); + +__DATA__ + +=== TEST 1: old value without flags +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", "old-value") + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + + local success, err, forcible, current_value, current_flags = dogs:cas("foo", "new-value", 0, 0, value, flags) + ngx.say(success, " ", err, " ", forcible, " ", current_value, " ", current_flags) + + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + '; + } +--- request +GET /test +--- response_body +old-value nil +true nil false nil nil +new-value nil +--- no_error_log +[error] + + + +=== TEST 2: old-value with flags +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", "old-value", 0, 1) + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + + local success, err, forcible, current_value, current_flags = dogs:cas("foo", "new-value", 0, 2, value, flags) + ngx.say(success, " ", err, " ", forcible, " ", current_value, " ", current_flags) + + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + '; + } +--- request +GET /test +--- response_body +old-value 1 +true nil false nil nil +new-value 2 +--- no_error_log +[error] + + + +=== TEST 3: only check value +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", "old-value", 0, 1) + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + + local success, err, forcible, current_value, current_flags = dogs:cas("foo", "new-value", 0, 2, value) + ngx.say(success, " ", err, " ", forcible, " ", current_value, " ", current_flags) + + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + '; + } +--- request +GET /test +--- response_body +old-value 1 +true nil false nil nil +new-value 2 +--- no_error_log +[error] + + + +=== TEST 4: only check flags +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", "old-value", 0, 1) + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + + local success, err, forcible, current_value, current_flags = dogs:cas("foo", "new-value", 0, 2, nil, flags) + ngx.say(success, " ", err, " ", forcible, " ", current_value, " ", current_flags) + + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + '; + } +--- request +GET /test +--- response_body +old-value 1 +true nil false nil nil +new-value 2 +--- no_error_log +[error] + + + +=== TEST 5: only check flags(flags is nil) +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", "old-value", 0, 0) + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + + local success, err, forcible, current_value, current_flags = dogs:cas("foo", "new-value", 0, 2, nil, flags) + ngx.say(success, " ", err, " ", forcible, " ", current_value, " ", current_flags) + + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + '; + } +--- request +GET /test +--- response_body +old-value nil +true nil false nil nil +new-value 2 +--- no_error_log +[error] + + + +=== TEST 6: check failed (value) +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", "old-value", 0, 1) + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + + local success, err, forcible, current_value, current_flags = dogs:cas("foo", "new-value", 0, 2, "oldvalue", flags) + ngx.say(success, " ", err, " ", forcible, " ", current_value, " ", current_flags) + + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + '; + } +--- request +GET /test +--- response_body +old-value 1 +false not matched false old-value 1 +old-value 1 +--- no_error_log +[error] + + + +=== TEST 7: check failed (flags) +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", "old-value", 0, 1) + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + + local success, err, forcible, current_value, current_flags = dogs:cas("foo", "new-value", 0, 2, value, nil) + ngx.say(success, " ", err, " ", forcible, " ", current_value, " ", current_flags) + + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + '; + } +--- request +GET /test +--- response_body +old-value 1 +false not matched false old-value 1 +old-value 1 +--- no_error_log +[error] + + + +=== TEST 8: sometimes check failed +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", 0) + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + + local i = 1 + + while i <= 6 do + if i % 2 == 1 then + dogs:incr("foo", 1) + end + + local success, err, forcible, current_value, current_flags = dogs:cas("foo", value + 1, 0, 0, value, flags) + if success then + ngx.say("success at time: ", i) + else + value = current_value + flags = current_flags + ngx.say(success, " ", err, " ", forcible, " ", value, " ", flags) + end + + i = i + 1 + end + + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + '; + } +--- request +GET /test +--- response_body +0 nil +false not matched false 1 nil +success at time: 2 +false not matched false 3 nil +success at time: 4 +false not matched false 5 nil +success at time: 6 +6 nil +--- no_error_log +[error] + + + +=== TEST 9: sometimes check failed(set nil: delete) +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", 0) + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + + local i = 1 + + while i <= 6 do + local success, err, forcible, current_value, current_flags = dogs:cas("foo", nil, 0, 0, value, flags) + if success then + ngx.say("success at time: ", i) + + elseif err == "not found" then + dogs:add("foo", 0) + ngx.say(success, " ", err, " ", forcible, " ", current_value, " ", current_flags) + + else + value = current_value + flags = current_flags + ngx.say(success, " ", err, " ", forcible, " ", value, " ", flags) + end + + i = i + 1 + end + + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + '; + } +--- request +GET /test +--- response_body +0 nil +success at time: 1 +false not found false nil nil +success at time: 3 +false not found false nil nil +success at time: 5 +false not found false nil nil +0 nil +--- no_error_log +[error] + + + +=== TEST 10: check failed (value type) +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", string.char(0)) + local value, flags = dogs:get("foo") + + local success, err, forcible, current_value, current_flags = dogs:cas("foo", "new-value", 0, 2, false, flags) + ngx.say(success, " ", err, " ", forcible) + + local value, flags = dogs:get("foo") + ngx.say("value == char(0): ", value == string.char(0)) + '; + } +--- request +GET /test +--- response_body +false not matched false +value == char(0): true +--- no_error_log +[error] + + + +=== TEST 11: value is boolean +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua ' + local dogs = ngx.shared.dogs + dogs:set("foo", true, 0, 1) + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + + local success, err, forcible, current_value, current_flags = dogs:cas("foo", false, 0, 2, value, flags) + ngx.say(success, " ", err, " ", forcible, " ", current_value, " ", current_flags) + + local value, flags = dogs:get("foo") + ngx.say(value, " ", flags) + '; + } +--- request +GET /test +--- response_body +true 1 +true nil false nil nil +false 2 +--- no_error_log +[error] + From 632f95f1bed4235362af5bd771c51a4d5a5ced97 Mon Sep 17 00:00:00 2001 From: doujiang24 Date: Wed, 3 Feb 2016 15:33:52 +0800 Subject: [PATCH 11/16] doc: fix confusing description --- README.markdown | 12 +++++++----- doc/HttpLuaModule.wiki | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/README.markdown b/README.markdown index f2298393dc..536ca39240 100644 --- a/README.markdown +++ b/README.markdown @@ -6116,11 +6116,11 @@ ngx.shared.DICT.cas ------------------- **syntax:** *success, err, forcible, current_value?, current_flags? = ngx.shared.DICT:cas(key, value, exptime, flags, old_value, old_flags?)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, certificate_by_lua** -Just like the [replace](#ngxshareddictreplace) method, but only stores the key-value pair into the dictionary [ngx.shared.DICT](#ngxshareddict) if the `old_value` argument and `old_flags` argument *does* match the value and flags in the dictionary [ngx.shared.DICT](#ngxshareddict) (or one of them). +Just like the [replace](#ngxshareddictreplace) method, but only stores the key-value pair into the dictionary [ngx.shared.DICT](#ngxshareddict) if and only if the `old_value` argument and `old_flags` argument *do* match the value and flags in the dictionary [ngx.shared.DICT](#ngxshareddict). -The `old_value` argument can be `nil` only when `old_flags` argument is specified, only `flags` will be checked. +The `old_value` argument can be `nil` only when `old_flags` argument is specified, in which case only `flags` will be checked. If `old_flags` argument is not specified, only `value` will be checked. @@ -6128,7 +6128,7 @@ The optional `old_flags` argument can be `nil`, and it means `0`. If they do *not* match, the `success` return value will be `false` and the `err` return value will be `"not matched"`. The `current_value` return value and `current_flags` return value will be the current `value` and current `flags` in the dictionary [ngx.shared.DICT](#ngxshareddict), just like [get](#ngxshareddictget) does. -And below is an example: +This function is often used to avoid race condition between [get](#ngxshareddictget) and [set](#ngxshareddictset) across multipe nginx worker processes, and below is an example: ```lua @@ -6141,7 +6141,9 @@ And below is an example: local newvalue = calculate(old_value) -- some logic local newflags = (old_flags or 0) + 1 - local success, err, forcibly, current_value, current_flags = cats:cas("foo", newvalue, 0, newflags, old_value, old_flags) + local success, err, forcibly, current_value, current_flags + = cats:cas("foo", newvalue, 0, newflags, old_value, old_flags) + if success then break diff --git a/doc/HttpLuaModule.wiki b/doc/HttpLuaModule.wiki index 4cfb299f56..cad8f94e9e 100644 --- a/doc/HttpLuaModule.wiki +++ b/doc/HttpLuaModule.wiki @@ -5123,11 +5123,11 @@ See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.cas == '''syntax:''' ''success, err, forcible, current_value?, current_flags? = ngx.shared.DICT:cas(key, value, exptime, flags, old_value, old_flags?)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, certificate_by_lua*'' -Just like the [[#ngx.shared.DICT.replace|replace]] method, but only stores the key-value pair into the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]] if the old_value argument and old_flags argument ''does'' match the value and flags in the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]] (or one of them). +Just like the [[#ngx.shared.DICT.replace|replace]] method, but only stores the key-value pair into the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]] if and only if the old_value argument and old_flags argument ''do'' match the value and flags in the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]]. -The old_value argument can be nil only when old_flags argument is specified, only flags will be checked. +The old_value argument can be nil only when old_flags argument is specified, in which case only flags will be checked. If old_flags argument is not specified, only value will be checked. @@ -5135,7 +5135,7 @@ The optional old_flags argument can be nil, and it mea If they do ''not'' match, the success return value will be false and the err return value will be "not matched". The current_value return value and current_flags return value will be the current value and current flags in the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]], just like [[#ngx.shared.DICT.get|get]] does. -And below is an example: +This function is often used to avoid race condition between [[#ngx.shared.DICT.get|get]] and [[#ngx.shared.DICT.set|set]] across multipe nginx worker processes, and below is an example: local cats = ngx.shared.cats @@ -5147,7 +5147,9 @@ And below is an example: local newvalue = calculate(old_value) -- some logic local newflags = (old_flags or 0) + 1 - local success, err, forcibly, current_value, current_flags = cats:cas("foo", newvalue, 0, newflags, old_value, old_flags) + local success, err, forcibly, current_value, current_flags + = cats:cas("foo", newvalue, 0, newflags, old_value, old_flags) + if success then break From efd0717033459caf6ed59efb5458ec9c634da06f Mon Sep 17 00:00:00 2001 From: splitice Date: Thu, 12 May 2016 19:45:52 +1000 Subject: [PATCH 12/16] add user_flags to incr return value --- src/ngx_http_lua_shdict.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index dc68d9562f..a991892f5c 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1459,10 +1459,19 @@ ngx_http_lua_shdict_incr(lua_State *L) ngx_memcpy(p, (double *) &num, sizeof(double)); + user_flags = sd->user_flags; + ngx_shmtx_unlock(&ctx->shpool->mutex); lua_pushnumber(L, num); lua_pushnil(L); + + if (user_flags) { + lua_pushinteger(L, (lua_Integer) user_flags); + } else { + lua_pushnil(L); + } + return 2; } From bae63adfcad97bf8f7e4dc70e35f198a57e21369 Mon Sep 17 00:00:00 2001 From: splitice Date: Mon, 23 May 2016 23:43:13 +1000 Subject: [PATCH 13/16] user flags not defined --- src/ngx_http_lua_shdict.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index a991892f5c..7a99334071 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1349,6 +1349,7 @@ ngx_http_lua_shdict_incr(lua_State *L) /* indicates whether to set the entries * exptime property, <0 meaning do not set */ ngx_time_t *tp; + uint32_t user_flags = 0; n = lua_gettop(L); From ebd0d576a81c42304eedcad942d03a930f109899 Mon Sep 17 00:00:00 2001 From: splitice Date: Tue, 24 May 2016 07:52:33 +1000 Subject: [PATCH 14/16] add see other to ngx.redirect --- src/ngx_http_lua_control.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ngx_http_lua_control.c b/src/ngx_http_lua_control.c index 4c5f65e13d..a884aa45a6 100644 --- a/src/ngx_http_lua_control.c +++ b/src/ngx_http_lua_control.c @@ -212,11 +212,11 @@ ngx_http_lua_ngx_redirect(lua_State *L) if (rc != NGX_HTTP_MOVED_TEMPORARILY && rc != NGX_HTTP_MOVED_PERMANENTLY - && rc != NGX_HTTP_TEMPORARY_REDIRECT) + && rc != NGX_HTTP_TEMPORARY_REDIRECT { return luaL_error(L, "only ngx.HTTP_MOVED_TEMPORARILY, " "ngx.HTTP_MOVED_PERMANENTLY, and " - "ngx.HTTP_TEMPORARY_REDIRECT are allowed"); + "ngx.HTTP_TEMPORARY_REDIRECT, and" } } else { From 4cc4a08582e5c1ee7cab0dce42236798c6a1a9ce Mon Sep 17 00:00:00 2001 From: splitice Date: Tue, 24 May 2016 07:53:05 +1000 Subject: [PATCH 15/16] fix previous commit --- src/ngx_http_lua_control.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ngx_http_lua_control.c b/src/ngx_http_lua_control.c index a884aa45a6..2b094ae337 100644 --- a/src/ngx_http_lua_control.c +++ b/src/ngx_http_lua_control.c @@ -213,10 +213,12 @@ ngx_http_lua_ngx_redirect(lua_State *L) if (rc != NGX_HTTP_MOVED_TEMPORARILY && rc != NGX_HTTP_MOVED_PERMANENTLY && rc != NGX_HTTP_TEMPORARY_REDIRECT + && rc != NGX_HTTP_SEE_OTHER) { return luaL_error(L, "only ngx.HTTP_MOVED_TEMPORARILY, " "ngx.HTTP_MOVED_PERMANENTLY, and " "ngx.HTTP_TEMPORARY_REDIRECT, and" + "ngx.HTTP_SEE_OTHER are allowed"); } } else { From e3b2c0999d6dbe53c9816879ed7c363b21fa2f26 Mon Sep 17 00:00:00 2001 From: splitice Date: Fri, 16 Dec 2016 10:59:27 +1100 Subject: [PATCH 16/16] fix incr init bug --- src/ngx_http_lua_shdict.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index 7cf70ce6f3..a5264c2ac4 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -1536,7 +1536,7 @@ ngx_http_lua_shdict_incr(lua_State *L) if (rc == NGX_DECLINED || rc == NGX_DONE) { - if (n == 3) { + if (n == 4) { ngx_shmtx_unlock(&ctx->shpool->mutex); lua_pushnil(L);