}
} else {
- timeout = dict->timeout;
+ timeout = 0;
}
rc = ngx_js_dict_incr(vm, shm_zone->data, &key, delta, init, &value,
if (node == NULL) {
njs_value_number_set(init, njs_value_number(init)
+ njs_value_number(delta));
- if (ngx_js_dict_add(vm, dict, key, init, timeout, now) != NGX_OK) {
+ if (ngx_js_dict_add(vm, dict, key, init,
+ timeout ? timeout : dict->timeout, now)
+ != NGX_OK)
+ {
ngx_rwlock_unlock(&dict->sh->rwlock);
return NGX_ERROR;
}
node->value.number += njs_value_number(delta);
*value = node->value.number;
- if (dict->timeout) {
+ if (dict->timeout && timeout) {
ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire);
node->expire.key = now + timeout;
ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
}
} else {
- timeout = dict->timeout;
+ timeout = 0;
}
key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]);
if (node == NULL) {
value = JS_NewFloat64(cx, init + delta);
- if (ngx_qjs_dict_add(cx, dict, key, value, timeout, now) != NGX_OK) {
+ if (ngx_qjs_dict_add(cx, dict, key, value,
+ timeout ? timeout : dict->timeout, now)
+ != NGX_OK)
+ {
ngx_rwlock_unlock(&dict->sh->rwlock);
JS_FreeValue(cx, value);
return ngx_qjs_throw_shared_memory_error(cx);
node->value.number += delta;
value = JS_NewFloat64(cx, node->value.number);
- if (dict->timeout) {
+ if (dict->timeout && timeout) {
ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire);
node->expire.key = now + timeout;
ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
listen 127.0.0.1:8080;
server_name localhost;
+ location /add {
+ js_content test.add;
+ }
+
location /get {
js_content test.get;
}
location /set {
js_content test.set;
}
+
+ location /ttl {
+ js_content test.ttl;
+ }
}
}
return v;
}
+ function add(r) {
+ var dict = ngx.shared[r.args.dict];
+ var value = convertToValue(dict, r.args.value);
+
+ if (r.args.timeout) {
+ var timeout = Number(r.args.timeout);
+ r.return(200, dict.add(r.args.key, value, timeout));
+
+ } else {
+ r.return(200, dict.add(r.args.key, value));
+ }
+ }
+
function get(r) {
var dict = ngx.shared[r.args.dict];
var val = dict.get(r.args.key);
}
}
- export default { get, incr, set };
+ function ttl(r) {
+ var dict = ngx.shared[r.args.dict];
+ var val = dict.ttl(r.args.key);
+ r.return(200, val === undefined ? 'undefined' : val);
+ }
+
+ export default { add, get, incr, set, ttl };
EOF
$t->try_run('js_shared_dict_zone state with timeout no support on 32-bit')
- ->plan(13);
+ ->plan(18);
###############################################################################
is($waka_state->{foo}->{value}, '43', 'get waka.foo from state');
+# incr() without timeout preserves existing TTL
+
+http_get('/set?dict=waka&key=prs&value=100&timeout=30000');
+
+my $ttl_before = get_ttl('/ttl?dict=waka&key=prs');
+ok($ttl_before >= 25000 && $ttl_before <= 30000,
+ 'incr preserve: initial ttl in 30s range');
+
+http_get('/incr?dict=waka&key=prs&by=-10');
+
+my $ttl_after = get_ttl('/ttl?dict=waka&key=prs');
+ok($ttl_after >= 20000 && $ttl_after <= $ttl_before,
+ 'incr preserve: ttl not reset after incr without timeout');
+
+# incr() with explicit timeout updates TTL
+
+http_get('/incr?dict=waka&key=prs&by=5&timeout=60000');
+
+my $ttl_explicit = get_ttl('/ttl?dict=waka&key=prs');
+ok($ttl_explicit >= 55000 && $ttl_explicit <= 60000,
+ 'incr explicit: ttl updated to 60s range');
+
+like(http_get('/get?dict=waka&key=prs'), qr/^95$/m,
+ 'incr preserve: value correct after operations');
+
+# add() per-entry timeout overrides directive default (waka: timeout=1000s)
+
+http_get('/add?dict=waka&key=add_ttl&value=77&timeout=30000');
+
+my $ttl_add = get_ttl('/ttl?dict=waka&key=add_ttl');
+ok($ttl_add >= 25000 && $ttl_add <= 30000,
+ 'add per-entry timeout overrides directive default');
+
like(http_get('/get?dict=exp&key=past'), qr/undefined/,
'expired entry cleaned on load');
###############################################################################
+sub get_ttl {
+ my ($uri) = @_;
+ my $resp = http_get($uri);
+ ($resp =~ /\x0d\x0a\x0d\x0a(\d+)/) ? $1 : -1;
+}
+
sub time_ms {
return time() * 1000;
}