Skip to content

Commit

Permalink
Modules: shared dictionary add, set, incr methods timeout argument.
Browse files Browse the repository at this point in the history
The optional timeout argument overrides the timeout specified with
the shared_dict_zone directive for the effected key and operation
only. The timeout is specified in milliseconds.

This is useful when the majority of keys are expected to require
unique timeouts.
  • Loading branch information
jo-carter authored and xeioex committed Jun 10, 2024
1 parent 5ab2598 commit d8fbff1
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 24 deletions.
85 changes: 67 additions & 18 deletions nginx/ngx_js_shared_dict.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,16 @@ static ngx_js_dict_node_t *ngx_js_dict_lookup(ngx_js_dict_t *dict,
#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);
njs_str_t *key, njs_value_t *value, ngx_msec_t timeout, 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);
njs_value_t *value, ngx_msec_t timeout, 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);
ngx_js_dict_node_t *node, njs_value_t *value, ngx_msec_t timeout,
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);
njs_value_t *delta, njs_value_t *init, double *value, ngx_msec_t timeout);
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,
Expand Down Expand Up @@ -726,7 +727,8 @@ njs_js_ext_shared_dict_incr(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
double value;
ngx_int_t rc;
njs_str_t key;
njs_value_t *delta, *init;
ngx_msec_t timeout;
njs_value_t *delta, *init, *timeo;
ngx_js_dict_t *dict;
ngx_shm_zone_t *shm_zone;
njs_opaque_value_t lvalue;
Expand Down Expand Up @@ -765,7 +767,30 @@ njs_js_ext_shared_dict_incr(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_value_number_set(init, 0);
}

rc = ngx_js_dict_incr(shm_zone->data, &key, delta, init, &value);
timeo = njs_arg(args, nargs, 4);
if (!njs_value_is_undefined(timeo)) {
if (!njs_value_is_number(timeo)) {
njs_vm_type_error(vm, "timeout is not a number");
return NJS_ERROR;
}

if (!dict->timeout) {
njs_vm_type_error(vm, "shared dict must be declared with timeout");
return NJS_ERROR;
}

timeout = (ngx_msec_t) njs_value_number(timeo);

if (timeout < 1) {
njs_vm_type_error(vm, "timeout must be greater than or equal to 1");
return NJS_ERROR;
}

} else {
timeout = dict->timeout;
}

rc = ngx_js_dict_incr(shm_zone->data, &key, delta, init, &value, timeout);
if (rc == NGX_ERROR) {
njs_vm_error(vm, "failed to increment value in shared dict");
return NJS_ERROR;
Expand Down Expand Up @@ -936,7 +961,8 @@ njs_js_ext_shared_dict_set(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
{
njs_str_t key;
ngx_int_t rc;
njs_value_t *value;
ngx_msec_t timeout;
njs_value_t *value, *timeo;
ngx_js_dict_t *dict;
ngx_shm_zone_t *shm_zone;

Expand Down Expand Up @@ -967,7 +993,30 @@ njs_js_ext_shared_dict_set(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
}
}

rc = ngx_js_dict_set(vm, shm_zone->data, &key, value, flags);
timeo = njs_arg(args, nargs, 3);
if (!njs_value_is_undefined(timeo)) {
if (!njs_value_is_number(timeo)) {
njs_vm_type_error(vm, "timeout is not a number");
return NJS_ERROR;
}

if (!dict->timeout) {
njs_vm_type_error(vm, "shared dict must be declared with timeout");
return NJS_ERROR;
}

timeout = (ngx_msec_t) njs_value_number(timeo);

if (timeout < 1) {
njs_vm_type_error(vm, "timeout must be greater than or equal to 1");
return NJS_ERROR;
}

} else {
timeout = dict->timeout;
}

rc = ngx_js_dict_set(vm, shm_zone->data, &key, value, timeout, flags);
if (rc == NGX_ERROR) {
return NJS_ERROR;
}
Expand Down Expand Up @@ -1118,7 +1167,7 @@ ngx_js_dict_node_free(ngx_js_dict_t *dict, ngx_js_dict_node_t *node)

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)
njs_value_t *value, ngx_msec_t timeout, unsigned flags)
{
ngx_msec_t now;
ngx_time_t *tp;
Expand All @@ -1137,7 +1186,7 @@ ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key,
return NGX_DECLINED;
}

if (ngx_js_dict_add(dict, key, value, now) != NGX_OK) {
if (ngx_js_dict_add(dict, key, value, timeout, now) != NGX_OK) {
goto memory_error;
}

Expand All @@ -1149,7 +1198,7 @@ ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key,
}
}

if (ngx_js_dict_update(dict, node, value, now) != NGX_OK) {
if (ngx_js_dict_update(dict, node, value, timeout, now) != NGX_OK) {
goto memory_error;
}
}
Expand All @@ -1170,7 +1219,7 @@ ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key,

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)
ngx_msec_t timeout, ngx_msec_t now)
{
size_t n;
uint32_t hash;
Expand Down Expand Up @@ -1214,7 +1263,7 @@ ngx_js_dict_add(ngx_js_dict_t *dict, njs_str_t *key, njs_value_t *value,
ngx_rbtree_insert(&dict->sh->rbtree, &node->sn.node);

if (dict->timeout) {
node->expire.key = now + dict->timeout;
node->expire.key = now + timeout;
ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
}

Expand All @@ -1224,7 +1273,7 @@ ngx_js_dict_add(ngx_js_dict_t *dict, njs_str_t *key, njs_value_t *value,

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)
njs_value_t *value, ngx_msec_t timeout, ngx_msec_t now)
{
u_char *p;
njs_str_t string;
Expand All @@ -1249,7 +1298,7 @@ ngx_js_dict_update(ngx_js_dict_t *dict, ngx_js_dict_node_t *node,

if (dict->timeout) {
ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire);
node->expire.key = now + dict->timeout;
node->expire.key = now + timeout;
ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
}

Expand Down Expand Up @@ -1306,7 +1355,7 @@ ngx_js_dict_delete(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key,

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)
njs_value_t *init, double *value, ngx_msec_t timeout)
{
ngx_msec_t now;
ngx_time_t *tp;
Expand All @@ -1322,7 +1371,7 @@ ngx_js_dict_incr(ngx_js_dict_t *dict, njs_str_t *key, njs_value_t *delta,
if (node == NULL) {
njs_value_number_set(init, njs_value_number(init)
+ njs_value_number(delta));
if (ngx_js_dict_add(dict, key, init, now) != NGX_OK) {
if (ngx_js_dict_add(dict, key, init, timeout, now) != NGX_OK) {
ngx_rwlock_unlock(&dict->sh->rwlock);
return NGX_ERROR;
}
Expand All @@ -1335,7 +1384,7 @@ ngx_js_dict_incr(ngx_js_dict_t *dict, njs_str_t *key, njs_value_t *delta,

if (dict->timeout) {
ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire);
node->expire.key = now + dict->timeout;
node->expire.key = now + timeout;
ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire);
}
}
Expand Down
55 changes: 49 additions & 6 deletions nginx/t/js_shared_dict.t
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ http {
js_shared_dict_zone zone=foo:32k timeout=2s evict;
js_shared_dict_zone zone=bar:64k type=string;
js_shared_dict_zone zone=waka:32k type=number;
js_shared_dict_zone zone=waka:32k timeout=1000s type=number;
js_shared_dict_zone zone=no_timeout:32k;
server {
Expand Down Expand Up @@ -146,7 +146,14 @@ $t->write_file('test.js', <<'EOF');
function add(r) {
var dict = ngx.shared[r.args.dict];
var value = convertToValue(dict, r.args.value);
r.return(200, dict.add(r.args.key, 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 capacity(r) {
Expand Down Expand Up @@ -200,8 +207,16 @@ $t->write_file('test.js', <<'EOF');
function incr(r) {
var dict = ngx.shared[r.args.dict];
var def = r.args.def ? parseInt(r.args.def) : 0;
var val = dict.incr(r.args.key, parseInt(r.args.by), def);
r.return(200, val);
if (r.args.timeout) {
var timeout = Number(r.args.timeout);
var val = dict.incr(r.args.key, parseInt(r.args.by), def, timeout);
r.return(200, val);
} else {
var val = dict.incr(r.args.key, parseInt(r.args.by), def);
r.return(200, val);
}
}
function keys(r) {
Expand Down Expand Up @@ -256,7 +271,14 @@ $t->write_file('test.js', <<'EOF');
function set(r) {
var dict = ngx.shared[r.args.dict];
var value = convertToValue(dict, r.args.value);
r.return(200, dict.set(r.args.key, value) === dict);
if (r.args.timeout) {
var timeout = Number(r.args.timeout);
r.return(200, dict.set(r.args.key, value, timeout) === dict);
} else {
r.return(200, dict.set(r.args.key, value) === dict);
}
}
function size(r) {
Expand All @@ -283,7 +305,7 @@ $t->write_file('test.js', <<'EOF');
set_clear, size, zones };
EOF

$t->try_run('no js_shared_dict_zone')->plan(44);
$t->try_run('no js_shared_dict_zone')->plan(51);

###############################################################################

Expand Down Expand Up @@ -350,6 +372,27 @@ like(http_get('/items?dict=waka'),

}

TODO: {
local $TODO = 'not yet' unless has_version('0.8.5');

http_get('/clear?dict=waka');
like(http_get('/set?dict=waka&key=BAR&value=1&timeout=1'), qr/true/,
'set waka.BAR');
like(http_get('/add?dict=waka&key=BAR2&value=1&timeout=1'), qr/true/,
'add waka.BAR2');
like(http_get('/incr?dict=waka&key=BAR3&by=42&timeout=1'), qr/42/,
'incr waka.BAR3');
like(http_get('/set?dict=waka&key=FOO&value=42&timeout=1000'), qr/true/,
'set waka.FOO');
like(http_get('/add?dict=waka&key=FOO2&value=42&timeout=1000'), qr/true/,
'add waka.FOO2');
like(http_get('/incr?dict=waka&key=FOO3&by=42&timeout=1000'), qr/42/,
'incr waka.FOO3');

like(http_get('/keys?dict=waka'), qr/\[FOO\,FOO2\,FOO3]/, 'waka keys');

}

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');
Expand Down

0 comments on commit d8fbff1

Please sign in to comment.