u_char *p;
njs_str_t string;
+ if (dict->timeout) {
+ /*
+ * Remove the node from the expire tree before allocating
+ * memory for the new value. This serves two purposes:
+ * it prevents ngx_js_dict_evict() from freeing this node
+ * during allocation, and it allows the node to be reinserted
+ * with the updated expiry time below.
+ */
+ ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire);
+ }
+
if (dict->type == NGX_JS_DICT_TYPE_STRING) {
njs_value_string_get(vm, value, &string);
p = ngx_js_dict_alloc(dict, string.length);
if (p == NULL) {
+ if (dict->timeout) {
+ ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
+ }
+
return NGX_ERROR;
}
}
if (dict->timeout) {
- ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire);
node->expire.key = now + timeout;
ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
}
u_char *p;
ngx_str_t string;
+ if (dict->timeout) {
+ /*
+ * Remove the node from the expire tree before allocating
+ * memory for the new value. This serves two purposes:
+ * it prevents ngx_js_dict_evict() from freeing this node
+ * during allocation, and it allows the node to be reinserted
+ * with the updated expiry time below.
+ */
+ ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire);
+ }
+
if (dict->type == NGX_JS_DICT_TYPE_STRING) {
string.data = (u_char *) JS_ToCStringLen(cx, &string.len, value);
if (string.data == NULL) {
+ if (dict->timeout) {
+ ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
+ }
+
return NGX_ERROR;
}
p = ngx_js_dict_alloc(dict, string.len);
if (p == NULL) {
JS_FreeCString(cx, (char *) string.data);
+
+ if (dict->timeout) {
+ ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
+ }
+
return NGX_ERROR;
}
} else {
if (JS_ToFloat64(cx, &node->value.number, value) < 0) {
+ if (dict->timeout) {
+ ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
+ }
+
return NGX_ERROR;
}
}
if (dict->timeout) {
- ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire);
node->expire.key = now + timeout;
ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
}
js_content test.name;
}
- location /evict_stress {
- js_content test.evict_stress;
- }
-
location /overflow {
js_content test.overflow;
}
r.return(200, dict.replace(r.args.key, value));
}
- function evict_stress(r) {
- var dict = ngx.shared.foo;
- dict.clear();
-
- var v = 'x'.repeat(1024);
- var n = 64;
- var failed = 0;
-
- for (var i = 0; i < n; i++) {
- try {
- dict.set('stress_' + i, v);
- } catch (e) {
- failed++;
- }
- }
-
- var last = dict.get('stress_' + (n - 1));
- var first = dict.get('stress_0');
-
- var last_s = last !== undefined ? 'exists' : 'missing';
- var first_s = first !== undefined ? 'exists' : 'missing';
-
- r.return(200, `failed:${failed},last:${last_s},first:${first_s}`);
- }
-
function overflow(r) {
var dict = ngx.shared.overflow;
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,
- ttl, zones, overflow };
+ export default { add, capacity, chain, clear, del, free_space, get,
+ has, incr, items, keys, name, njs: test_njs, pop,
+ replace, set, set_clear, size, ttl, zones,
+ overflow };
EOF
$t->try_run('no js_shared_dict_zone');
-$t->plan(63);
+$t->plan(59);
###############################################################################
}
-my $evict_resp = http_get('/evict_stress');
-like($evict_resp, qr/failed:0/, 'evict stress: all writes succeeded');
-like($evict_resp, qr/last:exists/, 'evict stress: last write exists');
-like($evict_resp, qr/first:missing/, 'evict stress: first write evicted');
-unlike($t->read_file('error.log'), qr/no memory in js shared zone "foo"/,
- 'evict stress: no shared zone foo errors in error log');
-
###############################################################################
sub get_ttl {
--- /dev/null
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for js_shared_dict_zone eviction.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http/)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ js_import test.js;
+
+ js_shared_dict_zone zone=foo:32k timeout=2s evict;
+ js_shared_dict_zone zone=stress:32k timeout=1000s evict;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location /stress {
+ js_content test.stress;
+ }
+
+ location /no_self_evict {
+ js_content test.no_self_evict;
+ }
+ }
+}
+
+EOF
+
+$t->write_file('test.js', <<'EOF');
+ function stress(r) {
+ var dict = ngx.shared.foo;
+ dict.clear();
+
+ var v = 'x'.repeat(1024);
+ var n = 64;
+ var failed = 0;
+
+ for (var i = 0; i < n; i++) {
+ try {
+ dict.set('stress_' + i, v);
+ } catch (e) {
+ failed++;
+ }
+ }
+
+ var last = dict.get('stress_' + (n - 1));
+ var first = dict.get('stress_0');
+
+ var last_s = last !== undefined ? 'exists' : 'missing';
+ var first_s = first !== undefined ? 'exists' : 'missing';
+
+ r.return(200, `failed:${failed},last:${last_s},first:${first_s}`);
+ }
+
+ function no_self_evict(r) {
+ var dict = ngx.shared.stress;
+ var v = 'x'.repeat(128);
+
+ dict.clear();
+ dict.set('target', v);
+
+ /* Count how many items can be added while 'target' is present. */
+
+ var elems = 0;
+ while (dict.has('target')) {
+ dict.set('fill_' + elems++, v);
+ }
+
+ dict.clear();
+ dict.set('target', v);
+
+ for (var i = 0; i < elems - 1; i++) {
+ dict.set('fill_' + i, v);
+ }
+
+ /* Check that 'target' is not evicted by the updates of itself. */
+
+ dict.set('target', 'y'.repeat(128));
+
+ r.return(200, 'FILLED:' + elems);
+ }
+
+ export default { stress, no_self_evict };
+EOF
+
+$t->try_run('no js_shared_dict_zone');
+
+$t->plan(6);
+
+###############################################################################
+
+my $evict_resp = http_get('/stress');
+like($evict_resp, qr/failed:0/, 'evict stress: all writes succeeded');
+like($evict_resp, qr/last:exists/, 'evict stress: last write exists');
+like($evict_resp, qr/first:missing/, 'evict stress: first write evicted');
+unlike($t->read_file('error.log'), qr/no memory in js shared zone "foo"/,
+ 'evict stress: no shared zone foo errors in error log');
+
+my $update_resp = http_get('/no_self_evict');
+like($update_resp, qr/FILLED:/, 'evict update: zone filled');
+unlike($t->read_file('error.log'), qr/is already free/,
+ 'evict update: no double-free in error log');
+
+###############################################################################