From 81907d75e9ef6fd4df1957d7c75849866e3d42cc Mon Sep 17 00:00:00 2001 From: doujiang24 Date: Wed, 14 Oct 2015 18:35:25 +0800 Subject: [PATCH 1/2] 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 2/2] 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