[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