From: Dmitry Volyntsev Date: Thu, 12 Mar 2026 23:48:14 +0000 (-0700) Subject: Modules: added ttl() method to shared dictionaries. X-Git-Tag: 0.9.7~13 X-Git-Url: http://www.kaiwu.me/postgresql/commit/static/gitweb.js?a=commitdiff_plain;h=cf376bf047d658fb5a95ec4b4ae57550e6f84843;p=njs.git Modules: added ttl() method to shared dictionaries. The method returns the remaining time-to-live in milliseconds for a given key, or undefined if the key does not exist or has expired. Throws TypeError if the dictionary was declared without the timeout parameter. --- diff --git a/nginx/ngx_js_shared_dict.c b/nginx/ngx_js_shared_dict.c index a7273df5..1963e24d 100644 --- a/nginx/ngx_js_shared_dict.c +++ b/nginx/ngx_js_shared_dict.c @@ -99,6 +99,8 @@ 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_ttl(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, uint32_t unused, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); @@ -171,6 +173,8 @@ static JSValue ngx_qjs_ext_shared_dict_set(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int flags); static JSValue ngx_qjs_ext_shared_dict_size(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue ngx_qjs_ext_shared_dict_ttl(JSContext *cx, + JSValueConst this_val, int argc, JSValueConst *argv); static JSValue ngx_qjs_ext_shared_dict_type(JSContext *cx, JSValueConst this_val); @@ -372,6 +376,17 @@ static njs_external_t ngx_js_ext_shared_dict[] = { } }, + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("ttl"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_ttl, + } + }, + { .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("type"), @@ -467,6 +482,7 @@ static const JSCFunctionListEntry ngx_qjs_ext_shared_dict[] = { NGX_JS_DICT_FLAG_MUST_EXIST), JS_CFUNC_MAGIC_DEF("set", 3, ngx_qjs_ext_shared_dict_set, 0), JS_CFUNC_DEF("size", 0, ngx_qjs_ext_shared_dict_size), + JS_CFUNC_DEF("ttl", 1, ngx_qjs_ext_shared_dict_ttl), JS_CGETSET_DEF("type", ngx_qjs_ext_shared_dict_type, NULL), }; @@ -1243,6 +1259,59 @@ njs_js_ext_shared_dict_size(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } +static njs_int_t +njs_js_ext_shared_dict_ttl(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + ngx_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; + } + + dict = shm_zone->data; + + if (!dict->timeout) { + njs_vm_type_error(vm, "shared dict must be declared with timeout"); + return NJS_ERROR; + } + + if (ngx_js_ngx_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + return NJS_ERROR; + } + + ngx_rwlock_rlock(&dict->sh->rwlock); + + node = ngx_js_dict_lookup(dict, &key); + + if (node != NULL) { + tp = ngx_timeofday(); + now = tp->sec * 1000 + tp->msec; + + if (now < node->expire.key) { + njs_value_number_set(retval, + (double) (node->expire.key - now)); + ngx_rwlock_unlock(&dict->sh->rwlock); + return NJS_OK; + } + } + + ngx_rwlock_unlock(&dict->sh->rwlock); + + njs_value_undefined_set(retval); + + return NJS_OK; +} + + static njs_int_t njs_js_ext_shared_dict_type(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused, njs_value_t *value, njs_value_t *setval, @@ -3755,6 +3824,57 @@ ngx_qjs_ext_shared_dict_size(JSContext *cx, JSValueConst this_val, } +static JSValue +ngx_qjs_ext_shared_dict_ttl(JSContext *cx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + ngx_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 = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_SHARED_DICT); + if (shm_zone == NULL) { + return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); + } + + dict = shm_zone->data; + + if (!dict->timeout) { + return JS_ThrowTypeError(cx, + "shared dict must be declared with timeout"); + } + + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { + return JS_EXCEPTION; + } + + ngx_rwlock_rlock(&dict->sh->rwlock); + + node = ngx_qjs_dict_lookup(dict, &key); + + if (node != NULL) { + tp = ngx_timeofday(); + now = tp->sec * 1000 + tp->msec; + + if (now < node->expire.key) { + ngx_rwlock_unlock(&dict->sh->rwlock); + JS_FreeCString(cx, (char *) key.data); + return JS_NewFloat64(cx, (double) (node->expire.key - now)); + } + } + + ngx_rwlock_unlock(&dict->sh->rwlock); + + JS_FreeCString(cx, (char *) key.data); + + return JS_UNDEFINED; +} + + static JSValue ngx_qjs_ext_shared_dict_type(JSContext *cx, JSValueConst this_val) { diff --git a/nginx/t/js_shared_dict.t b/nginx/t/js_shared_dict.t index 34a2a642..a28d09f8 100644 --- a/nginx/t/js_shared_dict.t +++ b/nginx/t/js_shared_dict.t @@ -128,6 +128,10 @@ http { js_content test.size; } + location /ttl { + js_content test.ttl; + } + location /zones { js_content test.zones; } @@ -352,6 +356,23 @@ $t->write_file('test.js', <<'EOF'); } + function ttl(r) { + var dict = ngx.shared[r.args.dict]; + + if (r.args.err) { + try { + dict.ttl(r.args.key); + r.return(200, 'no exception'); + } catch (e) { + r.return(200, e.toString()); + } + return; + } + + var val = dict.ttl(r.args.key); + r.return(200, val === undefined ? 'undefined' : val); + } + function zones(r) { r.return(200, Object.keys(ngx.shared).sort()); } @@ -359,12 +380,12 @@ $t->write_file('test.js', <<'EOF'); export default { add, capacity, chain, clear, del, evict_stress, free_space, get, has, incr, items, keys, name, njs: test_njs, pop, replace, set, set_clear, size, - zones, overflow }; + ttl, zones, overflow }; EOF $t->try_run('no js_shared_dict_zone'); -$t->plan(56); +$t->plan(63); ############################################################################### @@ -452,6 +473,42 @@ like(http_get('/keys?dict=waka'), qr/FOO\,FOO2\,FOO3/, 'waka keys'); } +# ttl() tests + +http_get('/clear?dict=waka'); + +http_get('/set?dict=waka&key=TTL1&value=1&timeout=30000'); +my $ttl_resp = get_ttl('/ttl?dict=waka&key=TTL1'); +ok($ttl_resp >= 25000 && $ttl_resp <= 30000, 'ttl for 30s entry in range'); + +like(http_get('/ttl?dict=waka&key=NOKEY'), qr/undefined/, + 'ttl for missing key'); + +like(http_get('/ttl?dict=no_timeout&key=x&err=1'), qr/TypeError/, + 'ttl on dict without timeout'); + +# per-entry timeout overrides directive default (waka: timeout=1000s) + +http_get('/add?dict=waka&key=TTL_ADD&value=1&timeout=30000'); +my $ttl_add = get_ttl('/ttl?dict=waka&key=TTL_ADD'); +ok($ttl_add >= 25000 && $ttl_add <= 30000, + 'add per-entry timeout overrides directive default'); + +http_get('/set?dict=waka&key=TTL_DEF&value=1'); +my $ttl_def = get_ttl('/ttl?dict=waka&key=TTL_DEF'); +ok($ttl_def >= 900000 && $ttl_def <= 1000000, + 'set without timeout uses directive default'); + +http_get('/incr?dict=waka&key=TTL_INCR&by=1&timeout=30000'); +my $ttl_incr = get_ttl('/ttl?dict=waka&key=TTL_INCR'); +ok($ttl_incr >= 25000 && $ttl_incr <= 30000, + 'incr per-entry timeout overrides directive default'); + +http_get('/incr?dict=waka&key=TTL_INCR_DEF&by=1'); +my $ttl_incr_def = get_ttl('/ttl?dict=waka&key=TTL_INCR_DEF'); +ok($ttl_incr_def >= 900000 && $ttl_incr_def <= 1000000, + 'incr without timeout uses directive default'); + like(http_get('/pop?dict=bar&key=FOO'), qr/zzz/, 'pop bar.FOO'); like(http_get('/pop?dict=bar&key=FOO'), qr/undefined/, 'pop deleted bar.FOO'); http_get('/set?dict=foo&key=BAR&value=xxx'); @@ -477,6 +534,12 @@ unlike($t->read_file('error.log'), qr/no memory in js shared zone "foo"/, ############################################################################### +sub get_ttl { + my ($uri) = @_; + my $resp = http_get($uri); + ($resp =~ /\x0d\x0a\x0d\x0a(\d+)/) ? $1 : -1; +} + sub has_version { my $need = shift;