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);
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);
}
},
+ {
+ .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"),
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),
};
}
+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,
}
+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)
{
js_content test.size;
}
+ location /ttl {
+ js_content test.ttl;
+ }
+
location /zones {
js_content test.zones;
}
}
+ 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());
}
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);
###############################################################################
}
+# 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');
###############################################################################
+sub get_ttl {
+ my ($uri) = @_;
+ my $resp = http_get($uri);
+ ($resp =~ /\x0d\x0a\x0d\x0a(\d+)/) ? $1 : -1;
+}
+
sub has_version {
my $need = shift;