]> git.kaiwu.me - njs.git/commitdiff
setTimeout() and clearTimeout() methods.
authorDmitry Volyntsev <xeioex@nginx.com>
Wed, 21 Mar 2018 14:33:12 +0000 (17:33 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Wed, 21 Mar 2018 14:33:12 +0000 (17:33 +0300)
Public methods are introduced to create and post async events for
a VM instance.  njs_vm_add_event() creates an async event for the VM
to wait for.  njs_vm_post_event() notifies the VM that the event
occurred.  If async events were added njs_vm_run() returns NJS_AGAIN
until there are no remaining pending events.

17 files changed:
Makefile
nginx/ngx_http_js_module.c
nginx/ngx_stream_js_module.c
njs/njs_builtin.c
njs/njs_event.c [new file with mode: 0644]
njs/njs_event.h [new file with mode: 0644]
njs/njs_generator.c
njs/njs_lexer_keyword.c
njs/njs_parser.c
njs/njs_parser.h
njs/njs_time.c [new file with mode: 0644]
njs/njs_time.h [new file with mode: 0644]
njs/njs_vm.h
njs/njscript.c
njs/njscript.h
njs/test/njs_interactive_test.c
njs/test/njs_unit_test.c

index 21270546944605fa6a69fb89d55178a70c409f2d..23fbc33501fc72b683f4d6edfcd59afe853bf7a8 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,9 @@ $(NXT_BUILDDIR)/libnjs.a: \
        $(NXT_BUILDDIR)/njs_date.o \
        $(NXT_BUILDDIR)/njs_error.o \
        $(NXT_BUILDDIR)/njs_math.o \
+       $(NXT_BUILDDIR)/njs_time.o \
        $(NXT_BUILDDIR)/njs_module.o \
+       $(NXT_BUILDDIR)/njs_event.o \
        $(NXT_BUILDDIR)/njs_fs.o \
        $(NXT_BUILDDIR)/njs_extern.o \
        $(NXT_BUILDDIR)/njs_variable.o \
@@ -56,7 +58,9 @@ $(NXT_BUILDDIR)/libnjs.a: \
                $(NXT_BUILDDIR)/njs_date.o \
                $(NXT_BUILDDIR)/njs_error.o \
                $(NXT_BUILDDIR)/njs_math.o \
+               $(NXT_BUILDDIR)/njs_time.o \
                $(NXT_BUILDDIR)/njs_module.o \
+               $(NXT_BUILDDIR)/njs_event.o \
                $(NXT_BUILDDIR)/njs_fs.o \
                $(NXT_BUILDDIR)/njs_extern.o \
                $(NXT_BUILDDIR)/njs_variable.o \
@@ -301,6 +305,18 @@ $(NXT_BUILDDIR)/njs_math.o: \
                -I$(NXT_LIB) -Injs \
                njs/njs_math.c
 
+$(NXT_BUILDDIR)/njs_time.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_object.h \
+       njs/njs_time.h \
+       njs/njs_time.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_time.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_time.c
+
 $(NXT_BUILDDIR)/njs_module.o: \
        $(NXT_BUILDDIR)/libnxt.a \
        njs/njscript.h \
@@ -312,6 +328,17 @@ $(NXT_BUILDDIR)/njs_module.o: \
                -I$(NXT_LIB) -Injs \
                njs/njs_module.c
 
+$(NXT_BUILDDIR)/njs_event.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_event.h \
+       njs/njs_event.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_event.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_event.c
+
 $(NXT_BUILDDIR)/njs_fs.o: \
        $(NXT_BUILDDIR)/libnxt.a \
        njs/njscript.h \
index 2a71c51b7c74badf772abcc0f54ec12f6ebbe58f..0e707682679c453ce6d7c6b62897c75f2ee4cd14 100644 (file)
@@ -29,6 +29,7 @@ typedef struct {
 
 typedef struct {
     njs_vm_t            *vm;
+    ngx_log_t           *log;
     njs_opaque_value_t   args[2];
 } ngx_http_js_ctx_t;
 
@@ -39,10 +40,21 @@ typedef struct {
 } ngx_http_js_table_entry_t;
 
 
-static ngx_int_t ngx_http_js_handler(ngx_http_request_t *r);
+typedef struct {
+    ngx_http_request_t  *request;
+    njs_vm_event_t       vm_event;
+    void                *unused;
+    ngx_int_t            ident;
+} ngx_http_js_event_t;
+
+
+static ngx_int_t ngx_http_js_content_handler(ngx_http_request_t *r);
+static void ngx_http_js_content_event_handler(ngx_http_request_t *r);
+static void ngx_http_js_content_write_event_handler(ngx_http_request_t *r);
 static ngx_int_t ngx_http_js_variable(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_http_js_init_vm(ngx_http_request_t *r);
+static void ngx_http_js_cleanup_ctx(void *data);
 static void ngx_http_js_cleanup_vm(void *data);
 
 static njs_ret_t ngx_http_js_ext_get_string(njs_vm_t *vm, njs_value_t *value,
@@ -94,6 +106,14 @@ static njs_ret_t ngx_http_js_ext_next_arg(njs_vm_t *vm, njs_value_t *value,
 static njs_ret_t ngx_http_js_ext_get_variable(njs_vm_t *vm, njs_value_t *value,
     void *obj, uintptr_t data);
 
+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 void ngx_http_js_handle_event(ngx_http_request_t *r,
+    njs_vm_event_t vm_event);
+
 static char *ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
 static char *ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
@@ -378,8 +398,33 @@ static njs_external_t  ngx_http_js_externals[] = {
 };
 
 
+static njs_vm_ops_t ngx_http_js_ops = {
+    ngx_http_js_set_timer,
+    ngx_http_js_clear_timer
+};
+
+
 static ngx_int_t
-ngx_http_js_handler(ngx_http_request_t *r)
+ngx_http_js_content_handler(ngx_http_request_t *r)
+{
+    ngx_int_t  rc;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http js content handler");
+
+    rc = ngx_http_read_client_request_body(r,
+                                           ngx_http_js_content_event_handler);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        return rc;
+    }
+
+    return NGX_DONE;
+}
+
+
+static void
+ngx_http_js_content_event_handler(ngx_http_request_t *r)
 {
     ngx_int_t                rc;
     nxt_str_t                name, exception;
@@ -387,10 +432,14 @@ ngx_http_js_handler(ngx_http_request_t *r)
     ngx_http_js_ctx_t       *ctx;
     ngx_http_js_loc_conf_t  *jlcf;
 
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http js content event handler");
+
     rc = ngx_http_js_init_vm(r);
 
     if (rc == NGX_ERROR || rc == NGX_DECLINED) {
-        return rc;
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
     }
 
     jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module);
@@ -407,7 +456,8 @@ ngx_http_js_handler(ngx_http_request_t *r)
     if (func == NULL) {
         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                        "js function \"%V\" not found", &jlcf->content);
-        return NGX_DECLINED;
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
     }
 
     if (njs_vm_call(ctx->vm, func, ctx->args, 2) != NJS_OK) {
@@ -416,10 +466,66 @@ ngx_http_js_handler(ngx_http_request_t *r)
         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                       "js exception: %*s", exception.length, exception.start);
 
-        return NGX_ERROR;
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
     }
 
-    return NGX_OK;
+    if (njs_vm_pending(ctx->vm)) {
+        r->write_event_handler = ngx_http_js_content_write_event_handler;
+        return;
+    }
+
+    ngx_http_finalize_request(r, NGX_OK);
+}
+
+
+static void
+ngx_http_js_content_write_event_handler(ngx_http_request_t *r)
+{
+    ngx_event_t               *wev;
+    ngx_connection_t          *c;
+    ngx_http_js_ctx_t         *ctx;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http js content write event handler");
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+    if (!njs_vm_pending(ctx->vm)) {
+        ngx_http_finalize_request(r, NGX_OK);
+        return;
+    }
+
+    c = r->connection;
+    wev = c->write;
+
+    if (wev->timedout) {
+        ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out");
+        ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    if (ngx_http_output_filter(r, NULL) == NGX_ERROR) {
+        ngx_http_finalize_request(r, NGX_ERROR);
+        return;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module);
+
+    if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
+        ngx_http_finalize_request(r, NGX_ERROR);
+        return;
+    }
+
+    if (!wev->delayed) {
+        if (wev->active && !wev->ready) {
+            ngx_add_timer(wev, clcf->send_timeout);
+
+        } else if (wev->timer_set) {
+            ngx_del_timer(wev);
+        }
+    }
 }
 
 
@@ -430,6 +536,7 @@ ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
     ngx_str_t *fname = (ngx_str_t *) data;
 
     ngx_int_t           rc;
+    nxt_int_t           pending;
     nxt_str_t           name, value, exception;
     njs_function_t     *func;
     ngx_http_js_ctx_t  *ctx;
@@ -461,6 +568,8 @@ ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
         return NGX_OK;
     }
 
+    pending = njs_vm_pending(ctx->vm);
+
     if (njs_vm_call(ctx->vm, func, ctx->args, 2) != NJS_OK) {
         njs_vm_retval_to_ext_string(ctx->vm, &exception);
 
@@ -475,6 +584,12 @@ ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
         return NGX_ERROR;
     }
 
+    if (!pending && njs_vm_pending(ctx->vm)) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "async operation inside \"%V\" variable handler", fname);
+        return NGX_ERROR;
+    }
+
     v->len = value.length;
     v->valid = 1;
     v->no_cacheable = 0;
@@ -489,6 +604,7 @@ static ngx_int_t
 ngx_http_js_init_vm(ngx_http_request_t *r)
 {
     nxt_int_t                rc;
+    nxt_str_t                exception;
     ngx_http_js_ctx_t       *ctx;
     ngx_pool_cleanup_t      *cln;
     ngx_http_js_loc_conf_t  *jlcf;
@@ -523,10 +639,17 @@ ngx_http_js_init_vm(ngx_http_request_t *r)
         return NGX_ERROR;
     }
 
-    cln->handler = ngx_http_js_cleanup_vm;
-    cln->data = ctx->vm;
+    ctx->log = r->connection->log;
+
+    cln->handler = ngx_http_js_cleanup_ctx;
+    cln->data = ctx;
+
+    if (njs_vm_run(ctx->vm) == NJS_ERROR) {
+        njs_vm_retval_to_ext_string(ctx->vm, &exception);
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "js exception: %*s", exception.length, exception.start);
 
-    if (njs_vm_run(ctx->vm) != NJS_OK) {
         return NGX_ERROR;
     }
 
@@ -544,6 +667,19 @@ ngx_http_js_init_vm(ngx_http_request_t *r)
 }
 
 
+static void
+ngx_http_js_cleanup_ctx(void *data)
+{
+    ngx_http_js_ctx_t *ctx = data;
+
+    if (njs_vm_pending(ctx->vm)) {
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "pending events");
+    }
+
+    njs_vm_destroy(ctx->vm);
+}
+
+
 static void
 ngx_http_js_cleanup_vm(void *data)
 {
@@ -1157,6 +1293,98 @@ ngx_http_js_ext_get_variable(njs_vm_t *vm, njs_value_t *value, void *obj,
 }
 
 
+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_connection_t     *c;
+    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;
+
+    c = r->connection;
+
+    ngx_http_js_handle_event(r, js_event->vm_event);
+
+    ngx_http_run_posted_requests(c);
+}
+
+
+static void
+ngx_http_js_handle_event(ngx_http_request_t *r, njs_vm_event_t vm_event)
+{
+    njs_ret_t           rc;
+    nxt_str_t           exception;
+    ngx_http_js_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+
+    njs_vm_post_event(ctx->vm, vm_event);
+
+    rc = njs_vm_run(ctx->vm);
+
+    if (rc == NJS_ERROR) {
+        njs_vm_retval_to_ext_string(ctx->vm, &exception);
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "js exception: %*s", exception.length, exception.start);
+
+        ngx_http_finalize_request(r, NGX_ERROR);
+    }
+
+    if (rc == NJS_OK) {
+        ngx_http_post_request(r, NULL);
+    }
+}
+
+
 static char *
 ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
@@ -1235,6 +1463,7 @@ ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
     ngx_memzero(&options, sizeof(njs_vm_opt_t));
 
     options.backtrace = 1;
+    options.ops = &ngx_http_js_ops;
 
     jlcf->vm = njs_vm_create(&options);
     if (jlcf->vm == NULL) {
@@ -1339,7 +1568,7 @@ ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
     jlcf->content = value[1];
 
     clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
-    clcf->handler = ngx_http_js_handler;
+    clcf->handler = ngx_http_js_content_handler;
 
     return NGX_CONF_OK;
 }
index f11e1459c064e53fd276beb340d240971b7ccc74..4890179c1cf1ceaa1298d675266370d61338b8c9 100644 (file)
@@ -30,6 +30,7 @@ typedef struct {
 
 typedef struct {
     njs_vm_t              *vm;
+    ngx_log_t             *log;
     njs_opaque_value_t     arg;
     ngx_buf_t             *buf;
     ngx_chain_t           *free;
@@ -49,6 +50,7 @@ static ngx_int_t ngx_stream_js_body_filter(ngx_stream_session_t *s,
 static ngx_int_t ngx_stream_js_variable(ngx_stream_session_t *s,
     ngx_stream_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_stream_js_init_vm(ngx_stream_session_t *s);
+static void ngx_stream_js_cleanup_ctx(void *data);
 static void ngx_stream_js_cleanup_vm(void *data);
 
 static njs_ret_t ngx_stream_js_ext_get_remote_address(njs_vm_t *vm,
@@ -526,6 +528,7 @@ ngx_stream_js_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v,
     ngx_str_t *fname = (ngx_str_t *) data;
 
     ngx_int_t             rc;
+    nxt_int_t             pending;
     nxt_str_t             name, value, exception;
     njs_function_t       *func;
     ngx_stream_js_ctx_t  *ctx;
@@ -557,6 +560,8 @@ ngx_stream_js_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v,
         return NGX_OK;
     }
 
+    pending = njs_vm_pending(ctx->vm);
+
     if (njs_vm_call(ctx->vm, func, &ctx->arg, 1) != NJS_OK) {
         njs_vm_retval_to_ext_string(ctx->vm, &exception);
 
@@ -571,6 +576,12 @@ ngx_stream_js_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v,
         return NGX_ERROR;
     }
 
+    if (!pending && njs_vm_pending(ctx->vm)) {
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "async operation inside \"%V\" variable handler", fname);
+        return NGX_ERROR;
+    }
+
     v->len = value.length;
     v->valid = 1;
     v->no_cacheable = 0;
@@ -585,6 +596,7 @@ static ngx_int_t
 ngx_stream_js_init_vm(ngx_stream_session_t *s)
 {
     nxt_int_t                  rc;
+    nxt_str_t                  exception;
     ngx_pool_cleanup_t        *cln;
     ngx_stream_js_ctx_t       *ctx;
     ngx_stream_js_srv_conf_t  *jscf;
@@ -619,10 +631,17 @@ ngx_stream_js_init_vm(ngx_stream_session_t *s)
         return NGX_ERROR;
     }
 
-    cln->handler = ngx_stream_js_cleanup_vm;
-    cln->data = ctx->vm;
+    ctx->log = s->connection->log;
+
+    cln->handler = ngx_stream_js_cleanup_ctx;
+    cln->data = ctx;
+
+    if (njs_vm_run(ctx->vm) == NJS_ERROR) {
+        njs_vm_retval_to_ext_string(ctx->vm, &exception);
+
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "js exception: %*s", exception.length, exception.start);
 
-    if (njs_vm_run(ctx->vm) != NJS_OK) {
         return NGX_ERROR;
     }
 
@@ -635,6 +654,19 @@ ngx_stream_js_init_vm(ngx_stream_session_t *s)
 }
 
 
+static void
+ngx_stream_js_cleanup_ctx(void *data)
+{
+    ngx_stream_js_ctx_t *ctx = data;
+
+    if (njs_vm_pending(ctx->vm)) {
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "pending events");
+    }
+
+    njs_vm_destroy(ctx->vm);
+}
+
+
 static void
 ngx_stream_js_cleanup_vm(void *data)
 {
index 01c948f3d52711a59457f02f945257624f9386f0..9c9a7bfb3318bcbad9b73584ed63053987bb1e77 100644 (file)
@@ -30,6 +30,7 @@
 #include <njs_date.h>
 #include <njs_error.h>
 #include <njs_math.h>
+#include <njs_time.h>
 #include <njs_module.h>
 #include <njs_fs.h>
 #include <string.h>
@@ -113,7 +114,9 @@ const njs_object_init_t    *njs_function_init[] = {
     &njs_encode_uri_component_function_init,
     &njs_decode_uri_function_init,
     &njs_decode_uri_component_function_init,
-    &njs_require_function_init
+    &njs_require_function_init,
+    &njs_set_timeout_function_init,
+    &njs_clear_timeout_function_init
 };
 
 
@@ -131,6 +134,9 @@ const njs_function_init_t  njs_native_functions[] = {
     { njs_string_decode_uri,           { NJS_SKIP_ARG, NJS_STRING_ARG } },
     { njs_string_decode_uri_component, { NJS_SKIP_ARG, NJS_STRING_ARG } },
     { njs_module_require,              { NJS_SKIP_ARG, NJS_STRING_ARG } },
+    { njs_set_timeout,
+      { NJS_SKIP_ARG, NJS_FUNCTION_ARG, NJS_NUMBER_ARG } },
+    { njs_clear_timeout,               { NJS_SKIP_ARG, NJS_NUMBER_ARG } },
 };
 
 
diff --git a/njs/njs_event.c b/njs/njs_event.c
new file mode 100644 (file)
index 0000000..2ac90d3
--- /dev/null
@@ -0,0 +1,121 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_string.h>
+#include <nxt_stub.h>
+#include <nxt_array.h>
+#include <nxt_djb_hash.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_random.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_number.h>
+#include <njs_string.h>
+#include <njs_object.h>
+#include <njs_array.h>
+#include <njs_function.h>
+#include <njs_error.h>
+#include <njs_event.h>
+#include <njs_time.h>
+#include <string.h>
+#include <stdio.h>
+
+
+static nxt_int_t njs_event_hash_test(nxt_lvlhsh_query_t *lhq, void *data);
+
+
+const nxt_lvlhsh_proto_t  njs_event_hash_proto
+    nxt_aligned(64) =
+{
+    NXT_LVLHSH_DEFAULT,
+    0,
+    njs_event_hash_test,
+    njs_lvlhsh_alloc,
+    njs_lvlhsh_free,
+};
+
+
+static nxt_int_t
+njs_event_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+    nxt_str_t    id;
+    njs_event_t  *event;
+
+    event = data;
+
+    njs_string_get(&event->id, &id);
+
+    if (nxt_strstr_eq(&lhq->key, &id)) {
+        return NXT_OK;
+    }
+
+    return NXT_DECLINED;
+}
+
+
+nxt_int_t
+njs_add_event(njs_vm_t *vm, njs_event_t *event)
+{
+    size_t              size;
+    nxt_int_t           ret;
+    nxt_lvlhsh_query_t  lhq;
+
+    size = snprintf((char *) njs_string_short_start(&event->id),
+                    NJS_STRING_SHORT, "%u", vm->event_id++);
+    njs_string_short_set(&event->id, size, size);
+
+    njs_string_get(&event->id, &lhq.key);
+    lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+    lhq.value = event;
+    lhq.proto = &njs_event_hash_proto;
+    lhq.pool = vm->mem_cache_pool;
+
+    ret = nxt_lvlhsh_insert(&vm->events_hash, &lhq);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        njs_exception_internal_error(vm, "Failed to add event with id: %s",
+                                     njs_string_short_start(&event->id));
+
+        njs_del_event(vm, event, NJS_EVENT_RELEASE | NJS_EVENT_DELETE);
+        return NJS_ERROR;
+    }
+
+    njs_value_number_set(&vm->retval, vm->event_id - 1);
+
+    return NJS_OK;
+}
+
+
+void
+njs_del_event(njs_vm_t *vm, njs_event_t *ev, nxt_uint_t action)
+{
+    nxt_lvlhsh_query_t  lhq;
+
+    if (action & NJS_EVENT_RELEASE) {
+        if (ev->destructor != NULL && ev->host_event != NULL) {
+            ev->destructor(vm->external, ev->host_event);
+        }
+
+        ev->host_event = NULL;
+    }
+
+    if (action & NJS_EVENT_DELETE) {
+        njs_string_get(&ev->id, &lhq.key);
+        lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+        lhq.proto = &njs_event_hash_proto;
+        lhq.pool = vm->mem_cache_pool;
+
+        if (ev->posted) {
+            ev->posted = 0;
+            nxt_queue_remove(&ev->link);
+        }
+
+        (void) nxt_lvlhsh_delete(&vm->events_hash, &lhq);
+    }
+}
diff --git a/njs/njs_event.h b/njs/njs_event.h
new file mode 100644 (file)
index 0000000..ebfd835
--- /dev/null
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_EVENT_H_INCLUDED_
+#define _NJS_EVENT_H_INCLUDED_
+
+
+#define NJS_EVENT_RELEASE      1
+#define NJS_EVENT_DELETE       2
+
+
+#define njs_is_pending_events(vm) (!nxt_lvlhsh_is_empty(&(vm)->events_hash))
+
+
+typedef struct {
+    njs_function_t        *function;
+    njs_opaque_value_t    *args;
+    nxt_uint_t            nargs;
+    njs_host_event_t      host_event;
+    njs_event_destructor  destructor;
+
+    njs_value_t           id;
+    nxt_queue_link_t      link;
+
+    unsigned              posted:1;
+} njs_event_t;
+
+
+nxt_int_t njs_add_event(njs_vm_t *vm, njs_event_t *event);
+void njs_del_event(njs_vm_t *vm, njs_event_t *event, nxt_uint_t action);
+
+
+extern const nxt_lvlhsh_proto_t  njs_event_hash_proto;
+
+
+#endif /* _NJS_EVENT_H_INCLUDED_ */
index 670580b372bd38253b7019cfbd52da38009bb3fd..57be2d50810e49edffa42a4c69f5dd1467a61a10 100644 (file)
@@ -320,6 +320,8 @@ njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
     case NJS_TOKEN_DECODE_URI:
     case NJS_TOKEN_DECODE_URI_COMPONENT:
     case NJS_TOKEN_REQUIRE:
+    case NJS_TOKEN_SET_TIMEOUT:
+    case NJS_TOKEN_CLEAR_TIMEOUT:
         return njs_generate_builtin_object(vm, parser, node);
 
     case NJS_TOKEN_FUNCTION:
index 88bb0f307c41ec029c347d7904096c3cd7a54dbf..872369555a279a9860ee812692de798f38fc78db 100644 (file)
@@ -102,6 +102,8 @@ static const njs_keyword_t  njs_keywords[] = {
     { nxt_string("decodeURI"),     NJS_TOKEN_DECODE_URI, 0 },
     { nxt_string("decodeURIComponent"),  NJS_TOKEN_DECODE_URI_COMPONENT, 0 },
     { nxt_string("require"),      NJS_TOKEN_REQUIRE, 0 },
+    { nxt_string("setTimeout"),   NJS_TOKEN_SET_TIMEOUT, 0 },
+    { nxt_string("clearTimeout"), NJS_TOKEN_CLEAR_TIMEOUT, 0 },
 
     /* Reserved words. */
 
index 5083a3146e74e2af7ba314f7e52eaed75cd09e2b..77cbccf709c633b9a8995f26f5224b3b7a2ce4a0 100644 (file)
@@ -2072,6 +2072,8 @@ njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token)
     case NJS_TOKEN_DECODE_URI:
     case NJS_TOKEN_DECODE_URI_COMPONENT:
     case NJS_TOKEN_REQUIRE:
+    case NJS_TOKEN_SET_TIMEOUT:
+    case NJS_TOKEN_CLEAR_TIMEOUT:
         return njs_parser_builtin_function(vm, parser, node);
 
     default:
index f9fa8f54cab5e514300f6a61e10c246b64a9d733..c2a33db4ccfdc96ef2ef0a9d2e37f25770f3f830 100644 (file)
@@ -199,6 +199,8 @@ typedef enum {
     NJS_TOKEN_DECODE_URI,
     NJS_TOKEN_DECODE_URI_COMPONENT,
     NJS_TOKEN_REQUIRE,
+    NJS_TOKEN_SET_TIMEOUT,
+    NJS_TOKEN_CLEAR_TIMEOUT,
 
     NJS_TOKEN_RESERVED,
 } njs_token_t;
diff --git a/njs/njs_time.c b/njs/njs_time.c
new file mode 100644 (file)
index 0000000..40afefe
--- /dev/null
@@ -0,0 +1,141 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_string.h>
+#include <nxt_stub.h>
+#include <nxt_array.h>
+#include <nxt_djb_hash.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_random.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_number.h>
+#include <njs_string.h>
+#include <njs_object.h>
+#include <njs_array.h>
+#include <njs_function.h>
+#include <njs_error.h>
+#include <njs_event.h>
+#include <njs_time.h>
+#include <string.h>
+#include <stdio.h>
+
+
+njs_ret_t
+njs_set_timeout(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
+    njs_index_t unused)
+{
+    uint64_t       delay;
+    njs_event_t   *event;
+    njs_vm_ops_t  *ops;
+
+    if (nxt_slow_path(nargs < 2)) {
+        njs_exception_type_error(vm, "too few arguments", NULL);
+        return NJS_ERROR;
+    }
+
+    if (nxt_slow_path(!njs_is_function(&args[1]))) {
+        njs_exception_type_error(vm, "first arg must be a function", NULL);
+        return NJS_ERROR;
+    }
+
+    ops = vm->ops;
+    if (nxt_slow_path(ops == NULL)) {
+        njs_exception_internal_error(vm, "not supported by host environment",
+                                     NULL);
+        return NJS_ERROR;
+    }
+
+    delay = 0;
+
+    if (nargs >= 3 && njs_is_number(&args[2])) {
+        delay = args[2].data.u.number;
+    }
+
+    event = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_event_t));
+    if (nxt_slow_path(event == NULL)) {
+        goto memory_error;
+    }
+
+    event->destructor = ops->clear_timer;
+    event->function = args[1].data.u.function;
+    event->nargs = (nargs >= 3) ? nargs - 3 : 0;
+    event->posted = 0;
+
+    if (event->nargs != 0) {
+        event->args = nxt_mem_cache_alloc(vm->mem_cache_pool,
+                                          sizeof(njs_value_t) * event->nargs);
+        if (nxt_slow_path(event->args == NULL)) {
+            goto memory_error;
+        }
+
+        memcpy(event->args, &args[3], sizeof(njs_value_t) * event->nargs);
+    }
+
+    event->host_event = ops->set_timer(vm->external, delay, event);
+    if (event->host_event == NULL) {
+        njs_exception_internal_error(vm, "set_timer() failed", NULL);
+        return NJS_ERROR;
+    }
+
+    return njs_add_event(vm, event);
+
+memory_error:
+
+    njs_exception_memory_error(vm);
+    return NJS_ERROR;
+}
+
+
+njs_ret_t
+njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
+    njs_index_t unused)
+{
+    u_char              buf[16];
+    njs_ret_t           ret;
+    njs_event_t         *event;
+    nxt_lvlhsh_query_t  lhq;
+
+    if (nxt_fast_path(nargs < 2) || !njs_is_number(&args[1])) {
+        vm->retval = njs_string_void;
+        return NJS_OK;
+    }
+
+    lhq.key.start = buf;
+    lhq.key.length = snprintf((char *) buf, sizeof(buf) - 1, "%u",
+                              (unsigned) args[1].data.u.number);
+    lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+    lhq.proto = &njs_event_hash_proto;
+    lhq.pool = vm->mem_cache_pool;
+
+    ret = nxt_lvlhsh_find(&vm->events_hash, &lhq);
+    if (ret == NXT_OK) {
+        event = lhq.value;
+        njs_del_event(vm, event, NJS_EVENT_RELEASE | NJS_EVENT_DELETE);
+    }
+
+    vm->retval = njs_string_void;
+
+    return NJS_OK;
+}
+
+
+const njs_object_init_t  njs_set_timeout_function_init = {
+    nxt_string("setTimeout"),
+    NULL,
+    0,
+};
+
+
+const njs_object_init_t  njs_clear_timeout_function_init = {
+    nxt_string("clearTimeout"),
+    NULL,
+    0,
+};
diff --git a/njs/njs_time.h b/njs/njs_time.h
new file mode 100644 (file)
index 0000000..86185be
--- /dev/null
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_TIMEOUT_H_INCLUDED_
+#define _NJS_TIMEOUT_H_INCLUDED_
+
+
+njs_ret_t njs_set_timeout(njs_vm_t *vm, njs_value_t *args,
+    nxt_uint_t nargs, njs_index_t unused);
+njs_ret_t njs_clear_timeout(njs_vm_t *vm, njs_value_t *args,
+    nxt_uint_t nargs, njs_index_t unused);
+
+
+extern const njs_object_init_t  njs_set_timeout_function_init;
+extern const njs_object_init_t  njs_clear_timeout_function_init;
+
+#endif /* _NJS_TIMEOUT_H_INCLUDED_ */
index 58c9b055979b8ba813623eef45631919101c8002..38415a6d8a9e3bd52dcba87235693844f2f7231f 100644 (file)
@@ -881,7 +881,9 @@ enum njs_function_e {
     NJS_FUNCTION_STRING_DECODE_URI,
     NJS_FUNCTION_STRING_DECODE_URI_COMPONENT,
     NJS_FUNCTION_REQUIRE,
-#define NJS_FUNCTION_MAX       (NJS_FUNCTION_REQUIRE + 1)
+    NJS_FUNCTION_SET_TIMEOUT,
+    NJS_FUNCTION_CLEAR_TIMEOUT,
+#define NJS_FUNCTION_MAX       (NJS_FUNCTION_CLEAR_TIMEOUT + 1)
 };
 
 
@@ -960,12 +962,12 @@ struct njs_vm_s {
 
     njs_value_t              *scopes[NJS_SCOPES];
 
-    void                     *external;
+    njs_external_ptr_t       external;
 
     njs_native_frame_t       *top_frame;
     njs_frame_t              *active_frame;
 
-    nxt_array_t              *external_objects; /* of void * */
+    nxt_array_t              *external_objects; /* of njs_external_ptr_t */
 
     nxt_lvlhsh_t             externals_hash;
     nxt_lvlhsh_t             external_prototypes_hash;
@@ -974,6 +976,12 @@ struct njs_vm_s {
     nxt_lvlhsh_t             values_hash;
     nxt_lvlhsh_t             modules_hash;
 
+    uint32_t                 event_id;
+    nxt_lvlhsh_t             events_hash;
+    nxt_queue_t              posted_events;
+
+    njs_vm_ops_t             *ops;
+
     /*
      * The prototypes and constructors arrays must be together because
      * they are copied from njs_vm_shared_t by single memcpy()
index b86874c52c200438cd6c203ac5008dcd81cebab9..9d0980fdc3205bf4886ab1d966de432b16cc5048 100644 (file)
 #include <njs_string.h>
 #include <njs_object.h>
 #include <njs_function.h>
+#include <njs_error.h>
 #include <njs_variable.h>
 #include <njs_parser.h>
 #include <njs_regexp.h>
+#include <njs_event.h>
+#include <njs_time.h>
 #include <string.h>
 
 
 static nxt_int_t njs_vm_init(njs_vm_t *vm);
+static nxt_int_t njs_vm_handle_events(njs_vm_t *vm);
 
 
 static void *
@@ -176,6 +180,8 @@ njs_vm_create(njs_vm_opt_t *options)
         nxt_lvlhsh_init(&vm->externals_hash);
         nxt_lvlhsh_init(&vm->external_prototypes_hash);
 
+        vm->ops = options->ops;
+
         vm->trace.level = NXT_LEVEL_TRACE;
         vm->trace.size = 2048;
         vm->trace.handler = njs_parser_trace_handler;
@@ -212,6 +218,23 @@ njs_vm_create(njs_vm_opt_t *options)
 void
 njs_vm_destroy(njs_vm_t *vm)
 {
+    njs_event_t        *event;
+    nxt_lvlhsh_each_t  lhe;
+
+    if (njs_is_pending_events(vm)) {
+        nxt_lvlhsh_each_init(&lhe, &njs_event_hash_proto);
+
+        for ( ;; ) {
+            event = nxt_lvlhsh_each(&vm->events_hash, &lhe);
+
+            if (event == NULL) {
+                break;
+            }
+
+            njs_del_event(vm, event, NJS_EVENT_RELEASE);
+        }
+    }
+
     nxt_mem_cache_pool_destroy(vm->mem_cache_pool);
 }
 
@@ -294,7 +317,7 @@ fail:
 
 
 njs_vm_t *
-njs_vm_clone(njs_vm_t *vm, void *external)
+njs_vm_clone(njs_vm_t *vm, njs_external_ptr_t external)
 {
     njs_vm_t              *nvm;
     uint32_t              items;
@@ -341,6 +364,8 @@ njs_vm_clone(njs_vm_t *vm, void *external)
             vm->external_objects->items = items;
         }
 
+        nvm->ops = vm->ops;
+
         nvm->current = vm->current;
 
         nvm->external = external;
@@ -413,6 +438,9 @@ njs_vm_init(njs_vm_t *vm)
         return NXT_ERROR;
     }
 
+    nxt_lvlhsh_init(&vm->events_hash);
+    nxt_queue_init(&vm->posted_events);
+
     if (vm->debug != NULL) {
         backtrace = nxt_array_create(4, sizeof(njs_backtrace_entry_t),
                                      &njs_array_mem_proto, vm->mem_cache_pool);
@@ -475,6 +503,29 @@ njs_vm_call(njs_vm_t *vm, njs_function_t *function, njs_opaque_value_t *args,
 }
 
 
+nxt_int_t
+njs_vm_pending(njs_vm_t *vm)
+{
+    return njs_is_pending_events(vm);
+}
+
+
+nxt_int_t
+njs_vm_post_event(njs_vm_t *vm, njs_vm_event_t vm_event)
+{
+    njs_event_t  *event;
+
+    event = (njs_event_t *) vm_event;
+
+    if (!event->posted) {
+        event->posted = 1;
+        nxt_queue_insert_tail(&vm->posted_events, &event->link);
+    }
+
+    return NJS_OK;
+}
+
+
 nxt_int_t
 njs_vm_run(njs_vm_t *vm)
 {
@@ -489,6 +540,10 @@ njs_vm_run(njs_vm_t *vm)
 
     ret = njs_vmcode_interpreter(vm);
 
+    if (ret == NJS_STOP) {
+        ret = njs_vm_handle_events(vm);
+    }
+
     if (nxt_slow_path(ret == NXT_AGAIN)) {
         nxt_thread_log_debug("VM: AGAIN");
         return ret;
@@ -528,6 +583,38 @@ njs_vm_run(njs_vm_t *vm)
 }
 
 
+static nxt_int_t
+njs_vm_handle_events(njs_vm_t *vm)
+{
+    nxt_int_t         ret;
+    njs_event_t       *ev;
+    nxt_queue_t       *events;
+    nxt_queue_link_t  *link;
+
+    events = &vm->posted_events;
+
+    for ( ;; ) {
+        link = nxt_queue_first(events);
+
+        if (link == nxt_queue_tail(events)) {
+            break;
+        }
+
+        ev = nxt_queue_link_data(link, njs_event_t, link);
+
+        njs_del_event(vm, ev, NJS_EVENT_DELETE);
+
+        ret = njs_vm_call(vm, ev->function, ev->args, ev->nargs);
+
+        if (ret == NJS_ERROR) {
+            return ret;
+        }
+    }
+
+    return njs_is_pending_events(vm) ? NJS_AGAIN : NJS_STOP;
+}
+
+
 nxt_noinline njs_value_t *
 njs_vm_retval(njs_vm_t *vm)
 {
index 9dd24c92d674ed250ae1b3c78ea426cd8912e122..ad4165af437eab71ed2ae804b19dc0371d7292d3 100644 (file)
@@ -70,9 +70,47 @@ struct njs_external_s {
     uintptr_t                       data;
 };
 
+
+/*
+ * NJS and event loops.
+ *
+ * njs_vm_ops_t callbacks are used to interact with the event loop environment.
+ *
+ * Functions get an external object as the first argument. The external
+ * object is provided as the third argument to njs_vm_clone().
+ *
+ * The callbacks are expected to return to the VM the unique id of an
+ * underlying event.  This id will be passed as the second argument to
+ * njs_event_destructor() at the moment the VM wants to destroy it.
+ *
+ * When an underlying events fires njs_vm_post_event() should be invoked with
+ * the value provided as vm_event.
+ *
+ * The events posted by njs_vm_post_event() are processed as soon as
+ * njs_vm_run() is invoked. njs_vm_run() returns NJS_AGAIN until pending events
+ * are present.
+ */
+
+typedef void *                      njs_vm_event_t;
+typedef void *                      njs_host_event_t;
+typedef void *                      njs_external_ptr_t;
+
+typedef njs_host_event_t (*njs_set_timer)(njs_external_ptr_t external,
+    uint64_t delay, njs_vm_event_t vm_event);
+typedef void (*njs_event_destructor)(njs_external_ptr_t external,
+    njs_host_event_t event);
+
+
+typedef struct {
+    njs_set_timer                   set_timer;
+    njs_event_destructor            clear_timer;
+} njs_vm_ops_t;
+
+
 typedef struct {
-    void                            *external;
+    njs_external_ptr_t              external;
     njs_vm_shared_t                 *shared;
+    njs_vm_ops_t                    *ops;
 
     uint8_t                         trailer;         /* 1 bit */
     uint8_t                         accumulative;    /* 1 bit */
@@ -97,9 +135,13 @@ NXT_EXPORT njs_vm_t *njs_vm_create(njs_vm_opt_t *options);
 NXT_EXPORT void njs_vm_destroy(njs_vm_t *vm);
 
 NXT_EXPORT nxt_int_t njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end);
-NXT_EXPORT njs_vm_t *njs_vm_clone(njs_vm_t *vm, void *external);
+NXT_EXPORT njs_vm_t *njs_vm_clone(njs_vm_t *vm, njs_external_ptr_t external);
 NXT_EXPORT nxt_int_t njs_vm_call(njs_vm_t *vm, njs_function_t *function,
     njs_opaque_value_t *args, nxt_uint_t nargs);
+
+NXT_EXPORT nxt_int_t njs_vm_pending(njs_vm_t *vm);
+NXT_EXPORT nxt_int_t njs_vm_post_event(njs_vm_t *vm, njs_vm_event_t vm_event);
+
 NXT_EXPORT nxt_int_t njs_vm_run(njs_vm_t *vm);
 
 NXT_EXPORT const njs_extern_t *njs_vm_external_prototype(njs_vm_t *vm,
index b09bccac6ee2edaa3d33837276eba3afbe417df1..55fe25b01fa6870b24e2c8490a5c798483d78b49 100644 (file)
@@ -172,6 +172,11 @@ static njs_interactive_test_t  njs_test[] =
                  "    at require (native)\n"
                  "    at main (native)\n") },
 
+    { nxt_string("setTimeout()" ENTER),
+      nxt_string("TypeError: too few arguments\n"
+                 "    at setTimeout (native)\n"
+                 "    at main (native)\n") },
+
     { nxt_string("function f(o) {function f_in(o) {return o.a.a};"
                  "               return f_in(o)}; f({})" ENTER),
       nxt_string("TypeError: cannot get property 'a' of undefined\n"
index 1b457ba475172f5423da0ca915d5925f9cc2da96..f451ef9e4ddce752608b62fd878f5e3319276986 100644 (file)
@@ -9003,6 +9003,25 @@ static njs_unit_test_t  njs_test[] =
                  "fs.writeFileSync('/njs_unknown_path', '', true)"),
       nxt_string("TypeError: Unknown options type (a string or object required)") },
 
+    /* setTimeout(). */
+
+    { nxt_string("setTimeout()"),
+      nxt_string("TypeError: too few arguments") },
+
+    { nxt_string("setTimeout(function(){})"),
+      nxt_string("InternalError: not supported by host environment") },
+
+    { nxt_string("setTimeout(function(){}, 12)"),
+      nxt_string("InternalError: not supported by host environment") },
+
+    /* clearTimeout(). */
+
+    { nxt_string("clearTimeout()"),
+      nxt_string("undefined") },
+
+    { nxt_string("clearTimeout(123)"),
+      nxt_string("undefined") },
+
     /* Trick: number to boolean. */
 
     { nxt_string("var a = 0; !!a"),