diff --git a/.travis.yml b/.travis.yml
index daae0704f7..ff4b40bcef 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -88,11 +88,11 @@ install:
- git clone https://github.com/openresty/rds-json-nginx-module.git ../rds-json-nginx-module
- git clone https://github.com/openresty/srcache-nginx-module.git ../srcache-nginx-module
- git clone https://github.com/openresty/redis2-nginx-module.git ../redis2-nginx-module
- - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core
+ - git clone -b atomic_shm https://github.com/webcore-no/lua-resty-core.git ../lua-resty-core
- git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache
- git clone https://github.com/openresty/lua-resty-mysql.git ../lua-resty-mysql
- git clone https://github.com/openresty/lua-resty-string.git ../lua-resty-string
- - git clone https://github.com/openresty/stream-lua-nginx-module.git ../stream-lua-nginx-module
+ - git clone -b atomic_shm https://github.com/webcore-no/stream-lua-nginx-module.git ../stream-lua-nginx-module
- git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git luajit2
before_script:
diff --git a/README.markdown b/README.markdown
index a3a631cb3e..97c84a7865 100644
--- a/README.markdown
+++ b/README.markdown
@@ -3581,10 +3581,12 @@ Nginx API for Lua
* [ngx.shared.DICT](#ngxshareddict)
* [ngx.shared.DICT.get](#ngxshareddictget)
* [ngx.shared.DICT.get_stale](#ngxshareddictget_stale)
+* [ngx.shared.DICT](#ngxshareddict)
* [ngx.shared.DICT.set](#ngxshareddictset)
* [ngx.shared.DICT.safe_set](#ngxshareddictsafe_set)
* [ngx.shared.DICT.add](#ngxshareddictadd)
* [ngx.shared.DICT.safe_add](#ngxshareddictsafe_add)
+* [ngx.shared.DICT.cas](#ngxshareddictcas)
* [ngx.shared.DICT.replace](#ngxshareddictreplace)
* [ngx.shared.DICT.delete](#ngxshareddictdelete)
* [ngx.shared.DICT.incr](#ngxshareddictincr)
@@ -6784,10 +6786,12 @@ The resulting object `dict` has the following methods:
* [get](#ngxshareddictget)
* [get_stale](#ngxshareddictget_stale)
+* [get_if_not_eq](#ngxshareddictget_if_not_eq)
* [set](#ngxshareddictset)
* [safe_set](#ngxshareddictsafe_set)
* [add](#ngxshareddictadd)
* [safe_add](#ngxshareddictsafe_add)
+* [cas](#ngxshareddictcas)
* [replace](#ngxshareddictreplace)
* [delete](#ngxshareddictdelete)
* [incr](#ngxshareddictincr)
@@ -6912,6 +6916,26 @@ See also [ngx.shared.DICT](#ngxshareddict).
[Back to TOC](#nginx-api-for-lua)
+ngx.shared.DICT
+---------------
+**syntax:** *value, flags = ngx.shared.DICT:get_if_not_eq(key, old_value?, old_flags?)*
+
+**context:** *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*, ssl_client_hello_by_lua**
+
+Similar to the [get](#ngxshareddictget) method, but only returns if
+`old_value` or `old_flags` do not match.
+
+If `old_value` or `old_flags` is `nil`
+it will be ignored when comparing.
+
+In case of match, `nil, false` will be returned.
+
+This method was first introduced in the `0.10.21` release.
+
+See also [ngx.shared.DICT](#ngxshareddict).
+
+[Back to TOC](#nginx-api-for-lua)
+
ngx.shared.DICT.set
-------------------
@@ -7012,6 +7036,28 @@ See also [ngx.shared.DICT](#ngxshareddict).
[Back to TOC](#nginx-api-for-lua)
+ngx.shared.DICT.cas
+-------------------
+**syntax:** *success, err, forcible = ngx.shared.DICT:cas(key, old_value?, old_flags?, value?, flags?, exptime?)*
+
+**context:** *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*, ssl_client_hello_by_lua**
+
+Conditionally sets key-value pair in shm.
+
+If `old_value` or `old_flags` is `nil` it will
+be ignored.
+
+If either `value` or `flags` is `nil` it will
+remain unchanged. If both are `nil`, key-value pair will be deleted.
+
+In case of mismatch, `false, false` will be returned.
+
+This method was first introduced in the `0.10.21` release.
+
+See also [ngx.shared.DICT](#ngxshareddict).
+
+[Back to TOC](#nginx-api-for-lua)
+
ngx.shared.DICT.replace
-----------------------
diff --git a/doc/HttpLuaModule.wiki b/doc/HttpLuaModule.wiki
index 0d7a6f73b1..569df82447 100644
--- a/doc/HttpLuaModule.wiki
+++ b/doc/HttpLuaModule.wiki
@@ -5739,10 +5739,12 @@ The resulting object dict
has the following methods:
* [[#ngx.shared.DICT.get|get]]
* [[#ngx.shared.DICT.get_stale|get_stale]]
+* [[#ngx.shared.DICT.get_if_not_eq|get_if_not_eq]]
* [[#ngx.shared.DICT.set|set]]
* [[#ngx.shared.DICT.safe_set|safe_set]]
* [[#ngx.shared.DICT.add|add]]
* [[#ngx.shared.DICT.safe_add|safe_add]]
+* [[#ngx.shared.DICT.cas|cas]]
* [[#ngx.shared.DICT.replace|replace]]
* [[#ngx.shared.DICT.delete|delete]]
* [[#ngx.shared.DICT.incr|incr]]
@@ -5855,6 +5857,23 @@ This method was first introduced in the 0.8.6
release.
See also [[#ngx.shared.DICT|ngx.shared.DICT]].
+== ngx.shared.DICT ==
+'''syntax:''' ''value, flags = ngx.shared.DICT:get_if_not_eq(key, old_value?, old_flags?)''
+
+'''context:''' ''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*, ssl_client_hello_by_lua*''
+
+Similar to the [[#ngx.shared.DICT.get|get]] method, but only returns if
+old_value
or old_flags
do not match.
+
+If old_value
or old_flags
is nil
+it will be ignored when comparing.
+
+In case of match, nil, false
will be returned.
+
+This method was first introduced in the 0.10.21
release.
+
+See also [[#ngx.shared.DICT|ngx.shared.DICT]].
+
== ngx.shared.DICT.set ==
'''syntax:''' ''success, err, forcible = ngx.shared.DICT:set(key, value, exptime?, flags?)''
@@ -5941,6 +5960,25 @@ This feature was first introduced in the v0.7.18
release.
See also [[#ngx.shared.DICT|ngx.shared.DICT]].
+== ngx.shared.DICT.cas ==
+'''syntax:''' ''success, err, forcible = ngx.shared.DICT:cas(key, old_value?, old_flags?, value?, flags?, exptime?)''
+
+'''context:''' ''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*, ssl_client_hello_by_lua*''
+
+Conditionally sets key-value pair in shm.
+
+If old_value
or old_flags
is nil
it will
+be ignored.
+
+If either value
or flags
is nil
it will
+remain unchanged. If both are nil
, key-value pair will be deleted.
+
+In case of mismatch, false, false
will be returned.
+
+This method was first introduced in the 0.10.21
release.
+
+See also [[#ngx.shared.DICT|ngx.shared.DICT]].
+
== ngx.shared.DICT.replace ==
'''syntax:''' ''success, err, forcible = ngx.shared.DICT:replace(key, value, exptime?, flags?)''
diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c
index 26622d29f0..fc04ce54c9 100644
--- a/src/ngx_http_lua_shdict.c
+++ b/src/ngx_http_lua_shdict.c
@@ -305,6 +305,37 @@ ngx_http_lua_shdict_expire(ngx_http_lua_shdict_ctx_t *ctx, ngx_uint_t n)
}
+static inline ngx_rbtree_node_t *
+ngx_http_lua_shdict_force_alloc(ngx_http_lua_shdict_ctx_t *ctx, size_t size,
+ int *forcible)
+{
+ int i;
+ ngx_rbtree_node_t *node;
+
+ node = ngx_slab_alloc_locked(ctx->shpool, size);
+ *forcible = 0;
+
+ if (node) {
+ return node;
+ }
+
+ for (i = 0; i < 30; i++) {
+ if (ngx_http_lua_shdict_expire(ctx, 0) == 0) {
+ break;
+ }
+
+ *forcible = 1;
+
+ node = ngx_slab_alloc_locked(ctx->shpool, size);
+ if (node) {
+ return node;
+ }
+ }
+
+ return NULL;
+}
+
+
void
ngx_http_lua_inject_shdict_api(ngx_http_lua_main_conf_t *lmcf, lua_State *L)
{
@@ -1305,13 +1336,463 @@ ngx_http_lua_ffi_shdict_udata_to_zone(void *zone_udata)
}
+int
+ngx_http_lua_ffi_shdict_cas(ngx_shm_zone_t *zone, u_char *key,
+ size_t key_len, int old_value_type, u_char *old_str_value_buf,
+ size_t old_str_value_len, double old_num_value, int old_user_flags,
+ int value_type, u_char *str_value_buf, size_t str_value_len,
+ double num_value, int user_flags, int set_user_flags,
+ long exptime, int match_flags, int *match, char **errmsg, int *forcible)
+{
+ int n, match_value;
+ u_char c, oc, *p;
+ uint32_t hash;
+ ngx_int_t rc;
+ ngx_time_t *tp;
+ ngx_queue_t *queue, *q;
+ ngx_rbtree_node_t *node;
+ ngx_http_lua_shdict_ctx_t *ctx;
+ ngx_http_lua_shdict_node_t *sd;
+
+ dd("exptime: %ld", exptime);
+
+ ctx = zone->data;
+
+ *match = 0;
+ *forcible = 0;
+ *errmsg = NULL;
+
+ hash = ngx_crc32_short(key, key_len);
+
+ switch (value_type) {
+
+ case SHDICT_TSTRING:
+ /* do nothing */
+ break;
+
+ case SHDICT_TNUMBER:
+ dd("num value: %lf", num_value);
+ str_value_buf = (u_char *) &num_value;
+ str_value_len = sizeof(double);
+ break;
+
+ case SHDICT_TBOOLEAN:
+ c = num_value ? 1 : 0;
+ str_value_buf = &c;
+ str_value_len = sizeof(u_char);
+ break;
+
+ case LUA_TNIL:
+ str_value_buf = NULL;
+ str_value_len = 0;
+ break;
+
+ default:
+ *errmsg = "unsupported value type";
+ return NGX_ERROR;
+ }
+
+ switch (old_value_type) {
+
+ case SHDICT_TSTRING:
+ /* do nothing */
+ match_value = 1;
+ break;
+
+ case SHDICT_TNUMBER:
+ dd("num value: %lf", old_num_value);
+ old_str_value_buf = (u_char *) &old_num_value;
+ old_str_value_len = sizeof(double);
+ match_value = 1;
+ break;
+
+ case SHDICT_TBOOLEAN:
+ oc = old_num_value ? 1 : 0;
+ old_str_value_buf = &oc;
+ old_str_value_len = sizeof(u_char);
+ match_value = 1;
+ break;
+
+ case LUA_TNIL:
+ match_value = 0;
+ break;
+
+ default:
+ *errmsg = "unsupported old_value type";
+ return NGX_ERROR;
+ }
+
+ ngx_shmtx_lock(&ctx->shpool->mutex);
+
+#if 1
+ ngx_http_lua_shdict_expire(ctx, 1);
+#endif
+
+ rc = ngx_http_lua_shdict_lookup(zone, hash, key, key_len, &sd);
+
+ dd("lookup returns %d", (int) rc);
+
+
+ if (match_flags
+ && (rc != NGX_OK || old_user_flags != (int) sd->user_flags))
+ {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ *match = 1;
+ return NGX_DECLINED;
+ }
+
+ if (match_value
+ && (rc != NGX_OK
+ || old_value_type != sd->value_type
+ || old_str_value_len != sd->value_len
+ || ngx_memcmp(old_str_value_buf, sd->data + key_len,
+ (size_t) old_str_value_len)
+ ))
+ {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ *match = 1;
+ return NGX_DECLINED;
+ }
+
+ if (rc == NGX_OK || rc == NGX_DONE) {
+ if (value_type == LUA_TNIL && !set_user_flags) {
+ goto remove;
+ }
+
+ if (set_user_flags && value_type == LUA_TNIL) {
+ /* Only set flag, leave value unchanged */
+ ngx_queue_remove(&sd->queue);
+ ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue);
+
+ if (exptime > 0) {
+ tp = ngx_timeofday();
+ sd->expires = (uint64_t) tp->sec * 1000 + tp->msec
+ + (uint64_t) exptime;
+
+ } else {
+ sd->expires = 0;
+ }
+
+ sd->user_flags = user_flags;
+
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ return NGX_OK;
+ }
+
+ if (str_value_buf
+ && str_value_len == (size_t) sd->value_len
+ && sd->value_type != SHDICT_TLIST)
+ {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "lua shared dict set: found old entry and value "
+ "size matched, reusing it");
+
+ ngx_queue_remove(&sd->queue);
+ ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue);
+
+ if (exptime > 0) {
+ tp = ngx_timeofday();
+ sd->expires = (uint64_t) tp->sec * 1000 + tp->msec
+ + (uint64_t) exptime;
+
+ } else {
+ sd->expires = 0;
+ }
+
+ if (set_user_flags) {
+ sd->user_flags = user_flags;
+ }
+
+ dd("setting value type to %d", value_type);
+
+ sd->value_type = (uint8_t) value_type;
+
+ ngx_memcpy(sd->data + key_len, str_value_buf, str_value_len);
+
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ return NGX_OK;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "lua shared dict set: found old entry but value size "
+ "NOT matched, removing it first");
+ if (!set_user_flags) {
+ user_flags = sd->user_flags;
+ }
+
+remove:
+
+ if (sd->value_type == SHDICT_TLIST) {
+ queue = ngx_http_lua_shdict_get_list_head(sd, key_len);
+
+ for (q = ngx_queue_head(queue);
+ q != ngx_queue_sentinel(queue);
+ q = ngx_queue_next(q))
+ {
+ p = (u_char *) ngx_queue_data(q,
+ ngx_http_lua_shdict_list_node_t,
+ queue);
+
+ ngx_slab_free_locked(ctx->shpool, p);
+ }
+ }
+
+ ngx_queue_remove(&sd->queue);
+
+ node = (ngx_rbtree_node_t *)
+ ((u_char *) sd - offsetof(ngx_rbtree_node_t, color));
+
+ ngx_rbtree_delete(&ctx->sh->rbtree, node);
+
+ ngx_slab_free_locked(ctx->shpool, node);
+ }
+
+ /* rc == NGX_DECLINED or value size unmatch */
+
+ if (str_value_buf == NULL) {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ return NGX_OK;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "lua shared dict cas: creating a new entry");
+
+ n = offsetof(ngx_rbtree_node_t, color)
+ + offsetof(ngx_http_lua_shdict_node_t, data)
+ + key_len
+ + str_value_len;
+
+ node = ngx_http_lua_shdict_force_alloc(ctx, n, forcible);
+ if (*forcible == 1) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "lua shared dict cas: overwrote non-expired items "
+ "due to memory shortage for entry \"%*s\"", key_len,
+ key);
+ }
+
+ if (!node) {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ *errmsg = "no memory";
+ return NGX_ERROR;
+ }
+
+ sd = (ngx_http_lua_shdict_node_t *) &node->color;
+
+ node->key = hash;
+ sd->key_len = (u_short) key_len;
+
+ if (exptime > 0) {
+ tp = ngx_timeofday();
+ sd->expires = (uint64_t) tp->sec * 1000 + tp->msec
+ + (uint64_t) exptime;
+
+ } else {
+ sd->expires = 0;
+ }
+
+ sd->user_flags = user_flags;
+ sd->value_len = (uint32_t) str_value_len;
+ dd("setting value type to %d", value_type);
+ sd->value_type = (uint8_t) value_type;
+
+ p = ngx_copy(sd->data, key, key_len);
+ ngx_memcpy(p, str_value_buf, str_value_len);
+
+ ngx_rbtree_insert(&ctx->sh->rbtree, node);
+ ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue);
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ return NGX_OK;
+}
+
+
+int
+ngx_http_lua_ffi_shdict_get_if_not_eq(ngx_shm_zone_t *zone,
+ u_char *key, size_t key_len, int old_value_type,
+ u_char *old_str_value_buf, size_t old_str_value_len,
+ double old_num_value, int old_user_flags, int *value_type,
+ u_char **str_value_buf, size_t *str_value_len, double *num_value,
+ int *user_flags, int match_flags, int *unmatch, char **errmsg)
+{
+ u_char oc;
+ ngx_str_t name;
+ uint32_t hash;
+ ngx_int_t rc;
+ ngx_http_lua_shdict_ctx_t *ctx;
+ ngx_http_lua_shdict_node_t *sd;
+ ngx_str_t value;
+ int match_value;
+
+ *errmsg = NULL;
+ *unmatch = 0;
+
+ ctx = zone->data;
+ name = ctx->name;
+
+ hash = ngx_crc32_short(key, key_len);
+
+ switch (old_value_type) {
+ case SHDICT_TSTRING:
+ /* do nothing */
+ match_value = 1;
+ break;
+
+ case SHDICT_TNUMBER:
+ dd("num value: %lf", old_num_value);
+ old_str_value_buf = (u_char *) &old_num_value;
+ old_str_value_len = sizeof(double);
+ match_value = 1;
+ break;
+
+ case SHDICT_TBOOLEAN:
+ oc = old_num_value ? 1 : 0;
+ old_str_value_buf = &oc;
+ old_str_value_len = sizeof(u_char);
+ match_value = 1;
+ break;
+
+ case LUA_TNIL:
+ match_value = 0;
+ break;
+
+ default:
+ *errmsg = "unsupported old_value type";
+ return NGX_ERROR;
+ }
+
+#if (NGX_DEBUG)
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "fetching key \"%*s\" in shared dict \"%V\"", key_len,
+ key, &name);
+#endif /* NGX_DEBUG */
+
+ ngx_shmtx_lock(&ctx->shpool->mutex);
+
+#if 1
+ ngx_http_lua_shdict_expire(ctx, 1);
+#endif
+
+ rc = ngx_http_lua_shdict_lookup(zone, hash, key, key_len, &sd);
+
+ dd("shdict lookup returns %d", (int) rc);
+
+ if (rc == NGX_DECLINED || (rc == NGX_DONE)) {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ *value_type = LUA_TNIL;
+ return NGX_OK;
+ }
+
+ if (match_value || match_flags) {
+ if (!match_flags || old_user_flags == (int) sd->user_flags)
+ {
+ if (!match_value || (old_value_type == sd->value_type &&
+ old_str_value_len == sd->value_len &&
+ !ngx_memcmp(old_str_value_buf, sd->data + key_len,
+ (size_t) old_str_value_len)))
+ {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ *unmatch = 1;
+ return NGX_DECLINED;
+ }
+ }
+ }
+
+ *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;
+
+ if (*str_value_len < (size_t) value.len) {
+ if (*value_type == SHDICT_TBOOLEAN) {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ return NGX_ERROR;
+ }
+
+ if (*value_type == SHDICT_TSTRING) {
+ *str_value_buf = malloc(value.len);
+ if (*str_value_buf == NULL) {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ switch (*value_type) {
+
+ case SHDICT_TSTRING:
+ *str_value_len = value.len;
+ ngx_memcpy(*str_value_buf, value.data, value.len);
+ break;
+
+ case SHDICT_TNUMBER:
+
+ if (value.len != sizeof(double)) {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
+ "bad lua number value size found for key %*s "
+ "in shared_dict %V: %z", key_len, key,
+ &name, value.len);
+ return NGX_ERROR;
+ }
+
+ *str_value_len = value.len;
+ ngx_memcpy(num_value, value.data, sizeof(double));
+ break;
+
+ case SHDICT_TBOOLEAN:
+
+ if (value.len != sizeof(u_char)) {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
+ "bad lua boolean value size found for key %*s "
+ "in shared_dict %V: %z", key_len, key, &name,
+ value.len);
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(*str_value_buf, value.data, value.len);
+ break;
+
+ case SHDICT_TLIST:
+
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ *errmsg = "value is a list";
+ return NGX_ERROR;
+
+ default:
+
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
+ "bad value type found for key %*s in "
+ "shared_dict %V: %d", key_len, key, &name,
+ *value_type);
+ return NGX_ERROR;
+ }
+
+ *user_flags = sd->user_flags;
+ dd("user flags: %d", *user_flags);
+
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ return NGX_OK;
+}
+
+
int
ngx_http_lua_ffi_shdict_store(ngx_shm_zone_t *zone, int op, u_char *key,
size_t key_len, int value_type, u_char *str_value_buf,
size_t str_value_len, double num_value, long exptime, int user_flags,
char **errmsg, int *forcible)
{
- int i, n;
+ int n;
u_char c, *p;
uint32_t hash;
ngx_int_t rc;
@@ -1497,43 +1978,26 @@ ngx_http_lua_ffi_shdict_store(ngx_shm_zone_t *zone, int op, u_char *key,
+ key_len
+ str_value_len;
- node = ngx_slab_alloc_locked(ctx->shpool, n);
-
- if (node == NULL) {
-
- if (op & NGX_HTTP_LUA_SHDICT_SAFE_STORE) {
- ngx_shmtx_unlock(&ctx->shpool->mutex);
-
- *errmsg = "no memory";
- return NGX_ERROR;
- }
-
- ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
- "lua shared dict set: overriding non-expired items "
- "due to memory shortage for entry \"%*s\"", key_len,
- key);
-
- for (i = 0; i < 30; i++) {
- if (ngx_http_lua_shdict_expire(ctx, 0) == 0) {
- break;
- }
-
- *forcible = 1;
+ if (op & NGX_HTTP_LUA_SHDICT_SAFE_STORE) {
+ node = ngx_slab_alloc_locked(ctx->shpool, n);
- node = ngx_slab_alloc_locked(ctx->shpool, n);
- if (node != NULL) {
- goto allocated;
- }
+ } else {
+ node = ngx_http_lua_shdict_force_alloc(ctx, n, forcible);
+ if (*forcible) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "lua shared dict store: overwrote non-expired items "
+ "due to memory shortage for entry \"%*s\"", key_len,
+ key);
}
+ }
+ if (!node) {
ngx_shmtx_unlock(&ctx->shpool->mutex);
*errmsg = "no memory";
return NGX_ERROR;
}
-allocated:
-
sd = (ngx_http_lua_shdict_node_t *) &node->color;
node->key = hash;
diff --git a/t/062-count.t b/t/062-count.t
index f5ba12c91d..c33a73abb5 100644
--- a/t/062-count.t
+++ b/t/062-count.t
@@ -283,7 +283,7 @@ n = 6
--- request
GET /test
--- response_body
-n = 22
+n = 24
--- no_error_log
[error]
diff --git a/t/167-atomic-shdict.t b/t/167-atomic-shdict.t
new file mode 100644
index 0000000000..b91eb2b0d8
--- /dev/null
+++ b/t/167-atomic-shdict.t
@@ -0,0 +1,665 @@
+# vim:set ft= ts=4 sw=4 et fdm=marker:
+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: CAS int value
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32)
+ local ok, err = dogs:cas("foo", 32, nil, 42)
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val), " ", ok)
+ }
+ }
+--- request
+GET /test
+--- response_body
+42 number true
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: CAS int value failed
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32)
+ local ok, err = dogs:cas("foo", 31, nil, 42)
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val), " ", ok, " ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+32 number false false
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: CAS string value
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ local value = "Turboencabulator"
+ dogs:set("foo", value)
+ dogs:cas("foo", value, nil, "bar")
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val))
+ }
+ }
+--- request
+GET /test
+--- response_body
+bar string
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: CAS string value invalid
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ local value = "Turboencabulator"
+ dogs:set("foo", value)
+ local ok, err = dogs:cas("foo", "c", nil, "bar")
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val), " ", ok, " ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+Turboencabulator string false false
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: CAS boolean value
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", true)
+ dogs:cas("foo", true, nil, false)
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val))
+ }
+ }
+--- request
+GET /test
+--- response_body
+false boolean
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: CAS flags
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", "bar", nil, 42)
+ local ok, err = dogs:cas("foo", nil, 42, "baz")
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val), " ", ok, " ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+baz string true nil
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: CAS flags invalid
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", "bar", nil, 42)
+ local ok, err = dogs:cas("foo", nil, 41, "baz")
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val), " ", ok, " ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+bar string false false
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: CAS invalid flags error message
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", "bar", nil, 42)
+ local ok, err = dogs:cas("foo", nil, 41, "baz")
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val), " ", ok, " ", err )
+
+ }
+ }
+--- request
+GET /test
+--- response_body
+bar string false false
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: CAS invalid value error message
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", "bar", nil, 42)
+ local ok, err = dogs:cas("foo", "baz", nil, "bas")
+
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val), " ", ok, " ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+bar string false false
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: COG number value
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32)
+ local val = dogs:get_if_not_eq("foo", 31)
+ ngx.say(val, " ", type(val))
+ }
+ }
+--- request
+GET /test
+--- response_body
+32 number
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: COG number value match
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32)
+ local val, err = dogs:get_if_not_eq("foo", 32)
+ ngx.say(val, " ", type(val)," ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+nil nil false
+--- no_error_log
+[error]
+
+
+
+=== TEST 12: COG string value
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", "bar")
+ local val, err = dogs:get_if_not_eq("foo", "baz")
+ ngx.say(val, " ", type(val))
+ }
+ }
+--- request
+GET /test
+--- response_body
+bar string
+--- no_error_log
+[error]
+
+
+
+=== TEST 13: COG string value match
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", "bar")
+ local val, err = dogs:get_if_not_eq("foo", "bar")
+ ngx.say(val, " ", type(val)," ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+nil nil false
+--- no_error_log
+[error]
+
+
+
+=== TEST 14: COG boolean value
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", true)
+ local val, err = dogs:get_if_not_eq("foo", false)
+ ngx.say(val, " ", type(val))
+ }
+ }
+--- request
+GET /test
+--- response_body
+true boolean
+--- no_error_log
+[error]
+
+
+
+=== TEST 15: COG boolean value match
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", true)
+ local val, err = dogs:get_if_not_eq("foo", true)
+ ngx.say(val, " ", type(val)," ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+nil nil false
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: COG flags
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, 42)
+ local val, err = dogs:get_if_not_eq("foo", nil, 41)
+ ngx.say(val, " ", type(val))
+ }
+ }
+--- request
+GET /test
+--- response_body
+32 number
+--- no_error_log
+[error]
+
+
+
+=== TEST 17: COG flags match
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, nil, 42)
+ local val, err = dogs:get_if_not_eq("foo", nil, 42)
+ ngx.say(val, " ", type(val)," ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+nil nil false
+--- no_error_log
+[error]
+
+
+
+=== TEST 18: COG flags 0
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, nil, 1)
+ local val = dogs:get_if_not_eq("foo", nil, 0)
+ ngx.say(val, " ", type(val))
+ }
+ }
+--- request
+GET /test
+--- response_body
+32 number
+--- no_error_log
+[error]
+
+
+
+=== TEST 19: COG flags 0 match
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32)
+ local val, err = dogs:get_if_not_eq("foo", nil, 0)
+ ngx.say(val, " ", type(val)," ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+nil nil false
+--- no_error_log
+[error]
+
+
+
+=== TEST 20: COG flags match but not value
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32)
+ local val, err = dogs:get_if_not_eq("foo", 31, 0)
+ ngx.say(val, " ", type(val))
+ }
+ }
+--- request
+GET /test
+--- response_body
+32 number
+--- no_error_log
+[error]
+
+
+
+=== TEST 21: COG value match but not flags
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, 0, 10)
+ local val = dogs:get_if_not_eq("foo", 32, 11)
+ ngx.say(val, " ", type(val))
+ }
+ }
+--- request
+GET /test
+--- response_body
+32 number
+--- no_error_log
+[error]
+
+
+
+=== TEST 22: COG flags and value match
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, 0, 10)
+ local val, err = dogs:get_if_not_eq("foo", 32, 10)
+ ngx.say(val, " ", type(val)," ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+nil nil false
+--- no_error_log
+[error]
+
+
+
+=== TEST 23: CAS only value match
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, 0, 10)
+ local ok, err = dogs:cas("foo", 32, 11, 1, 1)
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val)," ", ok, " ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+32 number false false
+--- no_error_log
+[error]
+
+
+
+=== TEST 24: CAS only flags match
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, 0, 10)
+ local ok, err = dogs:cas("foo", 31, 10, 1, 1)
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val)," ", ok, " ", err)
+ }
+ }
+--- request
+GET /test
+--- response_body
+32 number false false
+--- no_error_log
+[error]
+
+
+
+=== TEST 25: CAS set nil
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, 0, 10)
+ local ok, err = dogs:cas("foo", 32, 10)
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val)," ", ok)
+ }
+ }
+--- request
+GET /test
+--- response_body
+nil nil true
+--- no_error_log
+[error]
+
+
+
+=== TEST 26: CAS as remove
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, 0, 10)
+ local ok, err = dogs:cas("foo")
+ local val = dogs:get("foo")
+ ngx.say(val, " ", type(val)," ", ok)
+ }
+ }
+--- request
+GET /test
+--- response_body
+nil nil true
+--- no_error_log
+[error]
+
+
+
+=== TEST 27: CAS as set value
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, 0, 10)
+ local ok, err = dogs:cas("foo", nil, nil, "foo")
+ local val, flags = dogs:get("foo")
+ ngx.say(val, " ", type(val)," ", flags, " ", ok)
+ }
+ }
+--- request
+GET /test
+--- response_body
+foo string 10 true
+--- no_error_log
+[error]
+
+
+
+=== TEST 28: CAS as set flag
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, 0, 10)
+ local ok, err = dogs:cas("foo", nil, nil, nil, 13)
+ local val, flags = dogs:get("foo")
+ ngx.say(val, " ", type(val), " ", flags, " ", ok)
+ }
+ }
+--- request
+GET /test
+--- response_body
+32 number 13 true
+--- no_error_log
+[error]
+
+
+
+=== TEST 29: COG as get
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ dogs:set("foo", 32, 0, 10)
+ local val, flags = dogs:get_if_not_eq("foo")
+ ngx.say(val, " ", type(val), " ", flags )
+ }
+ }
+--- request
+GET /test
+--- response_body
+32 number 10
+--- no_error_log
+[error]
+
+
+
+=== TEST 30: COG get nothing
+--- http_config
+ lua_shared_dict dogs 1m;
+--- config
+ location = /test {
+ content_by_lua_block {
+ local dogs = ngx.shared.dogs
+ local val, flags = dogs:get_if_not_eq("foo")
+ ngx.say(val, " ", type(val), " ", flags )
+ }
+ }
+--- request
+GET /test
+--- response_body
+nil nil nil
+--- no_error_log
+[error]