diff --git a/README.markdown b/README.markdown
index 8a254c89a1..536ca39240 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,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 d43ecc3426..cad8f94e9e 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,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)''
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]
+