[njs] Modules: introduced js_shared_dict_zone directive.

Dmitry Volyntsev xeioex at nginx.com
Mon Jul 3 20:34:14 UTC 2023


details:   https://hg.nginx.org/njs/rev/ff7eb3c4bf76
branches:  
changeset: 2176:ff7eb3c4bf76
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Mon Jul 03 13:32:41 2023 -0700
description:
Modules: introduced js_shared_dict_zone directive.

The directive allows to declare a dictionary that is shared among the
working processes. A dictionary expects strings as keys. It stores
string or number as values. The value type is declared using
type= argument of the directive. The default type is string.

example.conf:
    # Declares a shared dictionary of strings of size 1 Mb that
    # removes key-value after 60 seconds of inactivity.
    js_shared_dict_zone zone=foo:1M timeout=60s;

    # Declares a shared dictionary of strings of size 512Kb that
    # forcibly remove oldest key-value pairs when memory is not enough.
    js_shared_dict_zone zone=bar:512K timeout=30s evict;

    # Declares a permanent number shared dictionary of size 32Kb.
    js_shared_dict_zone zone=num:32k type=number;

example.js:
    function get(r) {
        r.return(200, ngx.shared.foo.get(r.args.key));
    }

    function set(r) {
        r.return(200, ngx.shared.foo.set(r.args.key, r.args.value));
    }

    function delete(r) {
        r.return(200, ngx.shared.bar.delete(r.args.key));
    }

    function increment(r) {
        r.return(200, ngx.shared.num.incr(r.args.key, 2));
    }

In collaboration with Artem S. Povalyukhin, Jakub Jirutka and
洪志道 (Hong Zhi Dao).

This closes #437 issue on Github.

diffstat:

 nginx/config                    |     6 +-
 nginx/ngx_http_js_module.c      |    48 +-
 nginx/ngx_js.c                  |    34 +
 nginx/ngx_js.h                  |    18 +
 nginx/ngx_js_shared_dict.c      |  1586 +++++++++++++++++++++++++++++++++++++++
 nginx/ngx_js_shared_dict.h      |    19 +
 nginx/ngx_stream_js_module.c    |    48 +-
 nginx/t/js_shared_dict.t        |   299 +++++++
 nginx/t/stream_js_shared_dict.t |   188 ++++
 ts/ngx_core.d.ts                |   150 +++
 10 files changed, 2390 insertions(+), 6 deletions(-)

diffs (truncated from 2634 to 1000 lines):

diff -r 167f75576f49 -r ff7eb3c4bf76 nginx/config
--- a/nginx/config	Mon Jul 03 12:49:00 2023 -0700
+++ b/nginx/config	Mon Jul 03 13:32:41 2023 -0700
@@ -5,10 +5,12 @@ NJS_LIBXSLT=${NJS_LIBXSLT:-YES}
 NJS_ZLIB=${NJS_ZLIB:-YES}
 
 NJS_DEPS="$ngx_addon_dir/ngx_js.h \
-    $ngx_addon_dir/ngx_js_fetch.h"
+    $ngx_addon_dir/ngx_js_fetch.h \
+    $ngx_addon_dir/ngx_js_shared_dict.h"
 NJS_SRCS="$ngx_addon_dir/ngx_js.c \
     $ngx_addon_dir/ngx_js_fetch.c \
-    $ngx_addon_dir/ngx_js_regex.c"
+    $ngx_addon_dir/ngx_js_regex.c \
+    $ngx_addon_dir/ngx_js_shared_dict.c"
 
 NJS_OPENSSL_LIB=
 NJS_XSLT_LIB=
diff -r 167f75576f49 -r ff7eb3c4bf76 nginx/ngx_http_js_module.c
--- a/nginx/ngx_http_js_module.c	Mon Jul 03 12:49:00 2023 -0700
+++ b/nginx/ngx_http_js_module.c	Mon Jul 03 13:32:41 2023 -0700
@@ -252,10 +252,13 @@ static char *ngx_http_js_set(ngx_conf_t 
 static char *ngx_http_js_var(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
 static char *ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
+static char *ngx_http_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
 static char *ngx_http_js_body_filter_set(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
 static ngx_int_t ngx_http_js_init_conf_vm(ngx_conf_t *cf,
     ngx_js_loc_conf_t *conf);
+static void *ngx_http_js_create_main_conf(ngx_conf_t *cf);
 static void *ngx_http_js_create_loc_conf(ngx_conf_t *cf);
 static char *ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent,
     void *child);
@@ -393,6 +396,13 @@ static ngx_command_t  ngx_http_js_comman
 
 #endif
 
+    { ngx_string("js_shared_dict_zone"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_1MORE,
+      ngx_http_js_shared_dict_zone,
+      0,
+      0,
+      NULL },
+
       ngx_null_command
 };
 
@@ -401,7 +411,7 @@ static ngx_http_module_t  ngx_http_js_mo
     NULL,                          /* preconfiguration */
     ngx_http_js_init,              /* postconfiguration */
 
-    NULL,                          /* create main configuration */
+    ngx_http_js_create_main_conf,  /* create main configuration */
     NULL,                          /* init main configuration */
 
     NULL,                          /* create server configuration */
@@ -775,6 +785,7 @@ static uintptr_t ngx_http_js_uptr[] = {
     (uintptr_t) ngx_http_js_fetch_timeout,
     (uintptr_t) ngx_http_js_buffer_size,
     (uintptr_t) ngx_http_js_max_response_buffer_size,
+    (uintptr_t) 0 /* main_conf ptr */,
 };
 
 
@@ -798,6 +809,7 @@ njs_module_t *njs_http_js_addon_modules[
      */
     &ngx_js_ngx_module,
     &ngx_js_fetch_module,
+    &ngx_js_shared_dict_module,
 #ifdef NJS_HAVE_OPENSSL
     &njs_webcrypto_module,
 #endif
@@ -4149,10 +4161,14 @@ ngx_js_http_init(njs_vm_t *vm)
 static ngx_int_t
 ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf)
 {
-    njs_vm_opt_t  options;
+    njs_vm_opt_t         options;
+    ngx_js_main_conf_t  *jmcf;
 
     njs_vm_opt_init(&options);
 
+    jmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_js_module);
+    ngx_http_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf;
+
     options.backtrace = 1;
     options.unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_THROW;
     options.ops = &ngx_http_js_ops;
@@ -4293,6 +4309,14 @@ ngx_http_js_content(ngx_conf_t *cf, ngx_
 
 
 static char *
+ngx_http_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+    return ngx_js_shared_dict_zone(cf, cmd, conf, &ngx_http_js_module);
+}
+
+
+static char *
 ngx_http_js_body_filter_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     ngx_http_js_loc_conf_t *jlcf = conf;
@@ -4331,6 +4355,26 @@ ngx_http_js_body_filter_set(ngx_conf_t *
 
 
 static void *
+ngx_http_js_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_js_main_conf_t  *jmcf;
+
+    jmcf = ngx_pcalloc(cf->pool, sizeof(ngx_js_main_conf_t));
+    if (jmcf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     jmcf->dicts = NULL;
+     */
+
+    return jmcf;
+}
+
+
+static void *
 ngx_http_js_create_loc_conf(ngx_conf_t *cf)
 {
     ngx_http_js_loc_conf_t  *conf =
diff -r 167f75576f49 -r ff7eb3c4bf76 nginx/ngx_js.c
--- a/nginx/ngx_js.c	Mon Jul 03 12:49:00 2023 -0700
+++ b/nginx/ngx_js.c	Mon Jul 03 13:32:41 2023 -0700
@@ -32,6 +32,28 @@ static void ngx_js_cleanup_vm(void *data
 static njs_int_t ngx_js_core_init(njs_vm_t *vm);
 
 
+static njs_external_t  ngx_js_ext_global_shared[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+        .u.property = {
+            .value = "GlobalShared",
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_SELF,
+        .u.object = {
+            .enumerable = 1,
+            .prop_handler = njs_js_ext_global_shared_prop,
+            .keys = njs_js_ext_global_shared_keys,
+        }
+    },
+
+};
+
+
 static njs_external_t  ngx_js_ext_core[] = {
 
     {
@@ -113,6 +135,18 @@ static njs_external_t  ngx_js_ext_core[]
     },
 
     {
+        .flags = NJS_EXTERN_OBJECT,
+        .name.string = njs_str("shared"),
+        .enumerable = 1,
+        .writable = 1,
+        .u.object = {
+            .enumerable = 1,
+            .properties = ngx_js_ext_global_shared,
+            .nproperties = njs_nitems(ngx_js_ext_global_shared),
+        }
+    },
+
+    {
         .flags = NJS_EXTERN_PROPERTY,
         .name.string = njs_str("prefix"),
         .enumerable = 1,
diff -r 167f75576f49 -r ff7eb3c4bf76 nginx/ngx_js.h
--- a/nginx/ngx_js.h	Mon Jul 03 12:49:00 2023 -0700
+++ b/nginx/ngx_js.h	Mon Jul 03 13:32:41 2023 -0700
@@ -14,6 +14,7 @@
 #include <ngx_core.h>
 #include <njs.h>
 #include "ngx_js_fetch.h"
+#include "ngx_js_shared_dict.h"
 
 
 #define NGX_JS_UNSET        0
@@ -43,6 +44,9 @@ typedef ngx_flag_t (*ngx_external_size_p
     njs_external_ptr_t e);
 typedef ngx_ssl_t *(*ngx_external_ssl_pt)(njs_vm_t *vm, njs_external_ptr_t e);
 
+
+typedef struct ngx_js_dict_s  ngx_js_dict_t;
+
 typedef struct {
     ngx_str_t              name;
     ngx_str_t              path;
@@ -51,6 +55,10 @@ typedef struct {
 } ngx_js_named_path_t;
 
 
+#define NGX_JS_COMMON_MAIN_CONF                                               \
+    ngx_js_dict_t         *dicts                                              \
+
+
 #define _NGX_JS_COMMON_LOC_CONF                                               \
     njs_vm_t              *vm;                                                \
     ngx_array_t           *imports;                                           \
@@ -81,6 +89,11 @@ typedef struct {
 
 
 typedef struct {
+    NGX_JS_COMMON_MAIN_CONF;
+} ngx_js_main_conf_t;
+
+
+typedef struct {
     NGX_JS_COMMON_LOC_CONF;
 } ngx_js_loc_conf_t;
 
@@ -105,6 +118,9 @@ typedef struct {
     ((ngx_external_size_pt) njs_vm_meta(vm, 8))(vm, e)
 #define ngx_external_max_response_buffer_size(vm, e)                          \
     ((ngx_external_size_pt) njs_vm_meta(vm, 9))(vm, e)
+#define NGX_JS_MAIN_CONF_INDEX  10
+#define ngx_main_conf(vm)                                                     \
+	((ngx_js_main_conf_t *) njs_vm_meta(vm, NGX_JS_MAIN_CONF_INDEX))
 
 
 #define ngx_js_prop(vm, type, value, start, len)                              \
@@ -134,6 +150,8 @@ ngx_int_t ngx_js_init_conf_vm(ngx_conf_t
 ngx_js_loc_conf_t *ngx_js_create_conf(ngx_conf_t *cf, size_t size);
 char * ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child,
    ngx_int_t (*init_vm)(ngx_conf_t *cf, ngx_js_loc_conf_t *conf));
+char *ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf,
+    void *tag);
 
 njs_int_t ngx_js_ext_string(njs_vm_t *vm, njs_object_prop_t *prop,
     njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
diff -r 167f75576f49 -r ff7eb3c4bf76 nginx/ngx_js_shared_dict.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nginx/ngx_js_shared_dict.c	Mon Jul 03 13:32:41 2023 -0700
@@ -0,0 +1,1586 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include "ngx_js.h"
+#include "ngx_js_shared_dict.h"
+
+
+typedef struct {
+    ngx_rbtree_t           rbtree;
+    ngx_rbtree_node_t      sentinel;
+    ngx_atomic_t           rwlock;
+
+    ngx_rbtree_t           rbtree_expire;
+    ngx_rbtree_node_t      sentinel_expire;
+} ngx_js_dict_sh_t;
+
+
+typedef struct {
+    ngx_str_node_t         sn;
+    ngx_rbtree_node_t      expire;
+    union {
+        ngx_str_t          value;
+        double             number;
+    } u;
+} ngx_js_dict_node_t;
+
+
+struct ngx_js_dict_s {
+    ngx_shm_zone_t        *shm_zone;
+    ngx_js_dict_sh_t      *sh;
+    ngx_slab_pool_t       *shpool;
+
+    ngx_msec_t             timeout;
+    ngx_flag_t             evict;
+#define NGX_JS_DICT_TYPE_STRING  0
+#define NGX_JS_DICT_TYPE_NUMBER  1
+    ngx_uint_t             type;
+
+    ngx_js_dict_t         *next;
+};
+
+
+static njs_int_t njs_js_ext_shared_dict_capacity(njs_vm_t *vm,
+    njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
+    njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_clear(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t flags, njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_delete(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_free_space(njs_vm_t *vm,
+    njs_value_t *args, njs_uint_t nargs, njs_index_t unused,
+    njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_get(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_has(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_keys(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_incr(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_name(njs_vm_t *vm,
+    njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
+    njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_pop(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_set(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t flags, njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_size(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_js_ext_shared_dict_type(njs_vm_t *vm,
+    njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
+    njs_value_t *retval);
+static ngx_js_dict_node_t *ngx_js_dict_lookup(ngx_js_dict_t *dict,
+    njs_str_t *key);
+
+#define NGX_JS_DICT_FLAG_MUST_EXIST       1
+#define NGX_JS_DICT_FLAG_MUST_NOT_EXIST   2
+
+static ngx_int_t ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict,
+    njs_str_t *key, njs_value_t *value, unsigned flags);
+static ngx_int_t ngx_js_dict_add(ngx_js_dict_t *dict, njs_str_t *key,
+    njs_value_t *value, ngx_msec_t now);
+static ngx_int_t ngx_js_dict_update(ngx_js_dict_t *dict,
+    ngx_js_dict_node_t *node, njs_value_t *value, ngx_msec_t now);
+static ngx_int_t ngx_js_dict_get(njs_vm_t *vm, ngx_js_dict_t *dict,
+    njs_str_t *key, njs_value_t *retval);
+static ngx_int_t ngx_js_dict_incr(ngx_js_dict_t *dict, njs_str_t *key,
+    njs_value_t *delta, njs_value_t *init, double *value);
+static ngx_int_t ngx_js_dict_delete(njs_vm_t *vm, ngx_js_dict_t *dict,
+    njs_str_t *key, njs_value_t *retval);
+static ngx_int_t ngx_js_dict_copy_value_locked(njs_vm_t *vm,
+    ngx_js_dict_t *dict, ngx_js_dict_node_t *node, njs_value_t *retval);
+
+static void ngx_js_dict_expire(ngx_js_dict_t *dict, ngx_msec_t now);
+static void ngx_js_dict_evict(ngx_js_dict_t *dict, ngx_int_t count);
+
+static njs_int_t ngx_js_dict_shared_error_name(njs_vm_t *vm,
+    njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
+    njs_value_t *retval);
+
+static ngx_int_t ngx_js_dict_init_zone(ngx_shm_zone_t *shm_zone, void *data);
+static njs_int_t ngx_js_shared_dict_preinit(njs_vm_t *vm);
+static njs_int_t ngx_js_shared_dict_init(njs_vm_t *vm);
+
+
+static njs_external_t  ngx_js_ext_shared_dict[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+        .u.property = {
+            .value = "SharedDict",
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("add"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_set,
+            .magic8 = NGX_JS_DICT_FLAG_MUST_NOT_EXIST,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("capacity"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_js_ext_shared_dict_capacity,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("clear"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_clear,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("freeSpace"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_free_space,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("delete"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_delete,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("incr"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_incr,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("get"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_get,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("has"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_has,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("keys"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_keys,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("name"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_js_ext_shared_dict_name,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("pop"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_pop,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("replace"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_set,
+            .magic8 = NGX_JS_DICT_FLAG_MUST_EXIST,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("set"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_set,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("size"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = njs_js_ext_shared_dict_size,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("type"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_js_ext_shared_dict_type,
+        }
+    },
+
+};
+
+
+static njs_external_t  ngx_js_ext_error_ctor_props[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("name"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_js_dict_shared_error_name,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("prototype"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_object_prototype_create,
+        }
+    },
+
+};
+
+
+static njs_external_t  ngx_js_ext_error_proto_props[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("name"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_js_dict_shared_error_name,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("prototype"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = njs_object_prototype_create,
+        }
+    },
+
+};
+
+
+static njs_int_t    ngx_js_shared_dict_proto_id;
+static njs_int_t    ngx_js_shared_dict_error_id;
+
+
+njs_module_t  ngx_js_shared_dict_module = {
+    .name = njs_str("shared_dict"),
+    .preinit = ngx_js_shared_dict_preinit,
+    .init = ngx_js_shared_dict_init,
+};
+
+
+njs_int_t
+njs_js_ext_global_shared_prop(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
+{
+    njs_int_t            ret;
+    njs_str_t            name;
+    ngx_js_dict_t       *dict;
+    ngx_shm_zone_t      *shm_zone;
+    ngx_js_main_conf_t  *conf;
+
+    ret = njs_vm_prop_name(vm, prop, &name);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    conf = ngx_main_conf(vm);
+
+    for (dict = conf->dicts; dict != NULL; dict = dict->next) {
+        shm_zone = dict->shm_zone;
+
+        if (shm_zone->shm.name.len == name.length
+            && ngx_strncmp(shm_zone->shm.name.data, name.start,
+                           name.length)
+               == 0)
+        {
+            ret = njs_vm_external_create(vm, retval,
+                                         ngx_js_shared_dict_proto_id,
+                                         shm_zone, 0);
+            if (ret != NJS_OK) {
+                njs_vm_internal_error(vm, "sharedDict creation failed");
+                return NJS_ERROR;
+            }
+
+            return NJS_OK;
+        }
+    }
+
+    njs_value_null_set(retval);
+
+    return NJS_DECLINED;
+}
+
+
+njs_int_t
+njs_js_ext_global_shared_keys(njs_vm_t *vm, njs_value_t *unused,
+    njs_value_t *keys)
+{
+    njs_int_t            rc;
+    njs_value_t         *value;
+    ngx_js_dict_t       *dict;
+    ngx_shm_zone_t      *shm_zone;
+    ngx_js_main_conf_t  *conf;
+
+    conf = ngx_main_conf(vm);
+
+    rc = njs_vm_array_alloc(vm, keys, 4);
+    if (rc != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    for (dict = conf->dicts; dict != NULL; dict = dict->next) {
+        shm_zone = dict->shm_zone;
+
+        value = njs_vm_array_push(vm, keys);
+        if (value == NULL) {
+            return NJS_ERROR;
+        }
+
+        rc = njs_vm_value_string_set(vm, value, shm_zone->shm.name.data,
+                                     shm_zone->shm.name.len);
+        if (rc != NJS_OK) {
+            return NJS_ERROR;
+        }
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_js_ext_shared_dict_capacity(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
+{
+    ngx_shm_zone_t  *shm_zone;
+
+    shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, value);
+    if (shm_zone == NULL) {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    njs_value_number_set(retval, shm_zone->shm.size);
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_js_ext_shared_dict_clear(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    ngx_js_dict_t   *dict;
+    ngx_shm_zone_t  *shm_zone;
+
+    shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id,
+                               njs_argument(args, 0));
+    if (shm_zone == NULL) {
+        njs_vm_type_error(vm, "\"this\" is not a shared dict");
+        return NJS_ERROR;
+    }
+
+    dict = shm_zone->data;
+
+    ngx_rwlock_wlock(&dict->sh->rwlock);
+
+    ngx_js_dict_evict(dict, 0x7fffffff /* INT_MAX */);
+
+    ngx_rwlock_unlock(&dict->sh->rwlock);
+
+    njs_value_undefined_set(retval);
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_js_ext_shared_dict_free_space(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
+{
+    size_t           bytes;
+    ngx_js_dict_t   *dict;
+    ngx_shm_zone_t  *shm_zone;
+
+    shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id,
+                               njs_argument(args, 0));
+    if (shm_zone == NULL) {
+        njs_vm_type_error(vm, "\"this\" is not a shared dict");
+        return NJS_ERROR;
+    }
+
+    dict = shm_zone->data;
+
+    ngx_rwlock_rlock(&dict->sh->rwlock);
+    bytes = dict->shpool->pfree * ngx_pagesize;
+    ngx_rwlock_unlock(&dict->sh->rwlock);
+
+    njs_value_number_set(retval, bytes);
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_js_ext_shared_dict_delete(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    ngx_int_t        rc;
+    njs_str_t        key;
+    ngx_shm_zone_t  *shm_zone;
+
+    shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id,
+                               njs_argument(args, 0));
+    if (shm_zone == NULL) {
+        njs_vm_type_error(vm, "\"this\" is not a shared dict");
+        return NJS_ERROR;
+    }
+
+    if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) {
+        return NJS_ERROR;
+    }
+
+    rc = ngx_js_dict_delete(vm, shm_zone->data, &key, NULL);
+
+    njs_value_boolean_set(retval, rc == NGX_OK);
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_js_ext_shared_dict_get(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    ngx_int_t        rc;
+    njs_str_t        key;
+    ngx_shm_zone_t  *shm_zone;
+
+    shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id,
+                               njs_argument(args, 0));
+    if (shm_zone == NULL) {
+        njs_vm_type_error(vm, "\"this\" is not a shared dict");
+        return NJS_ERROR;
+    }
+
+    if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) {
+        return NJS_ERROR;
+    }
+
+    rc = ngx_js_dict_get(vm, shm_zone->data, &key, retval);
+    if (njs_slow_path(rc == NGX_ERROR)) {
+        njs_vm_error(vm, "failed to get value from shared dict");
+        return NJS_ERROR;
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_js_ext_shared_dict_has(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    njs_str_t            key;
+    ngx_msec_t           now;
+    ngx_time_t          *tp;
+    ngx_js_dict_t       *dict;
+    ngx_shm_zone_t      *shm_zone;
+    ngx_js_dict_node_t  *node;
+
+    shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id,
+                               njs_argument(args, 0));
+    if (shm_zone == NULL) {
+        njs_vm_type_error(vm, "\"this\" is not a shared dict");
+        return NJS_ERROR;
+    }
+
+    if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) {
+        return NJS_ERROR;
+    }
+
+    dict = shm_zone->data;
+
+    ngx_rwlock_rlock(&dict->sh->rwlock);
+
+    node = ngx_js_dict_lookup(dict, &key);
+
+    if (node != NULL && dict->timeout) {
+        tp = ngx_timeofday();
+        now = tp->sec * 1000 + tp->msec;
+
+        if (now >= node->expire.key) {
+            node = NULL;
+        }
+    }
+
+    ngx_rwlock_unlock(&dict->sh->rwlock);
+
+    njs_value_boolean_set(retval, node != NULL);
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_js_ext_shared_dict_keys(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    njs_int_t            rc;
+    ngx_int_t            max_count;
+    njs_value_t         *value;
+    ngx_rbtree_t        *rbtree;
+    ngx_js_dict_t       *dict;
+    ngx_shm_zone_t      *shm_zone;
+    ngx_rbtree_node_t   *rn;
+    ngx_js_dict_node_t  *node;
+
+    shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id,
+                               njs_argument(args, 0));
+    if (shm_zone == NULL) {
+        njs_vm_type_error(vm, "\"this\" is not a shared dict");
+        return NJS_ERROR;
+    }
+
+    dict = shm_zone->data;
+
+    max_count = 1024;
+
+    if (nargs > 1) {
+        if (ngx_js_integer(vm, njs_arg(args, nargs, 1), &max_count) != NGX_OK) {
+            return NJS_ERROR;
+        }
+    }
+
+    rc = njs_vm_array_alloc(vm, retval, 8);
+    if (rc != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    ngx_rwlock_rlock(&dict->sh->rwlock);
+
+    rbtree = &dict->sh->rbtree;
+
+    if (rbtree->root == rbtree->sentinel) {
+        goto done;
+    }
+
+    for (rn = ngx_rbtree_min(rbtree->root, rbtree->sentinel);
+         rn != NULL;
+         rn = ngx_rbtree_next(rbtree, rn))
+    {
+        if (max_count-- == 0) {
+            break;
+        }
+
+        node = (ngx_js_dict_node_t *) rn;
+
+        value = njs_vm_array_push(vm, retval);
+        if (value == NULL) {
+            goto fail;
+        }
+
+        rc = njs_vm_value_string_set(vm, value, node->sn.str.data,
+                                     node->sn.str.len);
+        if (rc != NJS_OK) {
+            goto fail;
+        }
+    }
+
+done:
+
+    ngx_rwlock_unlock(&dict->sh->rwlock);
+
+    return NJS_OK;
+
+fail:
+
+    ngx_rwlock_unlock(&dict->sh->rwlock);
+
+    return NJS_ERROR;
+}
+
+
+static njs_int_t
+njs_js_ext_shared_dict_incr(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    double               value;
+    ngx_int_t            rc;
+    njs_str_t            key;
+    njs_value_t         *delta, *init;
+    ngx_js_dict_t       *dict;
+    ngx_shm_zone_t      *shm_zone;
+    njs_opaque_value_t   lvalue;
+
+    shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id,
+                               njs_argument(args, 0));
+    if (shm_zone == NULL) {
+        njs_vm_type_error(vm, "\"this\" is not a shared dict");
+        return NJS_ERROR;
+    }
+
+    dict = shm_zone->data;
+
+    if (dict->type != NGX_JS_DICT_TYPE_NUMBER) {
+        njs_vm_type_error(vm, "shared dict is not a number dict");
+        return NJS_ERROR;
+    }
+
+    if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) {
+        return NJS_ERROR;
+    }
+
+    delta = njs_arg(args, nargs, 2);
+    if (!njs_value_is_number(delta)) {
+        njs_vm_type_error(vm, "delta is not a number");
+        return NJS_ERROR;
+    }
+
+    init = njs_lvalue_arg(njs_value_arg(&lvalue), args, nargs, 3);
+    if (!njs_value_is_number(init) && !njs_value_is_undefined(init)) {
+        njs_vm_type_error(vm, "init value is not a number");
+        return NJS_ERROR;
+    }
+
+    if (njs_value_is_undefined(init)) {
+        njs_value_number_set(init, 0);
+    }
+
+    rc = ngx_js_dict_incr(shm_zone->data, &key, delta, init, &value);
+    if (rc == NGX_ERROR) {
+        njs_vm_error(vm, "failed to increment value in shared dict");
+        return NJS_ERROR;
+    }
+
+    njs_value_number_set(retval, value);
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_js_ext_shared_dict_name(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
+{
+    ngx_shm_zone_t  *shm_zone;
+
+    shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, value);
+    if (shm_zone == NULL) {
+        njs_value_undefined_set(retval);
+        return NJS_DECLINED;
+    }
+
+    return njs_vm_value_string_set(vm, retval, shm_zone->shm.name.data,
+                                   shm_zone->shm.name.len);
+}
+
+
+static njs_int_t


More information about the nginx-devel mailing list