[njs] Interactive shell: immediate events support.
Dmitry Volyntsev
xeioex at nginx.com
Fri Dec 28 17:34:27 UTC 2018
details: https://hg.nginx.org/njs/rev/c4ec36309dda
branches:
changeset: 706:c4ec36309dda
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Fri Dec 28 19:36:24 2018 +0300
description:
Interactive shell: immediate events support.
This closes #66 issue on Github.
diffstat:
njs/njs.c | 2 +-
njs/njs_shell.c | 331 ++++++++++++++++++++++++++++++++++++------
njs/test/njs_expect_test.exp | 61 +++++++
3 files changed, 341 insertions(+), 53 deletions(-)
diffs (622 lines):
diff -r 2866557dc7a0 -r c4ec36309dda njs/njs.c
--- a/njs/njs.c Fri Dec 28 15:39:59 2018 +0300
+++ b/njs/njs.c Fri Dec 28 19:36:24 2018 +0300
@@ -645,7 +645,7 @@ njs_vm_handle_events(njs_vm_t *vm)
ev = nxt_queue_link_data(link, njs_event_t, link);
if (ev->once) {
- njs_del_event(vm, ev, NJS_EVENT_DELETE);
+ njs_del_event(vm, ev, NJS_EVENT_RELEASE | NJS_EVENT_DELETE);
} else {
ev->posted = 0;
diff -r 2866557dc7a0 -r c4ec36309dda njs/njs_shell.c
--- a/njs/njs_shell.c Fri Dec 28 15:39:59 2018 +0300
+++ b/njs/njs_shell.c Fri Dec 28 19:36:24 2018 +0300
@@ -20,13 +20,6 @@
#include <readline.h>
-typedef enum {
- NJS_COMPLETION_VAR = 0,
- NJS_COMPLETION_SUFFIX,
- NJS_COMPLETION_GLOBAL
-} njs_completion_phase_t;
-
-
typedef struct {
char *file;
nxt_int_t version;
@@ -40,22 +33,45 @@ typedef struct {
typedef struct {
size_t index;
size_t length;
- njs_vm_t *vm;
nxt_array_t *completions;
nxt_array_t *suffix_completions;
nxt_lvlhsh_each_t lhe;
- njs_completion_phase_t phase;
+
+ enum {
+ NJS_COMPLETION_VAR = 0,
+ NJS_COMPLETION_SUFFIX,
+ NJS_COMPLETION_GLOBAL
+ } phase;
} njs_completion_t;
+typedef struct {
+ njs_vm_event_t vm_event;
+ nxt_queue_link_t link;
+} njs_ev_t;
+
+
+typedef struct {
+ njs_vm_t *vm;
+
+ nxt_lvlhsh_t events; /* njs_ev_t * */
+ nxt_queue_t posted_events;
+
+ uint64_t time;
+
+ njs_completion_t completion;
+} njs_console_t;
+
+
static nxt_int_t njs_get_options(njs_opts_t *opts, int argc, char **argv);
-static nxt_int_t njs_externals_init(njs_vm_t *vm);
+static nxt_int_t njs_console_init(njs_vm_t *vm, njs_console_t *console);
+static nxt_int_t njs_externals_init(njs_vm_t *vm, njs_console_t *console);
static nxt_int_t njs_interactive_shell(njs_opts_t *opts,
njs_vm_opt_t *vm_options);
static nxt_int_t njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options);
-static nxt_int_t njs_process_script(njs_vm_t *vm, njs_opts_t *opts,
+static nxt_int_t njs_process_script(njs_console_t *console, njs_opts_t *opts,
const nxt_str_t *script);
-static nxt_int_t njs_editline_init(njs_vm_t *vm);
+static nxt_int_t njs_editline_init(void);
static char **njs_completion_handler(const char *text, int start, int end);
static char *njs_completion_generator(const char *text, int state);
@@ -70,6 +86,15 @@ static njs_ret_t njs_ext_console_time(nj
static njs_ret_t njs_ext_console_time_end(njs_vm_t *vm, njs_value_t *args,
nxt_uint_t nargs, njs_index_t unused);
+static njs_host_event_t njs_console_set_timer(njs_external_ptr_t external,
+ uint64_t delay, njs_vm_event_t vm_event);
+static void njs_console_clear_timer(njs_external_ptr_t external,
+ njs_host_event_t event);
+
+static nxt_int_t lvlhsh_key_test(nxt_lvlhsh_query_t *lhq, void *data);
+static void *lvlhsh_pool_alloc(void *pool, size_t size, nxt_uint_t nalloc);
+static void lvlhsh_pool_free(void *pool, void *p, size_t size);
+
static njs_external_t njs_ext_console[] = {
@@ -150,10 +175,22 @@ static njs_external_t njs_externals[] =
};
-static njs_completion_t njs_completion;
+static const nxt_lvlhsh_proto_t lvlhsh_proto nxt_aligned(64) = {
+ NXT_LVLHSH_LARGE_SLAB,
+ 0,
+ lvlhsh_key_test,
+ lvlhsh_pool_alloc,
+ lvlhsh_pool_free,
+};
-static uint64_t njs_console_time = UINT64_MAX;
+static njs_vm_ops_t njs_console_ops = {
+ njs_console_set_timer,
+ njs_console_clear_timer
+};
+
+
+static njs_console_t njs_console;
int
@@ -182,6 +219,8 @@ main(int argc, char **argv)
vm_options.accumulative = opts.interactive;
vm_options.backtrace = 1;
vm_options.sandbox = opts.sandbox;
+ vm_options.ops = &njs_console_ops;
+ vm_options.external = &njs_console;
if (opts.interactive) {
ret = njs_interactive_shell(&opts, &vm_options);
@@ -259,7 +298,26 @@ njs_get_options(njs_opts_t *opts, int ar
static nxt_int_t
-njs_externals_init(njs_vm_t *vm)
+njs_console_init(njs_vm_t *vm, njs_console_t *console)
+{
+ console->vm = vm;
+
+ nxt_lvlhsh_init(&console->events);
+ nxt_queue_init(&console->posted_events);
+
+ console->time = UINT64_MAX;
+
+ console->completion.completions = njs_vm_completions(vm, NULL);
+ if (console->completion.completions == NULL) {
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_externals_init(njs_vm_t *vm, njs_console_t *console)
{
nxt_uint_t ret;
njs_value_t *value;
@@ -279,7 +337,7 @@ njs_externals_init(njs_vm_t *vm)
return NXT_ERROR;
}
- ret = njs_vm_external_create(vm, value, proto, NULL);
+ ret = njs_vm_external_create(vm, value, proto, console);
if (ret != NXT_OK) {
return NXT_ERROR;
}
@@ -289,6 +347,11 @@ njs_externals_init(njs_vm_t *vm)
return NXT_ERROR;
}
+ ret = njs_console_init(vm, console);
+ if (ret != NXT_OK) {
+ return NXT_ERROR;
+ }
+
return NXT_OK;
}
@@ -299,22 +362,22 @@ njs_interactive_shell(njs_opts_t *opts,
njs_vm_t *vm;
nxt_str_t line;
+ if (njs_editline_init() != NXT_OK) {
+ fprintf(stderr, "failed to init completions\n");
+ return NXT_ERROR;
+ }
+
vm = njs_vm_create(vm_options);
if (vm == NULL) {
fprintf(stderr, "failed to create vm\n");
return NXT_ERROR;
}
- if (njs_externals_init(vm) != NXT_OK) {
+ if (njs_externals_init(vm, vm_options->external) != NXT_OK) {
fprintf(stderr, "failed to add external protos\n");
return NXT_ERROR;
}
- if (njs_editline_init(vm) != NXT_OK) {
- fprintf(stderr, "failed to init completions\n");
- return NXT_ERROR;
- }
-
if (!opts->quiet) {
printf("interactive njs %s\n\n", NJS_VERSION);
@@ -335,7 +398,7 @@ njs_interactive_shell(njs_opts_t *opts,
add_history((char *) line.start);
- njs_process_script(vm, opts, &line);
+ njs_process_script(vm_options->external, opts, &line);
/* editline allocs a new buffer every time. */
free(line.start);
@@ -439,14 +502,14 @@ njs_process_file(njs_opts_t *opts, njs_v
goto done;
}
- ret = njs_externals_init(vm);
+ ret = njs_externals_init(vm, vm_options->external);
if (ret != NXT_OK) {
fprintf(stderr, "failed to add external protos\n");
ret = NXT_ERROR;
goto done;
}
- ret = njs_process_script(vm, opts, &script);
+ ret = njs_process_script(vm_options->external, opts, &script);
if (ret != NXT_OK) {
ret = NXT_ERROR;
goto done;
@@ -490,11 +553,43 @@ njs_output(njs_vm_t *vm, njs_opts_t *opt
static nxt_int_t
-njs_process_script(njs_vm_t *vm, njs_opts_t *opts, const nxt_str_t *script)
+njs_process_events(njs_console_t *console, njs_opts_t *opts)
+{
+ njs_ev_t *ev;
+ nxt_queue_t *events;
+ nxt_queue_link_t *link;
+
+ events = &console->posted_events;
+
+ for ( ;; ) {
+ link = nxt_queue_first(events);
+
+ if (link == nxt_queue_tail(events)) {
+ break;
+ }
+
+ ev = nxt_queue_link_data(link, njs_ev_t, link);
+
+ nxt_queue_remove(&ev->link);
+ ev->link.prev = NULL;
+ ev->link.next = NULL;
+
+ njs_vm_post_event(console->vm, ev->vm_event, NULL, 0);
+ }
+
+ return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_process_script(njs_console_t *console, njs_opts_t *opts,
+ const nxt_str_t *script)
{
u_char *start;
+ njs_vm_t *vm;
nxt_int_t ret;
+ vm = console->vm;
start = script->start;
ret = njs_vm_compile(vm, &start, start + script->length);
@@ -510,14 +605,31 @@ njs_process_script(njs_vm_t *vm, njs_opt
njs_output(vm, opts, ret);
- if (ret == NJS_OK) {
- while (njs_vm_posted(vm)) {
- ret = njs_vm_run(vm);
+ for ( ;; ) {
+ if (!njs_vm_pending(vm)) {
+ break;
+ }
+
+ ret = njs_process_events(console, opts);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ fprintf(stderr, "njs_process_events() failed\n");
+ ret = NJS_ERROR;
+ break;
+ }
- if (ret == NJS_ERROR) {
- njs_output(vm, opts, ret);
- return ret;
- }
+ if (njs_vm_waiting(vm) && !njs_vm_posted(vm)) {
+ /*TODO: async events. */
+
+ fprintf(stderr, "njs_process_script(): "
+ "async events unsupported\n");
+ ret = NJS_ERROR;
+ break;
+ }
+
+ ret = njs_vm_run(vm);
+
+ if (ret == NJS_ERROR) {
+ njs_output(vm, opts, ret);
}
}
@@ -526,7 +638,7 @@ njs_process_script(njs_vm_t *vm, njs_opt
static nxt_int_t
-njs_editline_init(njs_vm_t *vm)
+njs_editline_init(void)
{
rl_completion_append_character = '\0';
rl_attempted_completion_function = njs_completion_handler;
@@ -534,13 +646,6 @@ njs_editline_init(njs_vm_t *vm)
setlocale(LC_ALL, "");
- njs_completion.completions = njs_vm_completions(vm, NULL);
- if (njs_completion.completions == NULL) {
- return NXT_ERROR;
- }
-
- njs_completion.vm = vm;
-
return NXT_OK;
}
@@ -571,10 +676,12 @@ njs_completion_generator(const char *tex
size_t len;
nxt_str_t expression, *suffix;
const char *p;
+ njs_vm_t *vm;
njs_variable_t *var;
njs_completion_t *cmpl;
- cmpl = &njs_completion;
+ vm = njs_console.vm;
+ cmpl = &njs_console.completion;
if (state == 0) {
cmpl->phase = 0;
@@ -589,12 +696,12 @@ next:
switch (cmpl->phase) {
case NJS_COMPLETION_VAR:
- if (cmpl->vm->parser == NULL) {
+ if (vm->parser == NULL) {
njs_next_phase(cmpl);
}
for ( ;; ) {
- var = nxt_lvlhsh_each(&cmpl->vm->parser->scope->variables,
+ var = nxt_lvlhsh_each(&vm->parser->scope->variables,
&cmpl->lhe);
if (var == NULL) {
@@ -630,8 +737,7 @@ next:
expression.start = (u_char *) text;
expression.length = p - text;
- cmpl->suffix_completions = njs_vm_completions(cmpl->vm,
- &expression);
+ cmpl->suffix_completions = njs_vm_completions(vm, &expression);
if (cmpl->suffix_completions == NULL) {
njs_next_phase(cmpl);
}
@@ -797,14 +903,21 @@ static njs_ret_t
njs_ext_console_time(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
njs_index_t unused)
{
+ njs_console_t *console;
+
if (!njs_value_is_void(njs_arg(args, nargs, 1))) {
njs_vm_error(vm, "labels not implemented");
return NJS_ERROR;
}
- vm->retval = njs_value_void;
+ console = njs_vm_external(vm, njs_arg(args, nargs, 0));
+ if (nxt_slow_path(console == NULL)) {
+ return NJS_ERROR;
+ }
- njs_console_time = nxt_time();
+ console->time = nxt_time();
+
+ vm->retval = njs_value_void;
return NJS_OK;
}
@@ -814,7 +927,8 @@ static njs_ret_t
njs_ext_console_time_end(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
njs_index_t unused)
{
- uint64_t ns, ms;
+ uint64_t ns, ms;
+ njs_console_t *console;
ns = nxt_time();
@@ -823,16 +937,21 @@ njs_ext_console_time_end(njs_vm_t *vm, n
return NJS_ERROR;
}
- if (nxt_fast_path(njs_console_time != UINT64_MAX)) {
+ console = njs_vm_external(vm, njs_arg(args, nargs, 0));
+ if (nxt_slow_path(console == NULL)) {
+ return NJS_ERROR;
+ }
- ns = ns - njs_console_time;
+ if (nxt_fast_path(console->time != UINT64_MAX)) {
+
+ ns = ns - console->time;
ms = ns / 1000000;
ns = ns % 1000000;
printf("default: %" PRIu64 ".%06" PRIu64 "ms\n", ms, ns);
- njs_console_time = UINT64_MAX;
+ console->time = UINT64_MAX;
} else {
printf("Timer \"default\" doesn’t exist.\n");
@@ -842,3 +961,111 @@ njs_ext_console_time_end(njs_vm_t *vm, n
return NJS_OK;
}
+
+
+static njs_host_event_t
+njs_console_set_timer(njs_external_ptr_t external, uint64_t delay,
+ njs_vm_event_t vm_event)
+{
+ njs_ev_t *ev;
+ njs_vm_t *vm;
+ nxt_int_t ret;
+ njs_console_t *console;
+ nxt_lvlhsh_query_t lhq;
+
+ if (delay != 0) {
+ fprintf(stderr, "njs_console_set_timer(): async timers unsupported\n");
+ return NULL;
+ }
+
+ console = external;
+ vm = console->vm;
+
+ ev = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_ev_t));
+ if (nxt_slow_path(ev == NULL)) {
+ return NULL;
+ }
+
+ ev->vm_event = vm_event;
+
+ lhq.key.start = (u_char *) &ev->vm_event;
+ lhq.key.length = sizeof(njs_vm_event_t);
+ lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+
+ lhq.replace = 0;
+ lhq.value = ev;
+ lhq.proto = &lvlhsh_proto;
+ lhq.pool = vm->mem_cache_pool;
+
+ ret = nxt_lvlhsh_insert(&console->events, &lhq);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NULL;
+ }
+
+ nxt_queue_insert_tail(&console->posted_events, &ev->link);
+
+ return (njs_host_event_t) ev;
+}
+
+
+static void
+njs_console_clear_timer(njs_external_ptr_t external, njs_host_event_t event)
+{
+ njs_vm_t *vm;
+ njs_ev_t *ev;
+ nxt_int_t ret;
+ njs_console_t *console;
+ nxt_lvlhsh_query_t lhq;
+
+ ev = event;
+ console = external;
+ vm = console->vm;
+
+ lhq.key.start = (u_char *) &ev->vm_event;
+ lhq.key.length = sizeof(njs_vm_event_t);
+ lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+
+ lhq.proto = &lvlhsh_proto;
+ lhq.pool = vm->mem_cache_pool;
+
+ if (ev->link.prev != NULL) {
+ nxt_queue_remove(&ev->link);
+ }
+
+ ret = nxt_lvlhsh_delete(&console->events, &lhq);
+ if (ret != NXT_OK) {
+ fprintf(stderr, "nxt_lvlhsh_delete() failed\n");
+ }
+
+ nxt_mem_cache_free(vm->mem_cache_pool, ev);
+}
+
+
+static nxt_int_t
+lvlhsh_key_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+ njs_ev_t *ev;
+
+ ev = data;
+
+ if (memcmp(&ev->vm_event, lhq->key.start, sizeof(njs_vm_event_t)) == 0) {
+ return NXT_OK;
+ }
+
+ return NXT_DECLINED;
+}
+
+
+static void *
+lvlhsh_pool_alloc(void *pool, size_t size, nxt_uint_t nalloc)
+{
+ return nxt_mem_cache_align(pool, size, size);
+}
+
+
+static void
+lvlhsh_pool_free(void *pool, void *p, size_t size)
+{
+ nxt_mem_cache_free(pool, p);
+}
+
diff -r 2866557dc7a0 -r c4ec36309dda njs/test/njs_expect_test.exp
--- a/njs/test/njs_expect_test.exp Fri Dec 28 15:39:59 2018 +0300
+++ b/njs/test/njs_expect_test.exp Fri Dec 28 19:36:24 2018 +0300
@@ -292,6 +292,67 @@ njs_test {
"'й'"}
}
+# Immediate events
+
+njs_test {
+ {"var t = setImmediate(console.log, 'a', 'aa')\r\n"
+ "undefined\r\n'a' 'aa'"}
+}
+
+njs_test {
+ {"var a = 1 + 1; setTimeout(function (x) {a = x}, 0, 'a'); a\r\n"
+ "2"}
+ {"a\r\n"
+ "a\r\n'a'"}
+}
+
+njs_test {
+ {"setTimeout(function () {}, 1, 'a')\r\n"
+ "njs_console_set_timer(): async timers unsupported"}
+}
+
+njs_test {
+ {"var a = 1 + 1; setTimeout(function (x) { setTimeout(function (y) {a = y}, 0, x)}, 0, 'a'); a\r\n"
+ "2"}
+ {"a\r\n"
+ "a\r\n'a'"}
+}
+
+njs_test {
+ {"var a = 1 + 1; setImmediate(function (x) { setImmediate(function (y) {a = y}, x)}, 'a'); a\r\n"
+ "2"}
+ {"a\r\n"
+ "a\r\n'a'"}
+}
+
+njs_test {
+ {"var i = 0; (function x() { if (i < 10) setImmediate(x); i++; })()\r\n"
+ "undefined"}
+ {"i\r\n"
+ "i\r\n11"}
+}
+
+njs_test {
+ {"var a = 0, t = setImmediate(function() {a = 1}); clearTimeout(t)\r\n"
+ "undefined"}
+ {"a\r\n"
+ "a\r\n0"}
+}
+
+njs_test {
+ {"var i = 0; (function x() { if (i < 3) setImmediate(x); i++; throw 'Oops';})()\r\n"
+ "Oops"}
+ {"i\r\n"
+ "i\r\n4"}
+}
+
+njs_test {
+ {"var i = 0, queue = []; (function x() { if (i < 5) setImmediate(x); queue.push(i++); })()\r\n"
+ "undefined"}
+ {"queue.toString()\r\n"
+ "queue.toString()\r\n'0,1,2,3,4,5'"}
+}
+
# require('fs')
set file [open njs_test_file w]
More information about the nginx-devel
mailing list