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]
+