[njs] Moving out setTimeout() and clearTimeout() from njs core.

Dmitry Volyntsev xeioex at nginx.com
Tue Nov 21 17:04:30 UTC 2023


details:   https://hg.nginx.org/njs/rev/dffdf7c50dfc
branches:  
changeset: 2238:dffdf7c50dfc
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Tue Nov 21 09:00:52 2023 -0800
description:
Moving out setTimeout() and clearTimeout() from njs core.

This functions are not part of the ECMAScript and should be
implemented by host environment.

diffstat:

 auto/sources                 |    1 -
 external/njs_shell.c         |  314 ++++++++++++++++++++++++++----------------
 nginx/ngx_http_js_module.c   |  123 +++++-----------
 nginx/ngx_js.c               |  273 +++++++++++++++++++++++++++++++++++++-
 nginx/ngx_js.h               |   46 ++++++-
 nginx/ngx_stream_js_module.c |  120 ++++-----------
 src/njs.h                    |    2 -
 src/njs_builtin.c            |    6 -
 src/njs_main.h               |    1 -
 src/test/njs_unit_test.c     |   24 ---
 test/shell_test.exp          |    2 +-
 11 files changed, 580 insertions(+), 332 deletions(-)

diffs (truncated from 1505 to 1000 lines):

diff -r 896e7e271382 -r dffdf7c50dfc auto/sources
--- a/auto/sources	Tue Nov 21 08:57:09 2023 -0800
+++ b/auto/sources	Tue Nov 21 09:00:52 2023 -0800
@@ -33,7 +33,6 @@ NJS_LIB_SRCS=" \
    src/njs_scope.c \
    src/njs_generator.c \
    src/njs_disassembler.c \
-   src/njs_timer.c \
    src/njs_module.c \
    src/njs_event.c \
    src/njs_extern.c \
diff -r 896e7e271382 -r dffdf7c50dfc external/njs_shell.c
--- a/external/njs_shell.c	Tue Nov 21 08:57:09 2023 -0800
+++ b/external/njs_shell.c	Tue Nov 21 09:00:52 2023 -0800
@@ -11,8 +11,6 @@
 #include <njs_arr.h>
 #include <njs_queue.h>
 #include <njs_rbtree.h>
-#include <njs_flathsh.h>
-#include <njs_djb_hash.h>
 
 #if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE)
 
@@ -70,7 +68,12 @@ typedef struct {
 
 
 typedef struct {
-    njs_vm_event_t          vm_event;
+    NJS_RBTREE_NODE         (node);
+    njs_function_t          *function;
+    njs_value_t             *args;
+    njs_uint_t              nargs;
+    uint32_t                id;
+
     njs_queue_link_t        link;
 } njs_ev_t;
 
@@ -85,7 +88,8 @@ typedef struct {
 typedef struct {
     njs_vm_t                *vm;
 
-    njs_lvlhsh_t            events;  /* njs_ev_t * */
+    uint32_t                event_id;
+    njs_rbtree_t            events;  /* njs_ev_t * */
     njs_queue_t             posted_events;
 
     njs_queue_t             labels;
@@ -120,6 +124,12 @@ static char *njs_completion_generator(co
 
 #endif
 
+static njs_int_t njs_set_timeout(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_set_immediate(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_clear_timeout(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
 static njs_int_t njs_ext_console_log(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t magic, njs_value_t *retval);
 static njs_int_t njs_ext_console_time(njs_vm_t *vm, njs_value_t *args,
@@ -127,17 +137,11 @@ static njs_int_t njs_ext_console_time(nj
 static njs_int_t njs_ext_console_time_end(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
 
-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 void njs_console_log(njs_vm_t *vm, njs_external_ptr_t external,
     njs_log_level_t level, const u_char *start, size_t length);
 
-static njs_int_t lvlhsh_key_test(njs_lvlhsh_query_t *lhq, void *data);
-static void *lvlhsh_pool_alloc(void *pool, size_t size);
-static void lvlhsh_pool_free(void *pool, void *p, size_t size);
+static intptr_t njs_event_rbtree_compare(njs_rbtree_node_t *node1,
+    njs_rbtree_node_t *node2);
 
 njs_int_t njs_array_buffer_detach(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
@@ -264,17 +268,7 @@ static njs_external_t  njs_ext_262[] = {
 };
 
 
-static const njs_lvlhsh_proto_t  lvlhsh_proto  njs_aligned(64) = {
-    NJS_LVLHSH_LARGE_SLAB,
-    lvlhsh_key_test,
-    lvlhsh_pool_alloc,
-    lvlhsh_pool_free,
-};
-
-
 static njs_vm_ops_t njs_console_ops = {
-    njs_console_set_timer,
-    njs_console_clear_timer,
     NULL,
     njs_console_log,
 };
@@ -635,7 +629,8 @@ njs_console_init(njs_vm_t *vm, njs_conso
 {
     console->vm = vm;
 
-    njs_lvlhsh_init(&console->events);
+    console->event_id = 0;
+    njs_rbtree_init(&console->events, njs_event_rbtree_compare);
     njs_queue_init(&console->posted_events);
     njs_queue_init(&console->labels);
 
@@ -649,6 +644,24 @@ njs_console_init(njs_vm_t *vm, njs_conso
 
 
 static njs_int_t
+njs_function_bind(njs_vm_t *vm, const njs_str_t *name,
+    njs_function_native_t native, njs_bool_t ctor)
+{
+    njs_function_t      *f;
+    njs_opaque_value_t   value;
+
+    f = njs_vm_function_alloc(vm, native, 1, ctor);
+    if (f == NULL) {
+        return NJS_ERROR;
+    }
+
+    njs_value_function_set(njs_value_arg(&value), f);
+
+    return njs_vm_bind(vm, name, njs_value_arg(&value), 1);
+}
+
+
+static njs_int_t
 njs_externals_init(njs_vm_t *vm)
 {
     njs_int_t           ret, proto_id;
@@ -659,6 +672,9 @@ njs_externals_init(njs_vm_t *vm)
     static const njs_str_t  dollar_262 = njs_str("$262");
     static const njs_str_t  print_name = njs_str("print");
     static const njs_str_t  console_log = njs_str("console.log");
+    static const njs_str_t  set_timeout = njs_str("setTimeout");
+    static const njs_str_t  set_immediate = njs_str("setImmediate");
+    static const njs_str_t  clear_timeout = njs_str("clearTimeout");
 
     console = njs_vm_options(vm)->external;
 
@@ -690,8 +706,18 @@ njs_externals_init(njs_vm_t *vm)
         return NJS_ERROR;
     }
 
-    ret = njs_console_init(vm, console);
-    if (njs_slow_path(ret != NJS_OK)) {
+    ret = njs_function_bind(vm, &set_timeout, njs_set_timeout, 0);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    ret = njs_function_bind(vm, &set_immediate, njs_set_immediate, 0);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    ret = njs_function_bind(vm, &clear_timeout, njs_clear_timeout, 0);
+    if (ret != NJS_OK) {
         return NJS_ERROR;
     }
 
@@ -712,6 +738,11 @@ njs_externals_init(njs_vm_t *vm)
         return NJS_ERROR;
     }
 
+    ret = njs_console_init(vm, console);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
     return NJS_OK;
 }
 
@@ -829,10 +860,13 @@ njs_console_output(njs_vm_t *vm, njs_val
 static njs_int_t
 njs_process_events(void *runtime)
 {
-    njs_ev_t          *ev;
-    njs_queue_t       *events;
-    njs_console_t     *console;
-    njs_queue_link_t  *link;
+    njs_ev_t            *ev;
+    njs_vm_t            *vm;
+    njs_int_t           ret;
+    njs_queue_t         *events;
+    njs_console_t       *console;
+    njs_queue_link_t    *link;
+    njs_opaque_value_t  retval;
 
     if (runtime == NULL) {
         njs_stderror("njs_process_events(): no runtime\n");
@@ -840,6 +874,7 @@ njs_process_events(void *runtime)
     }
 
     console = runtime;
+    vm = console->vm;
 
     events = &console->posted_events;
 
@@ -856,10 +891,24 @@ njs_process_events(void *runtime)
         ev->link.prev = NULL;
         ev->link.next = NULL;
 
-        njs_vm_post_event(console->vm, ev->vm_event, NULL, 0);
+        njs_rbtree_delete(&console->events, &ev->node);
+
+        ret = njs_vm_invoke(vm, ev->function, ev->args, ev->nargs,
+                            njs_value_arg(&retval));
+        if (ret == NJS_ERROR) {
+            njs_process_output(vm, njs_value_arg(&retval), ret);
+
+            if (!njs_vm_options(vm)->interactive) {
+                return NJS_ERROR;
+            }
+        }
     }
 
-    return NJS_OK;
+    if (!njs_rbtree_is_empty(&console->events)) {
+        return NJS_AGAIN;
+    }
+
+    return njs_vm_pending(vm) ? NJS_AGAIN: NJS_OK;
 }
 
 
@@ -1047,28 +1096,7 @@ njs_process_script(njs_vm_t *vm, void *r
     }
 
     for ( ;; ) {
-        if (!njs_vm_pending(vm) && !njs_vm_unhandled_rejection(vm)) {
-            ret = NJS_OK;
-            break;
-        }
-
-        ret = njs_process_events(runtime);
-        if (njs_slow_path(ret != NJS_OK)) {
-            njs_stderror("njs_process_events() failed\n");
-            ret = NJS_ERROR;
-            break;
-        }
-
-        if (njs_vm_waiting(vm) && !njs_vm_posted(vm)) {
-            /*TODO: async events. */
-
-            njs_stderror("njs_process_script(): async events unsupported\n");
-            ret = NJS_ERROR;
-            break;
-        }
-
         ret = njs_vm_run(vm);
-
         if (ret == NJS_ERROR) {
             njs_process_output(vm, njs_value_arg(&retval), ret);
 
@@ -1076,6 +1104,15 @@ njs_process_script(njs_vm_t *vm, void *r
                 return NJS_ERROR;
             }
         }
+
+        ret = njs_process_events(runtime);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            break;
+        }
+
+        if (ret == NJS_OK) {
+            break;
+        }
     }
 
     return ret;
@@ -1534,81 +1571,119 @@ njs_ext_console_time_end(njs_vm_t *vm, n
 }
 
 
-static njs_host_event_t
-njs_console_set_timer(njs_external_ptr_t external, uint64_t delay,
-    njs_vm_event_t vm_event)
+static njs_int_t
+njs_set_timer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_bool_t immediate, njs_value_t *retval)
 {
-    njs_ev_t            *ev;
-    njs_vm_t            *vm;
-    njs_int_t           ret;
-    njs_console_t       *console;
-    njs_lvlhsh_query_t  lhq;
+    njs_ev_t       *ev;
+    uint64_t       delay;
+    njs_uint_t     n;
+    njs_console_t  *console;
+
+    console = njs_vm_external_ptr(vm);
 
-    console = external;
-    vm = console->vm;
+    if (njs_slow_path(nargs < 2)) {
+        njs_vm_type_error(vm, "too few arguments");
+        return NJS_ERROR;
+    }
 
-    if (delay != 0) {
-        njs_vm_err(vm, "njs_console_set_timer(): async timers unsupported\n");
-        return NULL;
+    if (njs_slow_path(!njs_value_is_function(njs_argument(args, 1)))) {
+        njs_vm_type_error(vm, "first arg must be a function");
+        return NJS_ERROR;
     }
 
-    ev = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_ev_t));
-    if (njs_slow_path(ev == NULL)) {
-        return NULL;
+    delay = 0;
+
+    if (!immediate && nargs >= 3
+        && njs_value_is_number(njs_argument(args, 2)))
+    {
+        delay = njs_value_number(njs_argument(args, 2));
+    }
+
+    if (delay != 0) {
+        njs_vm_internal_error(vm, "njs_set_timer(): async timers unsupported");
+        return NJS_ERROR;
     }
 
-    ev->vm_event = vm_event;
+    n = immediate ? 2 : 3;
+    nargs = (nargs >= n) ? nargs - n : 0;
 
-    lhq.key.start = (u_char *) &ev->vm_event;
-    lhq.key.length = sizeof(njs_vm_event_t);
-    lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length);
+    ev = njs_mp_alloc(njs_vm_memory_pool(vm),
+                      sizeof(njs_ev_t) + sizeof(njs_opaque_value_t) * nargs);
+    if (njs_slow_path(ev == NULL)) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
+    }
 
-    lhq.replace = 0;
-    lhq.value = ev;
-    lhq.proto = &lvlhsh_proto;
-    lhq.pool = njs_vm_memory_pool(vm);
+    ev->function = njs_value_function(njs_argument(args, 1));
+    ev->nargs = nargs;
+    ev->args = (njs_value_t *) ((u_char *) ev + sizeof(njs_ev_t));
+    ev->id = console->event_id++;
 
-    ret = njs_lvlhsh_insert(&console->events, &lhq);
-    if (njs_slow_path(ret != NJS_OK)) {
-        return NULL;
+    if (ev->nargs != 0) {
+        memcpy(ev->args, njs_argument(args, n),
+               sizeof(njs_opaque_value_t) * ev->nargs);
     }
 
+    njs_rbtree_insert(&console->events, &ev->node);
+
     njs_queue_insert_tail(&console->posted_events, &ev->link);
 
-    return (njs_host_event_t) ev;
+    njs_value_number_set(retval, ev->id);
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_set_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    return njs_set_timer(vm, args, nargs, unused, 0, retval);
+}
+
+
+static njs_int_t
+njs_set_immediate(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
+{
+    return njs_set_timer(vm, args, nargs, unused, 1, retval);
 }
 
 
-static void
-njs_console_clear_timer(njs_external_ptr_t external, njs_host_event_t event)
+static njs_int_t
+njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_value_t *retval)
 {
-    njs_vm_t            *vm;
-    njs_ev_t            *ev;
-    njs_int_t           ret;
-    njs_console_t       *console;
-    njs_lvlhsh_query_t  lhq;
+    njs_ev_t           ev_lookup, *ev;
+    njs_console_t      *console;
+    njs_rbtree_node_t  *rb;
 
-    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 = njs_djb_hash(lhq.key.start, lhq.key.length);
-
-    lhq.proto = &lvlhsh_proto;
-    lhq.pool = njs_vm_memory_pool(vm);
-
-    if (ev->link.prev != NULL) {
-        njs_queue_remove(&ev->link);
+    if (nargs < 2 || !njs_value_is_number(njs_argument(args, 1))) {
+        njs_value_undefined_set(retval);
+        return NJS_OK;
     }
 
-    ret = njs_lvlhsh_delete(&console->events, &lhq);
-    if (ret != NJS_OK) {
-        njs_vm_err(vm, "njs_lvlhsh_delete() failed\n");
+    console = njs_vm_external_ptr(vm);
+
+    ev_lookup.id = njs_value_number(njs_argument(args, 1));
+
+    rb = njs_rbtree_find(&console->events, &ev_lookup.node);
+    if (njs_slow_path(rb == NULL)) {
+        njs_vm_internal_error(vm, "failed to find timer");
+        return NJS_ERROR;
     }
 
-    njs_mp_free(njs_vm_memory_pool(vm), ev);
+    njs_rbtree_delete(&console->events, (njs_rbtree_part_t *) rb);
+
+    ev = (njs_ev_t *) rb;
+    njs_queue_remove(&ev->link);
+    ev->link.prev = NULL;
+    ev->link.next = NULL;
+
+    njs_value_undefined_set(retval);
+
+    return NJS_OK;
 }
 
 
@@ -1630,30 +1705,21 @@ njs_console_log(njs_vm_t *vm, njs_extern
 }
 
 
-static njs_int_t
-lvlhsh_key_test(njs_lvlhsh_query_t *lhq, void *data)
+static intptr_t
+njs_event_rbtree_compare(njs_rbtree_node_t *node1, njs_rbtree_node_t *node2)
 {
-    njs_ev_t  *ev;
+    njs_ev_t  *ev1, *ev2;
 
-    ev = data;
+    ev1 = (njs_ev_t *) node1;
+    ev2 = (njs_ev_t *) node2;
 
-    if (memcmp(&ev->vm_event, lhq->key.start, sizeof(njs_vm_event_t)) == 0) {
-        return NJS_OK;
+    if (ev1->id < ev2->id) {
+        return -1;
     }
 
-    return NJS_DECLINED;
-}
-
+    if (ev1->id > ev2->id) {
+        return 1;
+    }
 
-static void *
-lvlhsh_pool_alloc(void *pool, size_t size)
-{
-    return njs_mp_align(pool, NJS_MAX_ALIGNMENT, size);
+    return 0;
 }
-
-
-static void
-lvlhsh_pool_free(void *pool, void *p, size_t size)
-{
-    njs_mp_free(pool, p);
-}
diff -r 896e7e271382 -r dffdf7c50dfc nginx/ngx_http_js_module.c
--- a/nginx/ngx_http_js_module.c	Tue Nov 21 08:57:09 2023 -0800
+++ b/nginx/ngx_http_js_module.c	Tue Nov 21 09:00:52 2023 -0800
@@ -49,7 +49,7 @@ typedef struct {
 
 
 typedef struct {
-    njs_vm_t              *vm;
+    NGX_JS_COMMON_CTX;
     ngx_log_t             *log;
     ngx_uint_t             done;
     ngx_int_t              status;
@@ -78,14 +78,6 @@ typedef struct {
 
 
 typedef struct {
-    ngx_http_request_t    *request;
-    njs_vm_event_t         vm_event;
-    void                  *unused;
-    ngx_int_t              ident;
-} ngx_http_js_event_t;
-
-
-typedef struct {
     njs_str_t              name;
 #if defined(nginx_version) && (nginx_version >= 1023000)
     unsigned               flags;
@@ -264,11 +256,6 @@ static njs_int_t ngx_http_js_location(nj
     unsigned flags, njs_str_t *name, njs_value_t *setval,
     njs_value_t *retval);
 
-static njs_host_event_t ngx_http_js_set_timer(njs_external_ptr_t external,
-    uint64_t delay, njs_vm_event_t vm_event);
-static void ngx_http_js_clear_timer(njs_external_ptr_t external,
-    njs_host_event_t event);
-static void ngx_http_js_timer_handler(ngx_event_t *ev);
 static ngx_pool_t *ngx_http_js_pool(njs_vm_t *vm, ngx_http_request_t *r);
 static ngx_resolver_t *ngx_http_js_resolver(njs_vm_t *vm,
     ngx_http_request_t *r);
@@ -281,6 +268,8 @@ static size_t ngx_http_js_max_response_b
     ngx_http_request_t *r);
 static void ngx_http_js_handle_vm_event(ngx_http_request_t *r,
     njs_vm_event_t vm_event, njs_value_t *args, njs_uint_t nargs);
+static void ngx_http_js_event_finalize(ngx_http_request_t *r, njs_int_t rc);
+static ngx_js_ctx_t *ngx_http_js_ctx(njs_vm_t *vm, ngx_http_request_t *r);
 
 static void ngx_http_js_periodic_handler(ngx_event_t *ev);
 static void ngx_http_js_periodic_write_event_handler(ngx_http_request_t *r);
@@ -853,8 +842,6 @@ static njs_external_t  ngx_http_js_ext_p
 
 
 static njs_vm_ops_t ngx_http_js_ops = {
-    ngx_http_js_set_timer,
-    ngx_http_js_clear_timer,
     NULL,
     ngx_js_logger,
 };
@@ -872,6 +859,8 @@ static uintptr_t ngx_http_js_uptr[] = {
     (uintptr_t) ngx_http_js_buffer_size,
     (uintptr_t) ngx_http_js_max_response_buffer_size,
     (uintptr_t) 0 /* main_conf ptr */,
+    (uintptr_t) ngx_http_js_event_finalize,
+    (uintptr_t) ngx_http_js_ctx,
 };
 
 
@@ -991,7 +980,7 @@ ngx_http_js_content_write_event_handler(
 
     ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
 
-    if (!njs_vm_pending(ctx->vm)) {
+    if (!ngx_vm_pending(ctx)) {
         ngx_http_js_content_finalize(r, ctx);
         return;
     }
@@ -1087,7 +1076,7 @@ ngx_http_js_header_filter(ngx_http_reque
     ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
 
     ctx->filter = 1;
-    pending = njs_vm_pending(ctx->vm);
+    pending = ngx_vm_pending(ctx);
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "http js header call \"%V\"", &jlcf->header_filter);
@@ -1190,7 +1179,7 @@ ngx_http_js_body_filter(ngx_http_request
                 return ret;
             }
 
-            pending = njs_vm_pending(ctx->vm);
+            pending = ngx_vm_pending(ctx);
 
             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                            "http js body call \"%V\"", &jlcf->body_filter);
@@ -1269,7 +1258,7 @@ ngx_http_js_variable_set(ngx_http_reques
 
     ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
 
-    pending = njs_vm_pending(ctx->vm);
+    pending = ngx_vm_pending(ctx);
 
     rc = ngx_js_invoke(ctx->vm, fname, r->connection->log, &ctx->request, 1,
                        &ctx->retval);
@@ -1352,6 +1341,8 @@ ngx_http_js_init_vm(ngx_http_request_t *
             return NGX_ERROR;
         }
 
+        ngx_js_ctx_init((ngx_js_ctx_t *) ctx);
+
         njs_value_invalid_set(njs_value_arg(&ctx->retval));
 
         ngx_http_set_ctx(r, ctx, ngx_http_js_module);
@@ -1424,14 +1415,14 @@ ngx_http_js_cleanup_ctx(void *data)
 {
     ngx_http_js_ctx_t *ctx = data;
 
-    if (njs_vm_pending(ctx->vm)) {
+    if (ngx_vm_pending(ctx)) {
         ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "pending events");
     }
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, "http js vm destroy: %p",
                    ctx->vm);
 
-    njs_vm_destroy(ctx->vm);
+    ngx_js_ctx_destroy((ngx_js_ctx_t *) ctx);
 }
 
 
@@ -4280,7 +4271,7 @@ ngx_http_js_periodic_write_event_handler
 
     ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
 
-    if (!njs_vm_pending(ctx->vm)) {
+    if (!ngx_vm_pending(ctx)) {
         ngx_http_js_periodic_finalize(r, NGX_OK);
         return;
     }
@@ -4317,9 +4308,9 @@ ngx_http_js_periodic_finalize(ngx_http_r
     ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "http js periodic finalize: \"%V\" rc: %i c: %i pending: %i",
                    &ctx->periodic->method, rc, r->count,
-                   njs_vm_pending(ctx->vm));
-
-    if (r->count > 1 || (rc == NGX_OK && njs_vm_pending(ctx->vm))) {
+                   ngx_vm_pending(ctx));
+
+    if (r->count > 1 || (rc == NGX_OK && ngx_vm_pending(ctx))) {
         return;
     }
 
@@ -4386,65 +4377,6 @@ ngx_http_js_periodic_init(ngx_js_periodi
 }
 
 
-static njs_host_event_t
-ngx_http_js_set_timer(njs_external_ptr_t external, uint64_t delay,
-    njs_vm_event_t vm_event)
-{
-    ngx_event_t          *ev;
-    ngx_http_request_t   *r;
-    ngx_http_js_event_t  *js_event;
-
-    r = (ngx_http_request_t *) external;
-
-    ev = ngx_pcalloc(r->pool, sizeof(ngx_event_t));
-    if (ev == NULL) {
-        return NULL;
-    }
-
-    js_event = ngx_palloc(r->pool, sizeof(ngx_http_js_event_t));
-    if (js_event == NULL) {
-        return NULL;
-    }
-
-    js_event->request = r;
-    js_event->vm_event = vm_event;
-    js_event->ident = r->connection->fd;
-
-    ev->data = js_event;
-    ev->log = r->connection->log;
-    ev->handler = ngx_http_js_timer_handler;
-
-    ngx_add_timer(ev, delay);
-
-    return ev;
-}
-
-
-static void
-ngx_http_js_clear_timer(njs_external_ptr_t external, njs_host_event_t event)
-{
-    ngx_event_t  *ev = event;
-
-    if (ev->timer_set) {
-        ngx_del_timer(ev);
-    }
-}
-
-
-static void
-ngx_http_js_timer_handler(ngx_event_t *ev)
-{
-    ngx_http_request_t   *r;
-    ngx_http_js_event_t  *js_event;
-
-    js_event = (ngx_http_js_event_t *) ev->data;
-
-    r = js_event->request;
-
-    ngx_http_js_handle_vm_event(r, js_event->vm_event, NULL, 0);
-}
-
-
 static ngx_pool_t *
 ngx_http_js_pool(njs_vm_t *vm, ngx_http_request_t *r)
 {
@@ -4530,7 +4462,19 @@ ngx_http_js_handle_vm_event(ngx_http_req
 
         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                       "js exception: %V", &exception);
-
+    }
+
+    ngx_http_js_event_finalize(r, rc);
+}
+
+
+static void
+ngx_http_js_event_finalize(ngx_http_request_t *r, njs_int_t rc)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http js event finalize rc: %i", (ngx_int_t) rc);
+
+    if (rc == NJS_ERROR) {
         if (r->health_check) {
             ngx_http_js_periodic_finalize(r, NGX_ERROR);
             return;
@@ -4548,6 +4492,13 @@ ngx_http_js_handle_vm_event(ngx_http_req
 }
 
 
+static ngx_js_ctx_t *
+ngx_http_js_ctx(njs_vm_t *vm, ngx_http_request_t *r)
+{
+    return ngx_http_get_module_ctx(r, ngx_http_js_module);
+}
+
+
 static njs_int_t
 ngx_js_http_init(njs_vm_t *vm)
 {
diff -r 896e7e271382 -r dffdf7c50dfc nginx/ngx_js.c
--- a/nginx/ngx_js.c	Tue Nov 21 08:57:09 2023 -0800
+++ b/nginx/ngx_js.c	Tue Nov 21 09:00:52 2023 -0800
@@ -43,6 +43,12 @@ static njs_int_t ngx_js_ext_console_time
     njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
 static njs_int_t ngx_js_ext_console_time_end(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_set_timeout(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_set_immediate(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
+static njs_int_t njs_clear_timeout(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
 static void ngx_js_cleanup_vm(void *data);
 
 static njs_int_t ngx_js_core_init(njs_vm_t *vm);
@@ -344,6 +350,7 @@ ngx_js_invoke(njs_vm_t *vm, ngx_str_t *f
     njs_int_t        ret;
     njs_str_t        name;
     ngx_str_t        exception;
+    ngx_js_ctx_t    *ctx;
     njs_function_t  *func;
 
     name.start = fname->data;
@@ -377,7 +384,13 @@ ngx_js_invoke(njs_vm_t *vm, ngx_str_t *f
         return NGX_ERROR;
     }
 
-    return (ret == NJS_AGAIN) ? NGX_AGAIN : NGX_OK;
+    if (ret == NJS_AGAIN) {
+        return NGX_AGAIN;
+    }
+
+    ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm));
+
+    return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN;
 }
 
 
@@ -431,12 +444,88 @@ ngx_js_string(njs_vm_t *vm, njs_value_t 
 
 
 static njs_int_t
+njs_function_bind(njs_vm_t *vm, const njs_str_t *name,
+    njs_function_native_t native, njs_bool_t ctor)
+{
+    njs_function_t      *f;
+    njs_opaque_value_t   value;
+
+    f = njs_vm_function_alloc(vm, native, 1, ctor);
+    if (f == NULL) {
+        return NJS_ERROR;
+    }
+
+    njs_value_function_set(njs_value_arg(&value), f);
+
+    return njs_vm_bind(vm, name, njs_value_arg(&value), 1);
+}
+
+
+
+static intptr_t
+ngx_js_event_rbtree_compare(njs_rbtree_node_t *node1, njs_rbtree_node_t *node2)
+{
+    ngx_js_event_t  *ev1, *ev2;
+
+    ev1 = (ngx_js_event_t *) ((u_char *) node1
+                              - offsetof(ngx_js_event_t, node));
+    ev2 = (ngx_js_event_t *) ((u_char *) node2
+                              - offsetof(ngx_js_event_t, node));
+
+    if (ev1->fd < ev2->fd) {
+        return -1;
+    }
+
+    if (ev1->fd > ev2->fd) {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+void
+ngx_js_ctx_init(ngx_js_ctx_t *ctx)
+{
+    ctx->event_id = 0;
+    njs_rbtree_init(&ctx->waiting_events, ngx_js_event_rbtree_compare);
+}
+
+
+void
+ngx_js_ctx_destroy(ngx_js_ctx_t *ctx)
+{
+    ngx_js_event_t     *event;
+    njs_rbtree_node_t  *node;
+
+    node = njs_rbtree_min(&ctx->waiting_events);
+
+    while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) {
+        event = (ngx_js_event_t *) ((u_char *) node
+                                    - offsetof(ngx_js_event_t, node));
+
+        if (event->destructor != NULL) {
+            event->destructor(njs_vm_external_ptr(event->vm), event);
+        }
+
+        node = njs_rbtree_node_successor(&ctx->waiting_events, node);
+    }
+
+    njs_vm_destroy(ctx->vm);
+}
+
+
+static njs_int_t
 ngx_js_core_init(njs_vm_t *vm)
 {
     njs_int_t           ret, proto_id;
     njs_str_t           name;
     njs_opaque_value_t  value;
 
+    static const njs_str_t  set_timeout = njs_str("setTimeout");
+    static const njs_str_t  set_immediate = njs_str("setImmediate");
+    static const njs_str_t  clear_timeout = njs_str("clearTimeout");
+
     proto_id = njs_vm_external_prototype(vm, ngx_js_ext_core,
                                          njs_nitems(ngx_js_ext_core));
     if (proto_id < 0) {
@@ -476,6 +565,21 @@ ngx_js_core_init(njs_vm_t *vm)
         return NJS_ERROR;
     }
 
+    ret = njs_function_bind(vm, &set_timeout, njs_set_timeout, 1);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    ret = njs_function_bind(vm, &set_immediate, njs_set_immediate, 1);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    ret = njs_function_bind(vm, &clear_timeout, njs_clear_timeout, 1);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
     return NJS_OK;
 }
 
@@ -859,6 +963,173 @@ not_found:
 }
 
 
+static void
+ngx_js_timer_handler(ngx_event_t *ev)
+{
+    njs_vm_t            *vm;
+    njs_int_t            ret;
+    ngx_str_t            exception;
+    ngx_js_ctx_t        *ctx;
+    ngx_js_event_t      *event;
+    ngx_connection_t    *c;
+    njs_external_ptr_t   external;
+    njs_opaque_value_t   retval;
+
+    event = (ngx_js_event_t *) ((u_char *) ev - offsetof(ngx_js_event_t, ev));
+
+    vm = event->vm;
+
+    ret = njs_vm_invoke(vm, event->function, event->args, event->nargs,
+                        njs_value_arg(&retval));
+
+    external = njs_vm_external_ptr(vm);
+    ctx = ngx_external_ctx(vm, external);
+    njs_rbtree_delete(&ctx->waiting_events, &event->node);
+
+    if (ret == NJS_ERROR) {
+        ngx_js_exception(vm, &exception);
+
+        c = ngx_external_connection(vm, njs_vm_external_ptr(vm));
+
+        ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                      "js exception: %V", &exception);
+    }
+
+    ngx_external_event_finalize(vm)(external, ret);
+}
+
+
+static void
+ngx_js_clear_timer(njs_external_ptr_t external, ngx_js_event_t *event)
+{
+    if (event->ev.timer_set) {
+        ngx_del_timer(&event->ev);
+    }
+}
+
+
+static njs_int_t
+njs_set_timer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused, njs_bool_t immediate, njs_value_t *retval)
+{
+    uint64_t           delay;
+    njs_uint_t         n;
+    ngx_js_ctx_t      *ctx;
+    ngx_js_event_t    *event;
+    ngx_connection_t  *c;
+
+    if (njs_slow_path(nargs < 2)) {
+        njs_vm_type_error(vm, "too few arguments");
+        return NJS_ERROR;
+    }
+
+    if (njs_slow_path(!njs_value_is_function(njs_argument(args, 1)))) {
+        njs_vm_type_error(vm, "first arg must be a function");
+        return NJS_ERROR;
+    }
+
+    delay = 0;
+
+    if (!immediate && nargs >= 3
+        && njs_value_is_number(njs_argument(args, 2)))
+    {
+        delay = njs_value_number(njs_argument(args, 2));
+    }
+
+    n = immediate ? 2 : 3;
+    nargs = (nargs >= n) ? nargs - n : 0;
+
+    event = njs_mp_zalloc(njs_vm_memory_pool(vm),
+                          sizeof(ngx_js_event_t)
+                          + sizeof(njs_opaque_value_t) * nargs);
+    if (njs_slow_path(event == NULL)) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
+    }
+
+    event->vm = vm;
+    event->function = njs_value_function(njs_argument(args, 1));
+    event->nargs = nargs;
+    event->args = (njs_value_t *) ((u_char *) event + sizeof(ngx_js_event_t));
+    event->destructor = ngx_js_clear_timer;
+
+    ctx = ngx_external_ctx(vm, njs_vm_external_ptr(vm));
+    event->fd = ctx->event_id++;
+
+    c = ngx_external_connection(vm, njs_vm_external_ptr(vm));
+
+    event->ev.log = c->log;
+    event->ev.data = event;
+    event->ev.handler = ngx_js_timer_handler;
+
+    if (event->nargs != 0) {
+        memcpy(event->args, njs_argument(args, n),
+               sizeof(njs_opaque_value_t) * event->nargs);
+    }
+
+    njs_rbtree_insert(&ctx->waiting_events, &event->node);
+
+    ngx_add_timer(&event->ev, delay);


More information about the nginx-devel mailing list