diff --git a/README.markdown b/README.markdown index 99b5275329..1a02eb5357 100644 --- a/README.markdown +++ b/README.markdown @@ -3055,6 +3055,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.lpush](#ngxshareddictlpush) @@ -6054,6 +6055,7 @@ The resulting object `dict` has the following methods: * [add](#ngxshareddictadd) * [safe_add](#ngxshareddictsafe_add) * [replace](#ngxshareddictreplace) +* [cas](#ngxshareddictcas) * [delete](#ngxshareddictdelete) * [incr](#ngxshareddictincr) * [lpush](#ngxshareddictlpush) @@ -6277,6 +6279,62 @@ 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.*, 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 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, in which case 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. + +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 + + 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 028a98d96f..81fbc9bce4 100644 --- a/doc/HttpLuaModule.wiki +++ b/doc/HttpLuaModule.wiki @@ -5067,6 +5067,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.lpush|lpush]] @@ -5261,6 +5262,58 @@ 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.*, 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 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, in which case 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. + +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 + 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)'' @@ -5275,7 +5328,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, forcible? = ngx.shared.DICT:incr(key, value, init?)'' +'''syntax:''' ''newval, err, forcible? = ngx.shared.DICT:incr(key, value, init?, 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.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' diff --git a/src/ngx_http_lua_control.c b/src/ngx_http_lua_control.c index 9e78bece05..0662032c46 100644 --- a/src/ngx_http_lua_control.c +++ b/src/ngx_http_lua_control.c @@ -212,11 +212,13 @@ 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 + && rc != NGX_HTTP_SEE_OTHER) { 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" + "ngx.HTTP_SEE_OTHER are allowed"); } } else { diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index ac7f6f1f96..a5264c2ac4 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); @@ -50,6 +51,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 #define NGX_HTTP_LUA_SHDICT_LEFT 0x0001 @@ -382,6 +384,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"); @@ -917,6 +922,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) { @@ -935,6 +948,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; @@ -950,14 +964,31 @@ ngx_http_lua_shdict_set_helper(lua_State *L, int flags) ngx_time_t *tp; ngx_shm_zone_t *zone; int forcible = 0; + ngx_queue_t *queue, *q; /* indicates whether to foricibly override other * valid entries */ - int32_t user_flags = 0; - ngx_queue_t *queue, *q; + 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); } @@ -1016,7 +1047,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; @@ -1038,7 +1071,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); } @@ -1065,6 +1098,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; } @@ -1269,6 +1358,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; } @@ -1286,21 +1449,46 @@ ngx_http_lua_shdict_incr(lua_State *L) u_char *p; ngx_shm_zone_t *zone; double value; + lua_Number exptime = -1; ngx_rbtree_node_t *node; /* indicates whether to foricibly override other * valid entries */ int forcible = 0; ngx_queue_t *queue, *q; + ngx_time_t *tp; + uint32_t user_flags = 0; n = lua_gettop(L); - if (n != 3 && n != 4) { - return luaL_error(L, "expecting 3 or 4 arguments, but only seen %d", n); + if (n < 3) { + return luaL_error(L, "expecting at least 3 arguments, but only seen %d", n); + } + + if (n > 5) { + return luaL_error(L, "expecting no more than 5 arguments, but %d seen", n); } if (lua_type(L, 1) != LUA_TTABLE) { return luaL_error(L, "bad \"zone\" argument"); } + + if (n >= 4) { + if (!lua_isnil(L, 4)) { + exptime = luaL_checknumber(L, 4); + if (exptime < 0) { + return luaL_error(L, "bad \"exptime\" argument"); + } + } + + if (n >= 5) { + if (!lua_isnil(L, 5)) { + exptime = luaL_checknumber(L, 5); + if (exptime < 0) { + return luaL_error(L, "bad \"init\" argument"); + } + } + } + } zone = ngx_http_lua_shdict_get_zone(L, 1); if (zone == NULL) { @@ -1333,10 +1521,6 @@ ngx_http_lua_shdict_incr(lua_State *L) value = luaL_checknumber(L, 3); - if (n == 4) { - init = luaL_checknumber(L, 4); - } - dd("looking up key %.*s in shared dict %.*s", (int) key.len, key.data, (int) ctx->name.len, ctx->name.data); @@ -1352,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); @@ -1399,6 +1583,18 @@ 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->lru_queue, &sd->queue); @@ -1411,10 +1607,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; remove: @@ -2646,7 +2851,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, int has_init, double init, + size_t key_len, double *value, int exptime, char **err, int has_init, double init, int *forcible) { int i, n; @@ -2657,6 +2862,7 @@ ngx_http_lua_ffi_shdict_incr(ngx_shm_zone_t *zone, u_char *key, double num; ngx_rbtree_node_t *node; u_char *p; + ngx_time_t *tp; ngx_queue_t *queue, *q; if (zone == NULL) { @@ -2736,6 +2942,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; 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] +